From c47d3b984bb673912b5e91e988540bbbedfd9f8c Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 4 Feb 2017 15:04:32 +0100 Subject: [PATCH 01/23] Create a simple home page --- .pylintrc | 317 ----- DBUtils.css | 113 ++ DBUtils/Examples/DBUtilsExample.py | 453 ------ DBUtils/Examples/Main.py | 21 - DBUtils/Examples/__init__.py | 1 - DBUtils/PersistentDB.py | 218 --- DBUtils/PersistentPg.py | 165 --- DBUtils/PooledDB.py | 535 -------- DBUtils/PooledPg.py | 295 ---- DBUtils/Properties.py | 17 - DBUtils/SimplePooledDB.py | 222 --- DBUtils/SimplePooledPg.py | 138 -- DBUtils/SteadyDB.py | 672 --------- DBUtils/SteadyPg.py | 318 ----- DBUtils/Tests/TestPersistentDB.py | 292 ---- DBUtils/Tests/TestPersistentPg.py | 196 --- DBUtils/Tests/TestPooledDB.py | 1220 ----------------- DBUtils/Tests/TestPooledPg.py | 316 ----- DBUtils/Tests/TestSimplePooledDB.py | 174 --- DBUtils/Tests/TestSimplePooledPg.py | 139 -- DBUtils/Tests/TestSteadyDB.py | 753 ---------- DBUtils/Tests/TestSteadyPg.py | 403 ------ DBUtils/Tests/TestThreadingLocal.py | 85 -- DBUtils/Tests/__init__.py | 1 - DBUtils/ThreadingLocal.py | 102 -- DBUtils/__init__.py | 12 - DBUtils/Docs/Doc.css => Doc.css | 0 DBUtils/Docs/DocUtils.css => DocUtils.css | 0 MANIFEST.in | 2 - ...RelNotes-0.8.1.html => RelNotes-0.8.1.html | 0 ...RelNotes-0.9.1.html => RelNotes-0.9.1.html | 0 ...RelNotes-0.9.2.html => RelNotes-0.9.2.html | 0 ...RelNotes-0.9.3.html => RelNotes-0.9.3.html | 0 ...RelNotes-0.9.4.html => RelNotes-0.9.4.html | 0 .../RelNotes-1.0.html => RelNotes-1.0.html | 0 .../RelNotes-1.1.html => RelNotes-1.1.html | 0 Release.md | 58 - .../UsersGuide.de.html => UsersGuide.de.html | 0 .../UsersGuide.de.rst => UsersGuide.de.rst | 0 .../Docs/UsersGuide.html => UsersGuide.html | 0 DBUtils/Docs/UsersGuide.rst => UsersGuide.rst | 0 DBUtils/Docs/dbdep.gif => dbdep.gif | Bin favicon16.png | Bin 0 -> 1905 bytes favicon32.png | Bin 0 -> 2841 bytes favicon96.png | Bin 0 -> 10868 bytes index.html | 81 ++ logo.png | Bin 0 -> 6518 bytes DBUtils/Docs/persist.gif => persist.gif | Bin DBUtils/Docs/pgdep.gif => pgdep.gif | Bin picnic.css | 2 + DBUtils/Docs/pool.gif => pool.gif | Bin setup.py | 72 - setversion.py | 114 -- 53 files changed, 196 insertions(+), 7311 deletions(-) delete mode 100644 .pylintrc create mode 100644 DBUtils.css delete mode 100644 DBUtils/Examples/DBUtilsExample.py delete mode 100644 DBUtils/Examples/Main.py delete mode 100644 DBUtils/Examples/__init__.py delete mode 100644 DBUtils/PersistentDB.py delete mode 100644 DBUtils/PersistentPg.py delete mode 100644 DBUtils/PooledDB.py delete mode 100644 DBUtils/PooledPg.py delete mode 100644 DBUtils/Properties.py delete mode 100644 DBUtils/SimplePooledDB.py delete mode 100644 DBUtils/SimplePooledPg.py delete mode 100644 DBUtils/SteadyDB.py delete mode 100644 DBUtils/SteadyPg.py delete mode 100644 DBUtils/Tests/TestPersistentDB.py delete mode 100644 DBUtils/Tests/TestPersistentPg.py delete mode 100644 DBUtils/Tests/TestPooledDB.py delete mode 100644 DBUtils/Tests/TestPooledPg.py delete mode 100644 DBUtils/Tests/TestSimplePooledDB.py delete mode 100644 DBUtils/Tests/TestSimplePooledPg.py delete mode 100644 DBUtils/Tests/TestSteadyDB.py delete mode 100644 DBUtils/Tests/TestSteadyPg.py delete mode 100644 DBUtils/Tests/TestThreadingLocal.py delete mode 100644 DBUtils/Tests/__init__.py delete mode 100644 DBUtils/ThreadingLocal.py delete mode 100644 DBUtils/__init__.py rename DBUtils/Docs/Doc.css => Doc.css (100%) rename DBUtils/Docs/DocUtils.css => DocUtils.css (100%) delete mode 100644 MANIFEST.in rename DBUtils/Docs/RelNotes-0.8.1.html => RelNotes-0.8.1.html (100%) rename DBUtils/Docs/RelNotes-0.9.1.html => RelNotes-0.9.1.html (100%) rename DBUtils/Docs/RelNotes-0.9.2.html => RelNotes-0.9.2.html (100%) rename DBUtils/Docs/RelNotes-0.9.3.html => RelNotes-0.9.3.html (100%) rename DBUtils/Docs/RelNotes-0.9.4.html => RelNotes-0.9.4.html (100%) rename DBUtils/Docs/RelNotes-1.0.html => RelNotes-1.0.html (100%) rename DBUtils/Docs/RelNotes-1.1.html => RelNotes-1.1.html (100%) delete mode 100644 Release.md rename DBUtils/Docs/UsersGuide.de.html => UsersGuide.de.html (100%) rename DBUtils/Docs/UsersGuide.de.rst => UsersGuide.de.rst (100%) rename DBUtils/Docs/UsersGuide.html => UsersGuide.html (100%) rename DBUtils/Docs/UsersGuide.rst => UsersGuide.rst (100%) rename DBUtils/Docs/dbdep.gif => dbdep.gif (100%) create mode 100644 favicon16.png create mode 100644 favicon32.png create mode 100644 favicon96.png create mode 100644 index.html create mode 100644 logo.png rename DBUtils/Docs/persist.gif => persist.gif (100%) rename DBUtils/Docs/pgdep.gif => pgdep.gif (100%) create mode 100644 picnic.css rename DBUtils/Docs/pool.gif => pool.gif (100%) delete mode 100755 setup.py delete mode 100755 setversion.py diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 4c3d6ec..0000000 --- a/.pylintrc +++ /dev/null @@ -1,317 +0,0 @@ -# lint Python modules using external checkers. -# -# This is the main checker controling the other ones and the reports -# generation. It is itself both a raw checker and an astng checker in order -# to: -# * handle message activation / deactivation at the module level -# * handle some basic but necessary stats'data (number of classes, methods...) - -# -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Profiled execution. -profile=no - -# Add to the black list. It should be a base name, not a -# path. You may set this option multiple times. -ignore=CVS - -# Pickle collected data for later comparisons. -persistent=yes - -# Set the cache size for astng objects. -cache-size=500 - -# List of plugins (as comma separated values of python modules names) to load, - -# usually to register additional checkers. -load-plugins= - - -[MESSAGES CONTROL] - -# Enable only checker(s) with the given id(s). This option conflict with the -# disable-checker option -#enable-checker= - -# Enable all checker(s) except those with the given id(s). This option conflict -# with the disable-checker option -#disable-checker= - -# Enable all messages in the listed categories. -#enable-msg-cat= - -# Disable all messages in the listed categories. -#disable-msg-cat= - -# Enable the message(s) with the given id(s). -#enable-msg= - -# Disable the message(s) with the given id(s). -#disable-msg= - - -[REPORTS] - -# set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html -output-format=text - -# Include message's id in output -include-ids=no - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - -# Tells wether to display a full report or only the messages -reports=yes - -# Python expression which should return a note less than 10 (10 is the highest -# note).You have access to the variables errors warning, statement which -# respectivly contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (R0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Add a comment according to your evaluation note. This is used by the global -# evaluation report (R0004). -comment=no - -# Enable the report(s) with the given id(s). -#enable-report= - -# Disable the report(s) with the given id(s). -#disable-report= - - -# checks for : - -# * doc strings -# * modules / classes / functions / methods / arguments / variables name -# * number of arguments, local variables, branchs, returns and statements in -# functions, methods -# * required module attributes -# * dangerous default values as arguments -# * redefinition of function / method / class -# * uses of the global statement -# -[BASIC] - -# Required attributes for module, separated by a comma -required-attributes= - -# Regular expression which should only match functions or classes name which do - -# not require a docstring -no-docstring-rgx=__.*__ - -# Regular expression which should only match correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression which should only match correct module level names -const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$ - -# Regular expression which should only match correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression which should only match correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct instance attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct list comprehension / - -# generator expression variable names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,apply,input - - -# try to find bugs in the code using type inference -# -[TYPECHECK] - -# Tells wether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# When zope mode is activated, consider the acquired-members option to ignore -# access to some undefined attributes. -zope=no - -# List of members which are usually get through zope's acquisition mecanism and -# so shouldn't trigger E0201 when accessed (need zope=yes to be considered). -acquired-members=REQUEST,acl_users,aq_parent - - -# checks for -# * unused variables / imports -# * undefined variables -# * redefinition of variable from builtins or from an outer scope -# * use of variable before assigment -# -[VARIABLES] - -# Tells wether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching names used for dummy variables (i.e. not used). -dummy-variables-rgx=_|dummy - -# List of additional names supposed to be defined in builtins. Remember that - -# you should avoid to define new builtins when possible. -additional-builtins= - - -# checks for : -# * methods without self as first argument -# * overridden methods signature -# * access only to existant members via self -# * attributes not defined in the __init__ method -# * supported interfaces implementation -# * unreachable code -# -[CLASSES] - -# List of interface methods to ignore, separated by a comma. This is used for -# instance to not check methods defines in Zope's Interface base class. -ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - - -# checks for sign of poor/misdesign: -# * number of methods, attributes, local variables... -# * size, complexity of functions, methods -# -[DESIGN] - -# Maximum number of arguments for function / method -#max-args=5 -max-args=15 - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -#max-branchs=12 -max-branchs=25 - -# Maximum number of statements in function / method body -#max-statements=50 -max-statements=75 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -#max-attributes=7 -max-attributes=20 - -# Minimum number of public methods for a class (see R0903). -#min-public-methods=2 -min-public-methods=1 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - - -# checks for -# * external modules dependencies -# * relative / wildcard imports -# * cyclic imports -# * uses of deprecated modules -# -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,string,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report R0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report R0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report R0402 must -# not be disabled) -int-import-graph= - - -# checks for : -# * unauthorized constructions -# * strict indentation -# * line length -# * use of <> instead of != -# -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=80 - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - - -# checks for: -# * warning notes in the code like FIXME, XXX -# * PEP 263: source code with non ascii character but no encoding declaration -# -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -# checks for similarities and duplicated code. This computation may be -# memory / CPU intensive, so you should disable it if you experiments some -# problems. - -# -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes diff --git a/DBUtils.css b/DBUtils.css new file mode 100644 index 0000000..b2cd83c --- /dev/null +++ b/DBUtils.css @@ -0,0 +1,113 @@ +body { + color: #001122; +} + +header.hero { + height: 160px; + margin: 0 0 40px 0; + overflow: hidden; + z-index: 1; +} + +header.hero .title { + height: 120px; + margin: 0; + padding: 24px 8px 2px 40px; + color: #003366; + font-size: 72px; + font-style: italic; + font-weight: bolder; + vertical-align: bottom; +} + +header.hero .title small { + font-size: 36px; +} + +header.hero .subtitle { + height: 40px; + margin: 0; + padding: 2px 8px 2px 40px; + background-color: #003366; + color: #f0f4f8; + font-size: 24px; + font-weight: normal; +} + +header.hero img.logo { + position: absolute; + top: 48px; + right: 8px; + border-style: none; + z-index: 2; +} + +@media screen and (max-width: 595px) { + header.hero img.logo { + display: none; + } +} + +div.slogan { + padding: 2pt 8pt; + color: #336699; + text-align: center; + font-size: 20px; + font-weight: bolder; +} + +div.content { + padding: 16pt; +} + +section.card { + margin: 8pt; + text-align: center; +} + +section.card > header { + background-color: #0b6330; +} + +section.card > header h2 { + color: #f0f8f4; + margin: 0 2pt; +} + +section.card > div { + padding: 8pt; + background-color: #f0f8f4; +} + +section#papers div { + font-size: smaller; + text-align: left; +} + +.small-button { + font-size: .75em; +} + +.button { + background: #003366; +} + +a { + color: #0b6330; +} + +a:hover { + text-decoration: underline; +} + +a.button:hover { + text-decoration: none; +} + +footer { + padding: 8pt; + text-align: center; + color: #336699; + font-size: 16px; + border-top: 1px solid #cccccc; +} diff --git a/DBUtils/Examples/DBUtilsExample.py b/DBUtils/Examples/DBUtilsExample.py deleted file mode 100644 index 0a58f66..0000000 --- a/DBUtils/Examples/DBUtilsExample.py +++ /dev/null @@ -1,453 +0,0 @@ - -from MiscUtils.Configurable import Configurable -from WebKit.Examples.ExamplePage import ExamplePage - - -class DBConfig(Configurable): - """Database configuration.""" - - def defaultConfig(self): - return { - 'dbapi': 'pg', - 'database': 'demo', - 'user': 'demo', - 'password': 'demo', - 'mincached': 5, - 'maxcached': 25 - } - - def configFilename(self): - return 'Configs/Database.config' - - -# the database tables used in this example: -tables = ('''seminars ( - id varchar(4) primary key, - title varchar(64) unique not null, - cost money, - places_left smallint)''', -'''attendees ( - name varchar(64) not null, - seminar varchar(4), - paid boolean, - primary key(name, seminar), - foreign key (seminar) references seminars(id) on delete cascade)''') - - -class DBUtilsExample(ExamplePage): - """Example page for the DBUtils package.""" - - # Initialize the database class once when this class is loaded: - config = DBConfig().config() - if config.get('maxcached', None) is None: - dbmod_name = 'Persistent' - else: - dbmod_name = 'Pooled' - dbapi_name = config.pop('dbapi', 'pg') - if dbapi_name == 'pg': # use the PyGreSQL classic DB API - dbmod_name += 'Pg' - if config.has_key('database'): - config['dbname'] = config['database'] - del config['database'] - if config.has_key('password'): - config['passwd'] = config['password'] - del config['password'] - else: # use a DB-API 2 compliant module - dbmod_name += 'DB' - dbapi = dbmod = dbclass = dbstatus = None - try: - dbapi = __import__(dbapi_name) - try: - dbmod = getattr(__import__('DBUtils.' + dbmod_name), dbmod_name) - try: - if dbapi_name != 'pg': - config['creator'] = dbapi - dbclass = getattr(dbmod, dbmod_name)(**config) - except dbapi.Error, error: - dbstatus = str(error) - except Exception: - dbstatus = 'Could not connect to the database.' - except Exception: - dbstatus = 'Could not import DBUtils.%s.' % dbmod_name - except Exception: - dbstatus = 'Could not import %s.' % dbapi_name - - # Initialize the buttons - _actions = [] - _buttons = [] - for action in ('create tables', - 'list seminars', 'list attendees', - 'add seminar', 'add attendee'): - value = action.capitalize() - action = action.split() - action[1] = action[1].capitalize() - action = ''.join(action) - _actions.append(action) - _buttons.append('' % (action, value)) - _buttons = tuple(_buttons) - - def title(self): - return "DBUtils Example" - - def actions(self): - return ExamplePage.actions(self) + self._actions - - def awake(self, transaction): - ExamplePage.awake(self, transaction) - self._output = [] - - def postAction(self, actionName): - self.writeBody() - del self._output - ExamplePage.postAction(self, actionName) - - def output(self, s): - self._output.append(s) - - def outputMsg(self, msg, error=False): - self._output.append('

%s

' - % (error and 'red' or 'green', msg)) - - def connection(self, shareable=True): - if self.dbstatus: - error = self.dbstatus - else: - try: - if self.dbmod_name == 'PooledDB': - return self.dbclass.connection(shareable) - else: - return self.dbclass.connection() - except self.dbapi.Error, error: - error = str(error) - except Exception: - error = 'Cannot connect to the database.' - self.outputMsg(error, True) - - def dedicated_connection(self): - return self.connection(False) - - def sqlEncode(self, s): - if s is None: - return 'null' - s = s.replace('\\', '\\\\').replace('\'', '\\\'') - return "'%s'" % s - - def createTables(self): - db = self.dedicated_connection() - if not db: - return - for table in tables: - self._output.append('

Creating the following table:

' - '
%s
' % table) - ddl = 'create table ' + table - try: - if self.dbapi_name == 'pg': - db.query(ddl) - else: - db.cursor().execute(ddl) - db.commit() - except self.dbapi.Error, error: - if self.dbapi_name != 'pg': - db.rollback() - self.outputMsg(error, True) - else: - self.outputMsg('The table was successfully created.') - db.close() - - def listSeminars(self): - id = self.request().field('id', None) - if id: - if type(id) != type([]): - id = [id] - cmd = ','.join(map(self.sqlEncode, id)) - cmd = 'delete from seminars where id in (%s)' % cmd - db = self.dedicated_connection() - if not db: - return - try: - if self.dbapi_name == 'pg': - db.query('begin') - db.query(cmd) - db.query('end') - else: - db.cursor().execute(cmd) - db.commit() - except self.dbapi.Error, error: - try: - if self.dbapi_name == 'pg': - db.query('end') - else: - db.rollback() - except Exception: - pass - self.outputMsg(error, True) - return - else: - self.outputMsg('Entries deleted: %d' % len(id)) - db = self.connection() - if not db: - return - query = ('select id, title, cost, places_left from seminars ' - 'order by title') - try: - if self.dbapi_name == 'pg': - result = db.query(query).getresult() - else: - cursor = db.cursor() - cursor.execute(query) - result = cursor.fetchall() - cursor.close() - except self.dbapi.Error, error: - self.outputMsg(error, True) - return - if not result: - self.outputMsg('There are no seminars in the database.', True) - return - wr = self.output - button = self._buttons[1].replace('List seminars', 'Delete') - wr('

List of seminars in the database:

') - wr('
' - '' - '' % button) - for id, title, cost, places in result: - if places is None: - places = 'unlimited' - if not cost: - cost = 'free' - wr('' % (id, title, cost, places, id)) - wr('
IDSeminar titleCostPlaces left%s
%s%s%s%s' - '' - '
') - - def listAttendees(self): - id = self.request().field('id', None) - if id: - if type(id) != type([]): - id = [id] - cmds = ['delete from attendees ' - 'where rpad(seminar,4)||name in (%s)' - % ','.join(map(self.sqlEncode, id))] - places = {} - for i in id: - i = i[:4].rstrip() - if places.has_key(i): - places[i] += 1 - else: - places[i] = 1 - for i, n in places.items(): - cmds.append("update seminars set places_left=places_left+%d " - "where id=%s" % (n, self.sqlEncode(i))) - db = self.dedicated_connection() - if not db: - return - try: - if self.dbapi_name == 'pg': - db.query('begin') - for cmd in cmds: - db.query(cmd) - db.query('end') - else: - for cmd in cmds: - db.cursor().execute(cmd) - db.commit() - except self.dbapi.Error, error: - if self.dbapi_name == 'pg': - db.query('end') - else: - db.rollback() - self.outputMsg(error, True) - return - else: - self.outputMsg('Entries deleted: %d' % len(id)) - db = self.connection() - if not db: - return - query = ('select a.name, s.id, s.title, a.paid ' - ' from attendees a,seminars s' - ' where s.id=a.seminar' - ' order by a.name, s.title') - try: - if self.dbapi_name == 'pg': - result = db.query(query).getresult() - else: - cursor = db.cursor() - cursor.execute(query) - result = cursor.fetchall() - cursor.close() - except self.dbapi.Error, error: - self.outputMsg(error, True) - return - if not result: - self.outputMsg('There are no attendees in the database.', True) - return - wr = self.output - button = self._buttons[2].replace('List attendees', 'Delete') - wr('

List of attendees in the database:

') - wr('
' - '' - '' % button) - for name, id, title, paid in result: - paid = paid and 'Yes' or 'No' - id = id.ljust(4) + name - wr('' - '' - '' % (name, title, paid, id)) - wr('
NameSeminarPaid%s
%s%s%s
') - - def addSeminar(self): - wr = self.output - wr('

Add a seminar entry to the database:

') - wr('
' - '' - '' - '' - '' - '' - '
ID
Title
Cost
Places
%s
' % self._buttons[3]) - request = self.request() - if not request.hasField('id'): - return - values = [] - for name in ('id', 'title', 'cost', 'places'): - values.append(request.field(name, '').strip()) - if not values[0] or not values[1]: - self.outputMsg('You must enter a seminar ID and a title!') - return - if not values[2]: - values[2] = None - if not values[3]: - values[3] = None - db = self.dedicated_connection() - if not db: - return - cmd = ('insert into seminars values (%s,%s,%s,%s)' - % tuple(map(self.sqlEncode, values))) - try: - if self.dbapi_name == 'pg': - db.query('begin') - db.query(cmd) - db.query('end') - else: - db.cursor().execute(cmd) - db.commit() - except self.dbapi.Error, error: - if self.dbapi_name == 'pg': - db.query('end') - else: - db.rollback() - self.outputMsg(error, True) - else: - self.outputMsg('"%s" added to seminars.' % values[1]) - db.close() - - def addAttendee(self): - db = self.connection() - if not db: - return - query = ('select id, title from seminars ' - 'where places_left is null or places_left>0 order by title') - try: - if self.dbapi_name == 'pg': - result = db.query(query).getresult() - else: - cursor = db.cursor() - cursor.execute(query) - result = cursor.fetchall() - cursor.close() - except self.dbapi.Error, error: - self.outputMsg(error, True) - return - if not result: - self.outputMsg('You have to define seminars first.') - return - sem = ['') - sem = ''.join(sem) - wr = self.output - wr('

Add an attendee entry to the database:

') - wr('
' - '' - '' - '' - '
Name
Seminar%s
Paid' - 'Yes ' - 'No' - '
%s
' % (sem, self._buttons[4])) - request = self.request() - if not request.hasField('name'): - return - values = [] - for name in ('name', 'seminar', 'paid'): - values.append(request.field(name, '').strip()) - if not values[0] or not values[1]: - self.outputMsg('You must enter a name and a seminar!') - return - db = self.dedicated_connection() - if not db: - return - try: - if self.dbapi_name == 'pg': - db.query('begin') - cmd = ("update seminars set places_left=places_left-1 " - "where id=%s" % self.sqlEncode(values[1])) - db.query(cmd) - cmd = ("select places_left from seminars " - "where id=%s" % self.sqlEncode(values[1])) - if (db.query(cmd).getresult()[0][0] or 0) < 0: - raise self.dbapi.Error("No more places left.") - cmd = ("insert into attendees values (%s,%s,%s)" - % tuple(map(self.sqlEncode, values))) - db.query(cmd) - db.query('end') - else: - cursor = db.cursor() - cmd = ("update seminars set places_left=places_left-1 " - "where id=%s" % self.sqlEncode(values[1])) - cursor.execute(cmd) - cmd = ("select places_left from seminars " - "where id=%s" % self.sqlEncode(values[1])) - cursor.execute(cmd) - if (cursor.fetchone()[0] or 0) < 0: - raise self.dbapi.Error("No more places left.") - cmd = ("insert into attendees values (%s,%s,%s)" - % tuple(map(self.sqlEncode, values))) - db.cursor().execute(cmd) - cursor.close() - db.commit() - except self.dbapi.Error, error: - if self.dbapi_name == 'pg': - db.query('end') - else: - db.rollback() - self.outputMsg(error, True) - else: - self.outputMsg('%s added to attendees.' % values[0]) - db.close() - - def writeContent(self): - wr = self.writeln - if self._output: - wr('\n'.join(self._output)) - wr('

Back

') - else: - wr('

Welcome to the %s!

' % self.title()) - wr('

We are using DBUtils.%s and the %s database module.

' - % (self.dbmod_name, self.dbapi_name)) - wr('

Configuration: %r

' % DBConfig().config()) - wr('

This example uses a small demo database ' - 'designed to track the attendees for a series of seminars ' - '(see "' - 'The Python DB-API" by Andrew Kuchling).

') - wr('
' - '

%s (create the needed database tables first)

' - '

%s %s (list all database entries)

' - '

%s %s (add entries)

' - '
' % self._buttons) diff --git a/DBUtils/Examples/Main.py b/DBUtils/Examples/Main.py deleted file mode 100644 index 39bf4b6..0000000 --- a/DBUtils/Examples/Main.py +++ /dev/null @@ -1,21 +0,0 @@ - -from WebKit.Examples.ExamplePage import ExamplePage - - -class Main(ExamplePage): - - def writeContent(self): - self.writeln('''

DBUtils example

-

You can set the DBUtils parameters in the following file

- -

With the default settings,

- -

Start the demo!

-''') diff --git a/DBUtils/Examples/__init__.py b/DBUtils/Examples/__init__.py deleted file mode 100644 index 71f8157..0000000 --- a/DBUtils/Examples/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# DBUtils Examples diff --git a/DBUtils/PersistentDB.py b/DBUtils/PersistentDB.py deleted file mode 100644 index e500f40..0000000 --- a/DBUtils/PersistentDB.py +++ /dev/null @@ -1,218 +0,0 @@ -"""PersistentDB - persistent DB-API 2 connections. - -Implements steady, thread-affine persistent connections to a database -based on an arbitrary DB-API 2 compliant database interface module. - -This should result in a speedup for persistent applications such as the -application server of "Webware for Python," without loss of robustness. - -Robustness is provided by using "hardened" SteadyDB connections. -Even if the underlying database is restarted and all connections -are lost, they will be automatically and transparently reopened. -However, since you don't want this to happen in the middle of a database -transaction, you must explicitly start transactions with the begin() -method so that SteadyDB knows that the underlying connection shall not -be replaced and errors passed on until the transaction is completed. - -Measures are taken to make the database connections thread-affine. -This means the same thread always uses the same cached connection, -and no other thread will use it. So even if the underlying DB-API module -is not thread-safe at the connection level this will be no problem here. - -For best performance, the application server should keep threads persistent. -For this, you have to set MinServerThreads = MaxServerThreads in Webware. - -For the Python DB-API 2 specification, see: - https://www.python.org/dev/peps/pep-0249/ -For information on Webware for Python, see: - https://cito.github.io/w4py/ - - -Usage: - -First you need to set up a generator for your kind of database connections -by creating an instance of PersistentDB, passing the following parameters: - - creator: either an arbitrary function returning new DB-API 2 - connection objects or a DB-API 2 compliant database module - maxusage: the maximum number of reuses of a single connection - (the default of 0 or None means unlimited reuse) - Whenever the limit is reached, the connection will be reset. - setsession: an optional list of SQL commands that may serve to - prepare the session, e.g. ["set datestyle to german", ...]. - failures: an optional exception class or a tuple of exception classes - for which the connection failover mechanism shall be applied, - if the default (OperationalError, InternalError) is not adequate - ping: an optional flag controlling when connections are checked - with the ping() method if such a method is available - (0 = None = never, 1 = default = whenever it is requested, - 2 = when a cursor is created, 4 = when a query is executed, - 7 = always, and all other bit combinations of these values) - closeable: if this is set to true, then closing connections will - be allowed, but by default this will be silently ignored - threadlocal: an optional class for representing thread-local data - that will be used instead of our Python implementation - (threading.local is faster, but cannot be used in all cases) - - The creator function or the connect function of the DB-API 2 compliant - database module specified as the creator will receive any additional - parameters such as the host, database, user, password etc. You may - choose some or all of these parameters in your own creator function, - allowing for sophisticated failover and load-balancing mechanisms. - -For instance, if you are using pgdb as your DB-API 2 database module and want -every connection to your local database 'mydb' to be reused 1000 times: - - import pgdb # import used DB-API 2 module - from DBUtils.PersistentDB import PersistentDB - persist = PersistentDB(pgdb, 1000, database='mydb') - -Once you have set up the generator with these parameters, you can -request database connections of that kind: - - db = persist.connection() - -You can use these connections just as if they were ordinary -DB-API 2 connections. Actually what you get is the hardened -SteadyDB version of the underlying DB-API 2 connection. - -Closing a persistent connection with db.close() will be silently -ignored since it would be reopened at the next usage anyway and -contrary to the intent of having persistent connections. Instead, -the connection will be automatically closed when the thread dies. -You can change this behavior be setting the closeable parameter. - -Note that you need to explicitly start transactions by calling the -begin() method. This ensures that the transparent reopening will be -suspended until the end of the transaction, and that the connection -will be rolled back before being reused by the same thread. - -By setting the threadlocal parameter to threading.local, getting -connections may become a bit faster, but this may not work in all -environments (for instance, mod_wsgi is known to cause problems -since it clears the threading.local data between requests). - - -Requirements: - -Python >= 2.3, < 3.0. - - -Ideas for improvement: - -* Add a thread for monitoring, restarting (or closing) bad or expired - connections (similar to DBConnectionPool/ResourcePool by Warren Smith). -* Optionally log usage, bad connections and exceeding of limits. - - -Copyright, credits and license: - -* Contributed as supplement for Webware for Python and PyGreSQL - by Christoph Zwerschke in September 2005 -* Based on an idea presented on the Webware developer mailing list - by Geoffrey Talvola in July 2005 - -Licensed under the MIT license. - -""" - -__version__ = '1.1.1b1' - -import ThreadingLocal -from DBUtils.SteadyDB import connect - - -class PersistentDBError(Exception): - """General PersistentDB error.""" - -class NotSupportedError(PersistentDBError): - """DB-API module not supported by PersistentDB.""" - - -class PersistentDB: - """Generator for persistent DB-API 2 connections. - - After you have created the connection pool, you can use - connection() to get thread-affine, steady DB-API 2 connections. - - """ - - version = __version__ - - def __init__(self, creator, - maxusage=None, setsession=None, failures=None, ping=1, - closeable=False, threadlocal=None, *args, **kwargs): - """Set up the persistent DB-API 2 connection generator. - - creator: either an arbitrary function returning new DB-API 2 - connection objects or a DB-API 2 compliant database module - maxusage: maximum number of reuses of a single connection - (number of database operations, 0 or None means unlimited) - Whenever the limit is reached, the connection will be reset. - setsession: optional list of SQL commands that may serve to prepare - the session, e.g. ["set datestyle to ...", "set time zone ..."] - failures: an optional exception class or a tuple of exception classes - for which the connection failover mechanism shall be applied, - if the default (OperationalError, InternalError) is not adequate - ping: determines when the connection should be checked with ping() - (0 = None = never, 1 = default = whenever it is requested, - 2 = when a cursor is created, 4 = when a query is executed, - 7 = always, and all other bit combinations of these values) - closeable: if this is set to true, then closing connections will - be allowed, but by default this will be silently ignored - threadlocal: an optional class for representing thread-local data - that will be used instead of our Python implementation - (threading.local is faster, but cannot be used in all cases) - args, kwargs: the parameters that shall be passed to the creator - function or the connection constructor of the DB-API 2 module - - """ - try: - threadsafety = creator.threadsafety - except AttributeError: - try: - if not callable(creator.connect): - raise AttributeError - except AttributeError: - threadsafety = 1 - else: - threadsafety = 0 - if not threadsafety: - raise NotSupportedError("Database module is not thread-safe.") - self._creator = creator - self._maxusage = maxusage - self._setsession = setsession - self._failures = failures - self._ping = ping - self._closeable = closeable - self._args, self._kwargs = args, kwargs - self.thread = (threadlocal or ThreadingLocal.local)() - - def steady_connection(self): - """Get a steady, non-persistent DB-API 2 connection.""" - return connect(self._creator, - self._maxusage, self._setsession, - self._failures, self._ping, self._closeable, - *self._args, **self._kwargs) - - def connection(self, shareable=False): - """Get a steady, persistent DB-API 2 connection. - - The shareable parameter exists only for compatibility with the - PooledDB connection method. In reality, persistent connections - are of course never shared with other threads. - - """ - try: - con = self.thread.connection - except AttributeError: - con = self.steady_connection() - if not con.threadsafety(): - raise NotSupportedError("Database module is not thread-safe.") - self.thread.connection = con - con._ping_check() - return con - - def dedicated_connection(self): - """Alias for connection(shareable=False).""" - return self.connection() diff --git a/DBUtils/PersistentPg.py b/DBUtils/PersistentPg.py deleted file mode 100644 index a6f6534..0000000 --- a/DBUtils/PersistentPg.py +++ /dev/null @@ -1,165 +0,0 @@ -"""PersistentPg - persistent classic PyGreSQL connections. - -Implements steady, thread-affine persistent connections to a PostgreSQL -database using the classic (not DB-API 2 compliant) PyGreSQL API. - -This should result in a speedup for persistent applications such as the -application server of "Webware for Python," without loss of robustness. - -Robustness is provided by using "hardened" SteadyPg connections. -Even if the underlying database is restarted and all connections -are lost, they will be automatically and transparently reopened. -However, since you don't want this to happen in the middle of a database -transaction, you must explicitly start transactions with the begin() -method so that SteadyPg knows that the underlying connection shall not -be replaced and errors passed on until the transaction is completed. - -Measures are taken to make the database connections thread-affine. -This means the same thread always uses the same cached connection, -and no other thread will use it. So the fact that the classic PyGreSQL -pg module is not thread-safe at the connection level is no problem here. - -For best performance, the application server should keep threads persistent. -For this, you have to set MinServerThreads = MaxServerThreads in Webware. - -For more information on PostgreSQL, see: - https://www.postgresql.org/ -For more information on PyGreSQL, see: - http://www.pygresql.org -For more information on Webware for Python, see: - https://cito.github.io/w4py/ - - -Usage: - -First you need to set up a generator for your kind of database connections -by creating an instance of PersistentPg, passing the following parameters: - - maxusage: the maximum number of reuses of a single connection - (the default of 0 or None means unlimited reuse) - When this maximum usage number of the connection is reached, - the connection is automatically reset (closed and reopened). - setsession: An optional list of SQL commands that may serve to - prepare the session, e.g. ["set datestyle to german", ...] - closeable: if this is set to true, then closing connections will - be allowed, but by default this will be silently ignored - threadlocal: an optional class for representing thread-local data - that will be used instead of our Python implementation - (threading.local is faster, but cannot be used in all cases) - - Additionally, you have to pass the parameters for the actual - PostgreSQL connection which are passed via PyGreSQL, - such as the names of the host, database, user, password etc. - -For instance, if you want every connection to your local database 'mydb' -to be reused 1000 times: - - from DBUtils.PersistentPg import PersistentPg - persist = PersistentPg(5, dbname='mydb') - -Once you have set up the generator with these parameters, you can -request database connections of that kind: - - db = persist.connection() - -You can use these connections just as if they were ordinary -classic PyGreSQL API connections. Actually what you get is the -hardened SteadyPg version of a classic PyGreSQL connection. - -Closing a persistent connection with db.close() will be silently -ignored since it would be reopened at the next usage anyway and -contrary to the intent of having persistent connections. Instead, -the connection will be automatically closed when the thread dies. -You can change this behavior be setting the closeable parameter. - -Note that you need to explicitly start transactions by calling the -begin() method. This ensures that the transparent reopening will be -suspended until the end of the transaction, and that the connection -will be rolled back before being reused in the same thread. To end -transactions, use one of the end(), commit() or rollback() methods. - -By setting the threadlocal parameter to threading.local, getting -connections may become a bit faster, but this may not work in all -environments (for instance, mod_wsgi is known to cause problems -since it clears the threading.local data between requests). - - -Requirements: - -Python >= 2.3, < 3.0, PyGreSQL >= 3.4. - - -Ideas for improvement: - -* Add a thread for monitoring, restarting (or closing) bad or expired - connections (similar to DBConnectionPool/ResourcePool by Warren Smith). -* Optionally log usage, bad connections and exceeding of limits. - - -Copyright, credits and license: - -* Contributed as supplement for Webware for Python and PyGreSQL - by Christoph Zwerschke in September 2005 -* Based on an idea presented on the Webware developer mailing list - by Geoffrey Talvola in July 2005 - -Licensed under the MIT license. - -""" - -__version__ = '1.1.1b1' - - -import ThreadingLocal -from DBUtils.SteadyPg import SteadyPgConnection - - -class PersistentPg: - """Generator for persistent classic PyGreSQL connections. - - After you have created the connection pool, you can use - connection() to get thread-affine, steady PostgreSQL connections. - - """ - - version = __version__ - - def __init__(self, maxusage=None, setsession=None, - closeable=False, threadlocal=None, *args, **kwargs): - """Set up the persistent PostgreSQL connection generator. - - maxusage: maximum number of reuses of a single connection - (0 or None means unlimited reuse) - When this maximum usage number of the connection is reached, - the connection is automatically reset (closed and reopened). - setsession: optional list of SQL commands that may serve to prepare - the session, e.g. ["set datestyle to ...", "set time zone ..."] - closeable: if this is set to true, then closing connections will - be allowed, but by default this will be silently ignored - threadlocal: an optional class for representing thread-local data - that will be used instead of our Python implementation - (threading.local is faster, but cannot be used in all cases) - args, kwargs: the parameters that shall be used to establish - the PostgreSQL connections using class PyGreSQL pg.DB() - - """ - self._maxusage = maxusage - self._setsession = setsession - self._closeable = closeable - self._args, self._kwargs = args, kwargs - self.thread = (threadlocal or ThreadingLocal.local)() - - def steady_connection(self): - """Get a steady, non-persistent PyGreSQL connection.""" - return SteadyPgConnection( - self._maxusage, self._setsession, self._closeable, - *self._args, **self._kwargs) - - def connection(self): - """Get a steady, persistent PyGreSQL connection.""" - try: - con = self.thread.connection - except AttributeError: - con = self.steady_connection() - self.thread.connection = con - return con diff --git a/DBUtils/PooledDB.py b/DBUtils/PooledDB.py deleted file mode 100644 index 0f2ade3..0000000 --- a/DBUtils/PooledDB.py +++ /dev/null @@ -1,535 +0,0 @@ -"""PooledDB - pooling for DB-API 2 connections. - -Implements a pool of steady, thread-safe cached connections -to a database which are transparently reused, -using an arbitrary DB-API 2 compliant database interface module. - -This should result in a speedup for persistent applications such as the -application server of "Webware for Python," without loss of robustness. - -Robustness is provided by using "hardened" SteadyDB connections. -Even if the underlying database is restarted and all connections -are lost, they will be automatically and transparently reopened. -However, since you don't want this to happen in the middle of a database -transaction, you must explicitly start transactions with the begin() -method so that SteadyDB knows that the underlying connection shall not -be replaced and errors passed on until the transaction is completed. - -Measures are taken to make the pool of connections thread-safe. -If the underlying DB-API module is thread-safe at the connection level, -the requested connections may be shared with other threads by default, -but you can also request dedicated connections in case you need them. - -For the Python DB-API 2 specification, see: - https://www.python.org/dev/peps/pep-0249/ -For information on Webware for Python, see: - https://cito.github.io/w4py/ - - -Usage: - -First you need to set up the database connection pool by creating -an instance of PooledDB, passing the following parameters: - - creator: either an arbitrary function returning new DB-API 2 - connection objects or a DB-API 2 compliant database module - mincached: the initial number of idle connections in the pool - (the default of 0 means no connections are made at startup) - maxcached: the maximum number of idle connections in the pool - (the default value of 0 or None means unlimited pool size) - maxshared: maximum number of shared connections allowed - (the default value of 0 or None means all connections are dedicated) - When this maximum number is reached, connections are - shared if they have been requested as shareable. - maxconnections: maximum number of connections generally allowed - (the default value of 0 or None means any number of connections) - blocking: determines behavior when exceeding the maximum - (if this is set to true, block and wait until the number of - connections decreases, but by default an error will be reported) - maxusage: maximum number of reuses of a single connection - (the default of 0 or None means unlimited reuse) - When this maximum usage number of the connection is reached, - the connection is automatically reset (closed and reopened). - setsession: an optional list of SQL commands that may serve to - prepare the session, e.g. ["set datestyle to german", ...] - reset: how connections should be reset when returned to the pool - (False or None to rollback transcations started with begin(), - the default value True always issues a rollback for safety's sake) - failures: an optional exception class or a tuple of exception classes - for which the connection failover mechanism shall be applied, - if the default (OperationalError, InternalError) is not adequate - ping: an optional flag controlling when connections are checked - with the ping() method if such a method is available - (0 = None = never, 1 = default = whenever fetched from the pool, - 2 = when a cursor is created, 4 = when a query is executed, - 7 = always, and all other bit combinations of these values) - - The creator function or the connect function of the DB-API 2 compliant - database module specified as the creator will receive any additional - parameters such as the host, database, user, password etc. You may - choose some or all of these parameters in your own creator function, - allowing for sophisticated failover and load-balancing mechanisms. - -For instance, if you are using pgdb as your DB-API 2 database module and -want a pool of at least five connections to your local database 'mydb': - - import pgdb # import used DB-API 2 module - from DBUtils.PooledDB import PooledDB - pool = PooledDB(pgdb, 5, database='mydb') - -Once you have set up the connection pool you can request -database connections from that pool: - - db = pool.connection() - -You can use these connections just as if they were ordinary -DB-API 2 connections. Actually what you get is the hardened -SteadyDB version of the underlying DB-API 2 connection. - -Please note that the connection may be shared with other threads -by default if you set a non-zero maxshared parameter and the DB-API 2 -module allows this. If you want to have a dedicated connection, use: - - db = pool.connection(shareable=False) - -You can also use this to get a dedicated connection: - - db = pool.dedicated_connection() - -If you don't need it any more, you should immediately return it to the -pool with db.close(). You can get another connection in the same way. - -Warning: In a threaded environment, never do the following: - - pool.connection().cursor().execute(...) - -This would release the connection too early for reuse which may be -fatal if the connections are not thread-safe. Make sure that the -connection object stays alive as long as you are using it, like that: - - db = pool.connection() - cur = db.cursor() - cur.execute(...) - res = cur.fetchone() - cur.close() # or del cur - db.close() # or del db - -Note that you need to explicitly start transactions by calling the -begin() method. This ensures that the connection will not be shared -with other threads, that the transparent reopening will be suspended -until the end of the transaction, and that the connection will be rolled -back before being given back to the connection pool. - - -Ideas for improvement: - -* Add a thread for monitoring, restarting (or closing) bad or expired - connections (similar to DBConnectionPool/ResourcePool by Warren Smith). -* Optionally log usage, bad connections and exceeding of limits. - - -Copyright, credits and license: - -* Contributed as supplement for Webware for Python and PyGreSQL - by Christoph Zwerschke in September 2005 -* Based on the code of DBPool, contributed to Webware for Python - by Dan Green in December 2000 - -Licensed under the MIT license. - -""" - -__version__ = '1.1.1b1' - - -from threading import Condition - -from DBUtils.SteadyDB import connect - - -class PooledDBError(Exception): - """General PooledDB error.""" - -class InvalidConnection(PooledDBError): - """Database connection is invalid.""" - -class NotSupportedError(PooledDBError): - """DB-API module not supported by PooledDB.""" - -class TooManyConnections(PooledDBError): - """Too many database connections were opened.""" - - -class PooledDB: - """Pool for DB-API 2 connections. - - After you have created the connection pool, you can use - connection() to get pooled, steady DB-API 2 connections. - - """ - - version = __version__ - - def __init__(self, creator, - mincached=0, maxcached=0, - maxshared=0, maxconnections=0, blocking=False, - maxusage=None, setsession=None, reset=True, - failures=None, ping=1, - *args, **kwargs): - """Set up the DB-API 2 connection pool. - - creator: either an arbitrary function returning new DB-API 2 - connection objects or a DB-API 2 compliant database module - mincached: initial number of idle connections in the pool - (0 means no connections are made at startup) - maxcached: maximum number of idle connections in the pool - (0 or None means unlimited pool size) - maxshared: maximum number of shared connections - (0 or None means all connections are dedicated) - When this maximum number is reached, connections are - shared if they have been requested as shareable. - maxconnections: maximum number of connections generally allowed - (0 or None means an arbitrary number of connections) - blocking: determines behavior when exceeding the maximum - (if this is set to true, block and wait until the number of - connections decreases, otherwise an error will be reported) - maxusage: maximum number of reuses of a single connection - (0 or None means unlimited reuse) - When this maximum usage number of the connection is reached, - the connection is automatically reset (closed and reopened). - setsession: optional list of SQL commands that may serve to prepare - the session, e.g. ["set datestyle to ...", "set time zone ..."] - reset: how connections should be reset when returned to the pool - (False or None to rollback transcations started with begin(), - True to always issue a rollback for safety's sake) - failures: an optional exception class or a tuple of exception classes - for which the connection failover mechanism shall be applied, - if the default (OperationalError, InternalError) is not adequate - ping: determines when the connection should be checked with ping() - (0 = None = never, 1 = default = whenever fetched from the pool, - 2 = when a cursor is created, 4 = when a query is executed, - 7 = always, and all other bit combinations of these values) - args, kwargs: the parameters that shall be passed to the creator - function or the connection constructor of the DB-API 2 module - - """ - try: - threadsafety = creator.threadsafety - except AttributeError: - try: - if not callable(creator.connect): - raise AttributeError - except AttributeError: - threadsafety = 2 - else: - threadsafety = 0 - if not threadsafety: - raise NotSupportedError("Database module is not thread-safe.") - self._creator = creator - self._args, self._kwargs = args, kwargs - self._blocking = blocking - self._maxusage = maxusage - self._setsession = setsession - self._reset = reset - self._failures = failures - self._ping = ping - if mincached is None: - mincached = 0 - if maxcached is None: - maxcached = 0 - if maxconnections is None: - maxconnections = 0 - if maxcached: - if maxcached < mincached: - maxcached = mincached - self._maxcached = maxcached - else: - self._maxcached = 0 - if threadsafety > 1 and maxshared: - self._maxshared = maxshared - self._shared_cache = [] # the cache for shared connections - else: - self._maxshared = 0 - if maxconnections: - if maxconnections < maxcached: - maxconnections = maxcached - if maxconnections < maxshared: - maxconnections = maxshared - self._maxconnections = maxconnections - else: - self._maxconnections = 0 - self._idle_cache = [] # the actual pool of idle connections - self._lock = Condition() - self._connections = 0 - # Establish an initial number of idle database connections: - idle = [self.dedicated_connection() for i in range(mincached)] - while idle: - idle.pop().close() - - def steady_connection(self): - """Get a steady, unpooled DB-API 2 connection.""" - return connect(self._creator, - self._maxusage, self._setsession, - self._failures, self._ping, True, - *self._args, **self._kwargs) - - def connection(self, shareable=True): - """Get a steady, cached DB-API 2 connection from the pool. - - If shareable is set and the underlying DB-API 2 allows it, - then the connection may be shared with other threads. - - """ - if shareable and self._maxshared: - self._lock.acquire() - try: - while (not self._shared_cache and self._maxconnections - and self._connections >= self._maxconnections): - self._wait_lock() - if len(self._shared_cache) < self._maxshared: - # shared cache is not full, get a dedicated connection - try: # first try to get it from the idle cache - con = self._idle_cache.pop(0) - except IndexError: # else get a fresh connection - con = self.steady_connection() - else: - con._ping_check() # check this connection - con = SharedDBConnection(con) - self._connections += 1 - else: # shared cache full or no more connections allowed - self._shared_cache.sort() # least shared connection first - con = self._shared_cache.pop(0) # get it - while con.con._transaction: - # do not share connections which are in a transaction - self._shared_cache.insert(0, con) - self._wait_lock() - self._shared_cache.sort() - con = self._shared_cache.pop(0) - con.con._ping_check() # check the underlying connection - con.share() # increase share of this connection - # put the connection (back) into the shared cache - self._shared_cache.append(con) - self._lock.notify() - finally: - self._lock.release() - con = PooledSharedDBConnection(self, con) - else: # try to get a dedicated connection - self._lock.acquire() - try: - while (self._maxconnections - and self._connections >= self._maxconnections): - self._wait_lock() - # connection limit not reached, get a dedicated connection - try: # first try to get it from the idle cache - con = self._idle_cache.pop(0) - except IndexError: # else get a fresh connection - con = self.steady_connection() - else: - con._ping_check() # check connection - con = PooledDedicatedDBConnection(self, con) - self._connections += 1 - finally: - self._lock.release() - return con - - def dedicated_connection(self): - """Alias for connection(shareable=False).""" - return self.connection(False) - - def unshare(self, con): - """Decrease the share of a connection in the shared cache.""" - self._lock.acquire() - try: - con.unshare() - shared = con.shared - if not shared: # connection is idle, - try: # so try to remove it - self._shared_cache.remove(con) # from shared cache - except ValueError: - pass # pool has already been closed - finally: - self._lock.release() - if not shared: # connection has become idle, - self.cache(con.con) # so add it to the idle cache - - def cache(self, con): - """Put a dedicated connection back into the idle cache.""" - self._lock.acquire() - try: - if not self._maxcached or len(self._idle_cache) < self._maxcached: - con._reset(force=self._reset) # rollback possible transaction - # the idle cache is not full, so put it there - self._idle_cache.append(con) # append it to the idle cache - else: # if the idle cache is already full, - con.close() # then close the connection - self._connections -= 1 - self._lock.notify() - finally: - self._lock.release() - - def close(self): - """Close all connections in the pool.""" - self._lock.acquire() - try: - while self._idle_cache: # close all idle connections - con = self._idle_cache.pop(0) - try: - con.close() - except Exception: - pass - if self._maxshared: # close all shared connections - while self._shared_cache: - con = self._shared_cache.pop(0).con - try: - con.close() - except Exception: - pass - self._connections -= 1 - self._lock.notifyAll() - finally: - self._lock.release() - - def __del__(self): - """Delete the pool.""" - try: - self.close() - except Exception: - pass - - def _wait_lock(self): - """Wait until notified or report an error.""" - if not self._blocking: - raise TooManyConnections - self._lock.wait() - - -# Auxiliary classes for pooled connections - -class PooledDedicatedDBConnection: - """Auxiliary proxy class for pooled dedicated connections.""" - - def __init__(self, pool, con): - """Create a pooled dedicated connection. - - pool: the corresponding PooledDB instance - con: the underlying SteadyDB connection - - """ - # basic initialization to make finalizer work - self._con = None - # proper initialization of the connection - if not con.threadsafety(): - raise NotSupportedError("Database module is not thread-safe.") - self._pool = pool - self._con = con - - def close(self): - """Close the pooled dedicated connection.""" - # Instead of actually closing the connection, - # return it to the pool for future reuse. - if self._con: - self._pool.cache(self._con) - self._con = None - - def __getattr__(self, name): - """Proxy all members of the class.""" - if self._con: - return getattr(self._con, name) - else: - raise InvalidConnection - - def __del__(self): - """Delete the pooled connection.""" - try: - self.close() - except Exception: - pass - - -class SharedDBConnection: - """Auxiliary class for shared connections.""" - - def __init__(self, con): - """Create a shared connection. - - con: the underlying SteadyDB connection - - """ - self.con = con - self.shared = 1 - - def __lt__(self, other): - if self.con._transaction == other.con._transaction: - return self.shared < other.shared - else: - return not self.con._transaction - - def __le__(self, other): - if self.con._transaction == other.con._transaction: - return self.shared <= other.shared - else: - return not self.con._transaction - - def __eq__(self, other): - return (self.con._transaction == other.con._transaction - and self.shared == other.shared) - - def __ne__(self, other): - return not self.__eq__(other) - - def __gt__(self, other): - return other.__lt__(self) - - def __ge__(self, other): - return other.__le__(self) - - def share(self): - """Increase the share of this connection.""" - self.shared += 1 - - def unshare(self): - """Decrease the share of this connection.""" - self.shared -= 1 - - -class PooledSharedDBConnection: - """Auxiliary proxy class for pooled shared connections.""" - - def __init__(self, pool, shared_con): - """Create a pooled shared connection. - - pool: the corresponding PooledDB instance - con: the underlying SharedDBConnection - - """ - # basic initialization to make finalizer work - self._con = None - # proper initialization of the connection - con = shared_con.con - if not con.threadsafety() > 1: - raise NotSupportedError("Database connection is not thread-safe.") - self._pool = pool - self._shared_con = shared_con - self._con = con - - def close(self): - """Close the pooled shared connection.""" - # Instead of actually closing the connection, - # unshare it and/or return it to the pool. - if self._con: - self._pool.unshare(self._shared_con) - self._shared_con = self._con = None - - def __getattr__(self, name): - """Proxy all members of the class.""" - if self._con: - return getattr(self._con, name) - else: - raise InvalidConnection - - def __del__(self): - """Delete the pooled connection.""" - try: - self.close() - except Exception: - pass diff --git a/DBUtils/PooledPg.py b/DBUtils/PooledPg.py deleted file mode 100644 index a052bb3..0000000 --- a/DBUtils/PooledPg.py +++ /dev/null @@ -1,295 +0,0 @@ -"""PooledPg - pooling for classic PyGreSQL connections. - -Implements a pool of steady, thread-safe cached connections -to a PostgreSQL database which are transparently reused, -using the classic (not DB-API 2 compliant) PyGreSQL API. - -This should result in a speedup for persistent applications such as the -application server of "Webware for Python," without loss of robustness. - -Robustness is provided by using "hardened" SteadyPg connections. -Even if the underlying database is restarted and all connections -are lost, they will be automatically and transparently reopened. -However, since you don't want this to happen in the middle of a database -transaction, you must explicitly start transactions with the begin() -method so that SteadyPg knows that the underlying connection shall not -be replaced and errors passed on until the transaction is completed. - -Measures are taken to make the pool of connections thread-safe -regardless of the fact that the classic PyGreSQL pg module itself -is not thread-safe at the connection level. - -For more information on PostgreSQL, see: - https://www.postgresql.org/ -For more information on PyGreSQL, see: - http://www.pygresql.org -For more information on Webware for Python, see: - https://cito.github.io/w4py/ - - -Usage: - -First you need to set up the database connection pool by creating -an instance of PooledPg, passing the following parameters: - - mincached: the initial number of connections in the pool - (the default of 0 means no connections are made at startup) - maxcached: the maximum number of connections in the pool - (the default value of 0 or None means unlimited pool size) - maxconnections: maximum number of connections generally allowed - (the default value of 0 or None means any number of connections) - blocking: determines behavior when exceeding the maximum - (if this is set to true, block and wait until the number of - connections decreases, but by default an error will be reported) - maxusage: maximum number of reuses of a single connection - (the default of 0 or None means unlimited reuse) - When this maximum usage number of the connection is reached, - the connection is automatically reset (closed and reopened). - setsession: an optional list of SQL commands that may serve to - prepare the session, e.g. ["set datestyle to german", ...] - - Additionally, you have to pass the parameters for the actual - PostgreSQL connection which are passed via PyGreSQL, - such as the names of the host, database, user, password etc. - -For instance, if you want a pool of at least five connections -to your local database 'mydb': - - from DBUtils.PooledPg import PooledPg - pool = PooledPg(5, dbname='mydb') - -Once you have set up the connection pool you can request -database connections from that pool: - - db = pool.connection() - -You can use these connections just as if they were ordinary -classic PyGreSQL API connections. Actually what you get is a -proxy class for the hardened SteadyPg version of the connection. - -The connection will not be shared with other threads. If you don't need -it any more, you should immediately return it to the pool with db.close(). -You can get another connection in the same way or with db.reopen(). - -Warning: In a threaded environment, never do the following: - - res = pool.connection().query(...).getresult() - -This would release the connection too early for reuse which may be -fatal because the connections are not thread-safe. Make sure that the -connection object stays alive as long as you are using it, like that: - - db = pool.connection() - res = db.query(...).getresult() - db.close() # or del db - -Note that you need to explicitly start transactions by calling the -begin() method. This ensures that the transparent reopening will be -suspended until the end of the transaction, and that the connection will -be rolled back before being given back to the connection pool. To end -transactions, use one of the end(), commit() or rollback() methods. - - -Ideas for improvement: - -* Add a thread for monitoring, restarting (or closing) bad or expired - connections (similar to DBConnectionPool/ResourcePool by Warren Smith). -* Optionally log usage, bad connections and exceeding of limits. - - -Copyright, credits and license: - -* Contributed as supplement for Webware for Python and PyGreSQL - by Christoph Zwerschke in September 2005 -* Based on the code of DBPool, contributed to Webware for Python - by Dan Green in December 2000 - -Licensed under the MIT license. - -""" - -__version__ = '1.1.1b1' - -from Queue import Queue, Empty, Full - -from DBUtils.SteadyPg import SteadyPgConnection - - -class PooledPgError(Exception): - """General PooledPg error.""" - -class InvalidConnection(PooledPgError): - """Database connection is invalid.""" - -class TooManyConnections(PooledPgError): - """Too many database connections were opened.""" - - -class PooledPg: - """Pool for classic PyGreSQL connections. - - After you have created the connection pool, you can use - connection() to get pooled, steady PostgreSQL connections. - - """ - - version = __version__ - - def __init__(self, - mincached=0, maxcached=0, - maxconnections=0, blocking=False, - maxusage=None, setsession=None, reset=None, - *args, **kwargs): - """Set up the PostgreSQL connection pool. - - mincached: initial number of connections in the pool - (0 means no connections are made at startup) - maxcached: maximum number of connections in the pool - (0 or None means unlimited pool size) - maxconnections: maximum number of connections generally allowed - (0 or None means an arbitrary number of connections) - blocking: determines behavior when exceeding the maximum - (if this is set to true, block and wait until the number of - connections decreases, otherwise an error will be reported) - maxusage: maximum number of reuses of a single connection - (0 or None means unlimited reuse) - When this maximum usage number of the connection is reached, - the connection is automatically reset (closed and reopened). - setsession: optional list of SQL commands that may serve to prepare - the session, e.g. ["set datestyle to ...", "set time zone ..."] - reset: how connections should be reset when returned to the pool - (0 or None to rollback transcations started with begin(), - 1 to always issue a rollback, 2 for a complete reset) - args, kwargs: the parameters that shall be used to establish - the PostgreSQL connections using class PyGreSQL pg.DB() - - """ - self._args, self._kwargs = args, kwargs - self._maxusage = maxusage - self._setsession = setsession - self._reset = reset or 0 - if mincached is None: - mincached = 0 - if maxcached is None: - maxcached = 0 - if maxconnections is None: - maxconnections = 0 - if maxcached: - if maxcached < mincached: - maxcached = mincached - if maxconnections: - if maxconnections < maxcached: - maxconnections = maxcached - # Create semaphore for number of allowed connections generally: - from threading import Semaphore - self._connections = Semaphore(maxconnections) - self._blocking = blocking - else: - self._connections = None - self._cache = Queue(maxcached) # the actual connection pool - # Establish an initial number of database connections: - idle = [self.connection() for i in range(mincached)] - while idle: - idle.pop().close() - - def steady_connection(self): - """Get a steady, unpooled PostgreSQL connection.""" - return SteadyPgConnection(self._maxusage, self._setsession, True, - *self._args, **self._kwargs) - - def connection(self): - """Get a steady, cached PostgreSQL connection from the pool.""" - if self._connections: - if not self._connections.acquire(self._blocking): - raise TooManyConnections - try: - con = self._cache.get(0) - except Empty: - con = self.steady_connection() - return PooledPgConnection(self, con) - - def cache(self, con): - """Put a connection back into the pool cache.""" - try: - if self._reset == 2: - con.reset() # reset the connection completely - else: - if self._reset or con._transaction: - try: - con.rollback() # rollback a possible transaction - except Exception: - pass - self._cache.put(con, 0) # and then put it back into the cache - except Full: - con.close() - if self._connections: - self._connections.release() - - def close(self): - """Close all connections in the pool.""" - while 1: - try: - con = self._cache.get(0) - try: - con.close() - except Exception: - pass - if self._connections: - self._connections.release() - except Empty: - break - - def __del__(self): - """Delete the pool.""" - try: - self.close() - except Exception: - pass - - -# Auxiliary class for pooled connections - -class PooledPgConnection: - """Proxy class for pooled PostgreSQL connections.""" - - def __init__(self, pool, con): - """Create a pooled DB-API 2 connection. - - pool: the corresponding PooledPg instance - con: the underlying SteadyPg connection - - """ - self._pool = pool - self._con = con - - def close(self): - """Close the pooled connection.""" - # Instead of actually closing the connection, - # return it to the pool so it can be reused. - if self._con: - self._pool.cache(self._con) - self._con = None - - def reopen(self): - """Reopen the pooled connection.""" - # If the connection is already back in the pool, - # get another connection from the pool, - # otherwise reopen the underlying connection. - if self._con: - self._con.reopen() - else: - self._con = self._pool.connection() - - def __getattr__(self, name): - """Proxy all members of the class.""" - if self._con: - return getattr(self._con, name) - else: - raise InvalidConnection - - def __del__(self): - """Delete the pooled connection.""" - try: - self.close() - except Exception: - pass diff --git a/DBUtils/Properties.py b/DBUtils/Properties.py deleted file mode 100644 index 26e6cd1..0000000 --- a/DBUtils/Properties.py +++ /dev/null @@ -1,17 +0,0 @@ -name = 'DBUtils' - -version = (1, 1, 1, 'b1') - -docs = [ - {'name': "User's Guide", 'file': 'UsersGuide.html'}, -] - -status = 'beta' - -requiredPyVersion = (2, 3, 0) - -synopsis = """DBUtils provides database related support classes and functions to Webware. There is plenty of useful reusable code here.""" - -WebKitConfig = { - 'examplePages': ['DBUtilsExample'] -} \ No newline at end of file diff --git a/DBUtils/SimplePooledDB.py b/DBUtils/SimplePooledDB.py deleted file mode 100644 index 32dbedf..0000000 --- a/DBUtils/SimplePooledDB.py +++ /dev/null @@ -1,222 +0,0 @@ -"""SimplePooledDB - a very simple DB-API 2 database connection pool. - -Implements a pool of threadsafe cached DB-API 2 connections -to a database which are transparently reused. - -This should result in a speedup for persistent applications -such as the "Webware for Python" AppServer. - -For more information on the DB-API 2, see: - https://www.python.org/dev/peps/pep-0249/ -For more information on Webware for Python, see: - https://cito.github.io/w4py/ - -Measures are taken to make the pool of connections threadsafe -regardless of whether the DB-API 2 module used is threadsafe -on the connection level (threadsafety > 1) or not. It must only -be threadsafe on the module level (threadsafety = 1). If the -DB-API 2 module is threadsafe, the connections will be shared -between threads (keep this in mind if you use transactions). - -Usage: - -The idea behind SimplePooledDB is that it's completely transparent. -After you have established your connection pool, stating the -DB-API 2 module to be used, the number of connections -to be cached in the pool and the connection parameters, e.g. - - import pgdb # import used DB-API 2 module - from DBUtils.SimplePooledDB import PooledDB - dbpool = PooledDB(pgdb, 5, host=..., database=..., user=..., ...) - -you can demand database connections from that pool, - - db = dbpool.connection() - -and use them just as if they were ordinary DB-API 2 connections. -It's really just a proxy class. - -db.close() will return the connection to the pool, it will not -actually close it. This is so your existing code works nicely. - -Ideas for improvement: - -* Do not create the maximum number of connections on startup -already, but only a certain number and the rest on demand. -* Detect and transparently reset "bad" connections. -* Connections should have some sort of maximum usage limit -after which they should be automatically closed and reopened. -* Prefer or enforce thread-affinity for the connections, -allowing for both sharable and non-sharable connections. - -Please note that these and other ideas have been already -implemented in in PooledDB, a more sophisticated version -of SimplePooledDB. You might also consider using PersistentDB -instead for thread-affine persistent database connections. -SimplePooledDB may still serve as a very simple reference -and example implementation for developers. - - -Copyright, credits and license: - -* Contributed as MiscUtils/DBPool for Webware for Python - by Dan Green, December 2000 -* Thread safety bug found by Tom Schwaller -* Fixes by Geoff Talvola (thread safety in _threadsafe_getConnection()) -* Clean up by Chuck Esterbrook -* Fix unthreadsafe functions which were leaking, Jay Love -* Eli Green's webware-discuss comments were lifted for additional docs -* Clean-up and detailed commenting, rename and move to DBUtils - by Christoph Zwerschke in September 2005 - -Licensed under the MIT license. - -""" - -__version__ = '1.1.1b1' - -class PooledDBError(Exception): - """General PooledDB error.""" - -class NotSupportedError(PooledDBError): - """DB-API module not supported by PooledDB.""" - - -class PooledDBConnection: - """A proxy class for pooled database connections. - - You don't normally deal with this class directly, - but use PooledDB to get new connections. - - """ - - def __init__(self, pool, con): - self._con = con - self._pool = pool - - def close(self): - """Close the pooled connection.""" - # Instead of actually closing the connection, - # return it to the pool so it can be reused. - if self._con is not None: - self._pool.returnConnection(self._con) - self._con = None - - def __getattr__(self, name): - # All other members are the same. - return getattr(self._con, name) - - def __del__(self): - self.close() - - -class PooledDB: - """A very simple database connection pool. - - After you have created the connection pool, - you can get connections using getConnection(). - - """ - - version = __version__ - - def __init__(self, dbapi, maxconnections, *args, **kwargs): - """Set up the database connection pool. - - dbapi: the DB-API 2 compliant module you want to use - maxconnections: the number of connections cached in the pool - args, kwargs: the parameters that shall be used to establish - the database connections using connect() - - """ - try: - threadsafety = dbapi.threadsafety - except Exception: - threadsafety = None - if threadsafety == 0: - raise NotSupportedError( - "Database module does not support any level of threading.") - elif threadsafety == 1: - # If there is no connection level safety, build - # the pool using the synchronized queue class - # that implements all the required locking semantics. - from Queue import Queue - self._queue = Queue(maxconnections) # create the queue - self.connection = self._unthreadsafe_get_connection - self.addConnection = self._unthreadsafe_add_connection - self.returnConnection = self._unthreadsafe_return_connection - elif threadsafety in (2, 3): - # If there is connection level safety, implement the - # pool with an ordinary list used as a circular buffer. - # We only need a minimum of locking in this case. - from threading import Lock - self._lock = Lock() # create a lock object to be used later - self._nextConnection = 0 # index of the next connection to be used - self._connections = [] # the list of connections - self.connection = self._threadsafe_get_connection - self.addConnection = self._threadsafe_add_connection - self.returnConnection = self._threadsafe_return_connection - else: - raise NotSupportedError( - "Database module threading support cannot be determined.") - # Establish all database connections (it would be better to - # only establish a part of them now, and the rest on demand). - for i in range(maxconnections): - self.addConnection(dbapi.connect(*args, **kwargs)) - - # The following functions are used with DB-API 2 modules - # that do not have connection level threadsafety, like PyGreSQL. - # However, the module must be threadsafe at the module level. - # Note: threadsafe/unthreadsafe refers to the DB-API 2 module, - # not to this class which should be threadsafe in any case. - - def _unthreadsafe_get_connection(self): - """Get a connection from the pool.""" - return PooledDBConnection(self, self._queue.get()) - - def _unthreadsafe_add_connection(self, con): - """Add a connection to the pool.""" - self._queue.put(con) - - def _unthreadsafe_return_connection(self, con): - """Return a connection to the pool. - - In this case, the connections need to be put - back into the queue after they have been used. - This is done automatically when the connection is closed - and should never be called explicitly outside of this module. - - """ - self._unthreadsafe_add_connection(con) - - # The following functions are used with DB-API 2 modules - # that are threadsafe at the connection level, like psycopg. - # Note: In this case, connections are shared between threads. - # This may lead to problems if you use transactions. - - def _threadsafe_get_connection(self): - """Get a connection from the pool.""" - self._lock.acquire() - try: - next = self._nextConnection - con = PooledDBConnection(self, self._connections[next]) - next += 1 - if next >= len(self._connections): - next = 0 - self._nextConnection = next - return con - finally: - self._lock.release() - - def _threadsafe_add_connection(self, con): - """Add a connection to the pool.""" - self._connections.append(con) - - def _threadsafe_return_connection(self, con): - """Return a connection to the pool. - - In this case, the connections always stay in the pool, - so there is no need to do anything here. - - """ - pass diff --git a/DBUtils/SimplePooledPg.py b/DBUtils/SimplePooledPg.py deleted file mode 100644 index bcb52c7..0000000 --- a/DBUtils/SimplePooledPg.py +++ /dev/null @@ -1,138 +0,0 @@ -"""SimplePooledPg - a very simple classic PyGreSQL connection pool. - -Implements a pool of threadsafe cached connections -to a PostgreSQL database which are transparently reused, -using the classic (not DB-API 2 compliant) PyGreSQL pg API. - -This should result in a speedup for persistent applications -such as the "Webware for Python" AppServer. - -For more information on PostgreSQL, see: - https://www.postgresql.org/ -For more information on PyGreSQL, see: - http://www.pygresql.org -For more information on Webware for Python, see: - https://cito.github.io/w4py/ - -Measures are taken to make the pool of connections threadsafe -regardless of the fact that the PyGreSQL pg module itself is -not threadsafe at the connection level. Connections will never be -shared between threads, so you can safely use transactions. - -Usage: - -The idea behind SimplePooledPg is that it's completely transparent. -After you have established your connection pool, stating the -number of connections to be cached in the pool and the -connection parameters, e.g. - - from DBUtils.SimplePooledPg import PooledPg - dbpool = PooledPg(5, host=..., database=..., user=..., ...) - -you can demand database connections from that pool, - - db = dbpool.connection() - -and use them just as if they were ordinary PyGreSQL pg API -connections. It's really just a proxy class. - -db.close() will return the connection to the pool, it will not -actually close it. This is so your existing code works nicely. - -Ideas for improvement: - -* Do not create the maximum number of connections on startup -already, but only a certain number and the rest on demand. -* Detect and transparently reset "bad" connections. The PyGreSQL -pg API provides a status attribute and a reset() method for that. -* Connections should have some sort of "maximum usage limit" -after which they should be automatically closed and reopened. -* Prefer or enforce thread affinity for the connections. - -Please note that these and other ideas have been already -implemented in in PooledPg, a more sophisticated version -of SimplePooledPg. You might also consider using PersistentPg -instead for thread-affine persistent PyGreSQL connections. -SimplePooledPg may still serve as a very simple reference -and example implementation for developers. - - -Copyright, credits and license: - -* Contributed as supplement for Webware for Python and PyGreSQL - by Christoph Zwerschke in September 2005 -* Based on the code of DBPool, contributed to Webware for Python - by Dan Green in December 2000 - -Licensed under the MIT license. - -""" - -__version__ = '1.1.1b1' - -from pg import DB as PgConnection - - -class PooledPgConnection: - """A proxy class for pooled PostgreSQL connections. - - You don't normally deal with this class directly, - but use PooledPg to get new connections. - - """ - - def __init__(self, pool, con): - self._con = con - self._pool = pool - - def close(self): - """Close the pooled connection.""" - # Instead of actually closing the connection, - # return it to the pool so it can be reused. - if self._con is not None: - self._pool.cache(self._con) - self._con = None - - def __getattr__(self, name): - # All other members are the same. - return getattr(self._con, name) - - def __del__(self): - self.close() - - -class PooledPg: - """A very simple PostgreSQL connection pool. - - After you have created the connection pool, - you can get connections using getConnection(). - - """ - - version = __version__ - - def __init__(self, maxconnections, *args, **kwargs): - """Set up the PostgreSQL connection pool. - - maxconnections: the number of connections cached in the pool - args, kwargs: the parameters that shall be used to establish - the PostgreSQL connections using pg.connect() - - """ - # Since there is no connection level safety, we - # build the pool using the synchronized queue class - # that implements all the required locking semantics. - from Queue import Queue - self._queue = Queue(maxconnections) - # Establish all database connections (it would be better to - # only establish a part of them now, and the rest on demand). - for i in range(maxconnections): - self.cache(PgConnection(*args, **kwargs)) - - def cache(self, con): - """Add or return a connection to the pool.""" - self._queue.put(con) - - def connection(self): - """Get a connection from the pool.""" - return PooledPgConnection(self, self._queue.get()) diff --git a/DBUtils/SteadyDB.py b/DBUtils/SteadyDB.py deleted file mode 100644 index 2bae058..0000000 --- a/DBUtils/SteadyDB.py +++ /dev/null @@ -1,672 +0,0 @@ -"""SteadyDB - hardened DB-API 2 connections. - -Implements steady connections to a database based on an -arbitrary DB-API 2 compliant database interface module. - -The connections are transparently reopened when they are -closed or the database connection has been lost or when -they are used more often than an optional usage limit. -Database cursors are transparently reopened as well when -the execution of a database operation cannot be performed -due to a lost connection. Only if the connection is lost -after the execution, when rows are already fetched from the -database, this will give an error and the cursor will not -be reopened automatically, because there is no reliable way -to recover the state of the cursor in such a situation. -Connections which have been marked as being in a transaction -with a begin() call will not be silently replaced either. - -A typical situation where database connections are lost -is when the database server or an intervening firewall is -shutdown and restarted for maintenance reasons. In such a -case, all database connections would become unusable, even -though the database service may be already available again. - -The "hardened" connections provided by this module will -make the database connections immediately available again. - -This approach results in a steady database connection that -can be used by PooledDB or PersistentDB to create pooled or -persistent connections to a database in a threaded environment -such as the application server of "Webware for Python." -Note, however, that the connections themselves may not be -thread-safe (depending on the used DB-API module). - -For the Python DB-API 2 specification, see: - https://www.python.org/dev/peps/pep-0249/ -For information on Webware for Python, see: - https://cito.github.io/w4py/ - -Usage: - -You can use the connection constructor connect() in the same -way as you would use the connection constructor of a DB-API 2 -module if you specify the DB-API 2 module to be used as the -first parameter, or alternatively you can specify an arbitrary -constructor function returning new DB-API 2 compliant connection -objects as the first parameter. Passing just a function allows -implementing failover mechanisms and load balancing strategies. - -You may also specify a usage limit as the second parameter -(set it to None if you prefer unlimited usage), an optional -list of commands that may serve to prepare the session as a -third parameter, the exception classes for which the failover -mechanism shall be applied, and you can specify whether is is -allowed to close the connection (by default this is true). -When the connection to the database is lost or has been used -too often, it will be transparently reset in most situations, -without further notice. - - import pgdb # import used DB-API 2 module - from DBUtils.SteadyDB import connect - db = connect(pgdb, 10000, ["set datestyle to german"], - host=..., database=..., user=..., ...) - ... - cursor = db.cursor() - ... - cursor.execute('select ...') - result = cursor.fetchall() - ... - cursor.close() - ... - db.close() - - -Ideas for improvement: - -* Alternatively to the maximum number of uses, - implement a maximum time to live for connections. -* Optionally log usage and loss of connection. - - -Copyright, credits and license: - -* Contributed as supplement for Webware for Python and PyGreSQL - by Christoph Zwerschke in September 2005 -* Allowing creator functions as first parameter as in SQLAlchemy - suggested by Ezio Vernacotola in December 2006 - -Licensed under the MIT license. - -""" - -__version__ = '1.1.1b1' - -import sys - - -class SteadyDBError(Exception): - """General SteadyDB error.""" - -class InvalidCursor(SteadyDBError): - """Database cursor is invalid.""" - - -def connect(creator, maxusage=None, setsession=None, - failures=None, ping=1, closeable=True, *args, **kwargs): - """A tough version of the connection constructor of a DB-API 2 module. - - creator: either an arbitrary function returning new DB-API 2 compliant - connection objects or a DB-API 2 compliant database module - maxusage: maximum usage limit for the underlying DB-API 2 connection - (number of database operations, 0 or None means unlimited usage) - callproc(), execute() and executemany() count as one operation. - When the limit is reached, the connection is automatically reset. - setsession: an optional list of SQL commands that may serve to prepare - the session, e.g. ["set datestyle to german", "set time zone mez"] - failures: an optional exception class or a tuple of exception classes - for which the failover mechanism shall be applied, if the default - (OperationalError, InternalError) is not adequate - ping: determines when the connection should be checked with ping() - (0 = None = never, 1 = default = when _ping_check() is called, - 2 = whenever a cursor is created, 4 = when a query is executed, - 7 = always, and all other bit combinations of these values) - closeable: if this is set to false, then closing the connection will - be silently ignored, but by default the connection can be closed - args, kwargs: the parameters that shall be passed to the creator - function or the connection constructor of the DB-API 2 module - - """ - return SteadyDBConnection(creator, maxusage, setsession, - failures, ping, closeable, *args, **kwargs) - - -class SteadyDBConnection: - """A "tough" version of DB-API 2 connections.""" - - version = __version__ - - def __init__(self, creator, maxusage=None, setsession=None, - failures=None, ping=1, closeable=True, *args, **kwargs): - """Create a "tough" DB-API 2 connection.""" - # basic initialization to make finalizer work - self._con = None - self._closed = True - # proper initialization of the connection - try: - self._creator = creator.connect - self._dbapi = creator - except AttributeError: - # try finding the DB-API 2 module via the connection creator - self._creator = creator - try: - self._dbapi = creator.dbapi - except AttributeError: - try: - self._dbapi = sys.modules[creator.__module__] - if self._dbapi.connect != creator: - raise AttributeError - except (AttributeError, KeyError): - self._dbapi = None - try: - self._threadsafety = creator.threadsafety - except AttributeError: - try: - self._threadsafety = self._dbapi.threadsafety - except AttributeError: - self._threadsafety = None - if not callable(self._creator): - raise TypeError("%r is not a connection provider." % (creator,)) - if maxusage is None: - maxusage = 0 - if not isinstance(maxusage, (int, long)): - raise TypeError("'maxusage' must be an integer value.") - self._maxusage = maxusage - self._setsession_sql = setsession - if failures is not None and not isinstance( - failures, tuple) and not issubclass(failures, Exception): - raise TypeError("'failures' must be a tuple of exceptions.") - self._failures = failures - self._ping = isinstance(ping, int) and ping or 0 - self._closeable = closeable - self._args, self._kwargs = args, kwargs - self._store(self._create()) - - def _create(self): - """Create a new connection using the creator function.""" - con = self._creator(*self._args, **self._kwargs) - try: - try: - if self._dbapi.connect != self._creator: - raise AttributeError - except AttributeError: - # try finding the DB-API 2 module via the connection itself - try: - mod = con.__module__ - except AttributeError: - mod = None - while mod: - try: - self._dbapi = sys.modules[mod] - if not callable(self._dbapi.connect): - raise AttributeError - except (AttributeError, KeyError): - pass - else: - break - i = mod.rfind('.') - if i < 0: - mod = None - else: - mod = mod[:i] - else: - try: - mod = con.OperationalError.__module__ - except AttributeError: - mod = None - while mod: - try: - self._dbapi = sys.modules[mod] - if not callable(self._dbapi.connect): - raise AttributeError - except (AttributeError, KeyError): - pass - else: - break - i = mod.rfind('.') - if i < 0: - mod = None - else: - mod = mod[:i] - else: - self._dbapi = None - if self._threadsafety is None: - try: - self._threadsafety = self._dbapi.threadsafety - except AttributeError: - try: - self._threadsafety = con.threadsafety - except AttributeError: - pass - if self._failures is None: - try: - self._failures = (self._dbapi.OperationalError, - self._dbapi.InternalError) - except AttributeError: - try: - self._failures = (self._creator.OperationalError, - self._creator.InternalError) - except AttributeError: - try: - self._failures = (con.OperationalError, - con.InternalError) - except AttributeError: - raise AttributeError( - "Could not determine failure exceptions" - " (please set failures or creator.dbapi).") - if isinstance(self._failures, tuple): - self._failure = self._failures[0] - else: - self._failure = self._failures - self._setsession(con) - except Exception, error: - # the database module could not be determined - # or the session could not be prepared - try: # close the connection first - con.close() - except Exception: - pass - raise error # reraise the original error again - return con - - def _setsession(self, con=None): - """Execute the SQL commands for session preparation.""" - if con is None: - con = self._con - if self._setsession_sql: - cursor = con.cursor() - for sql in self._setsession_sql: - cursor.execute(sql) - cursor.close() - - def _store(self, con): - """Store a database connection for subsequent use.""" - self._con = con - self._transaction = False - self._closed = False - self._usage = 0 - - def _close(self): - """Close the tough connection. - - You can always close a tough connection with this method - and it will not complain if you close it more than once. - - """ - if not self._closed: - try: - self._con.close() - except Exception: - pass - self._transaction = False - self._closed = True - - def _reset(self, force=False): - """Reset a tough connection. - - Rollback if forced or the connection was in a transaction. - - """ - if not self._closed and (force or self._transaction): - try: - self.rollback() - except Exception: - pass - - def _ping_check(self, ping=1, reconnect=True): - """Check whether the connection is still alive using ping(). - - If the the underlying connection is not active and the ping - parameter is set accordingly, the connection will be recreated - unless the connection is currently inside a transaction. - - """ - if ping & self._ping: - try: # if possible, ping the connection - alive = self._con.ping() - except (AttributeError, IndexError, TypeError, ValueError): - self._ping = 0 # ping() is not available - alive = None - reconnect = False - except Exception: - alive = False - else: - if alive is None: - alive = True - if alive: - reconnect = False - if reconnect and not self._transaction: - try: # try to reopen the connection - con = self._create() - except Exception: - pass - else: - self._close() - self._store(con) - alive = True - return alive - - def dbapi(self): - """Return the underlying DB-API 2 module of the connection.""" - if self._dbapi is None: - raise AttributeError("Could not determine DB-API 2 module" - " (please set creator.dbapi).") - return self._dbapi - - def threadsafety(self): - """Return the thread safety level of the connection.""" - if self._threadsafety is None: - if self._dbapi is None: - raise AttributeError("Could not determine threadsafety" - " (please set creator.dbapi or creator.threadsafety).") - return 0 - return self._threadsafety - - def close(self): - """Close the tough connection. - - You are allowed to close a tough connection by default - and it will not complain if you close it more than once. - - You can disallow closing connections by setting - the closeable parameter to something false. In this case, - closing tough connections will be silently ignored. - - """ - if self._closeable: - self._close() - elif self._transaction: - self._reset() - - def begin(self, *args, **kwargs): - """Indicate the beginning of a transaction. - - During a transaction, connections won't be transparently - replaced, and all errors will be raised to the application. - - If the underlying driver supports this method, it will be called - with the given parameters (e.g. for distributed transactions). - - """ - self._transaction = True - try: - begin = self._con.begin - except AttributeError: - pass - else: - begin(*args, **kwargs) - - def commit(self): - """Commit any pending transaction.""" - self._transaction = False - try: - self._con.commit() - except self._failures, error: # cannot commit - try: # try to reopen the connection - con = self._create() - except Exception: - pass - else: - self._close() - self._store(con) - raise error # reraise the original error - - def rollback(self): - """Rollback pending transaction.""" - self._transaction = False - try: - self._con.rollback() - except self._failures, error: # cannot rollback - try: # try to reopen the connection - con = self._create() - except Exception: - pass - else: - self._close() - self._store(con) - raise error # reraise the original error - - def cancel(self): - """Cancel a long-running transaction. - - If the underlying driver supports this method, it will be called. - - """ - self._transaction = False - try: - cancel = self._con.cancel - except AttributeError: - pass - else: - cancel() - - def ping(self, *args, **kwargs): - """Ping connection.""" - return self._con.ping(*args, **kwargs) - - def _cursor(self, *args, **kwargs): - """A "tough" version of the method cursor().""" - # The args and kwargs are not part of the standard, - # but some database modules seem to use these. - transaction = self._transaction - if not transaction: - self._ping_check(2) - try: - if self._maxusage: - if self._usage >= self._maxusage: - # the connection was used too often - raise self._failure - cursor = self._con.cursor(*args, **kwargs) # try to get a cursor - except self._failures, error: # error in getting cursor - try: # try to reopen the connection - con = self._create() - except Exception: - pass - else: - try: # and try one more time to get a cursor - cursor = con.cursor(*args, **kwargs) - except Exception: - pass - else: - self._close() - self._store(con) - if transaction: - raise error # reraise the original error again - return cursor - try: - con.close() - except Exception: - pass - if transaction: - self._transaction = False - raise error # reraise the original error again - return cursor - - def cursor(self, *args, **kwargs): - """Return a new Cursor Object using the connection.""" - return SteadyDBCursor(self, *args, **kwargs) - - def __del__(self): - """Delete the steady connection.""" - try: - self._close() # make sure the connection is closed - except Exception: - pass - - -class SteadyDBCursor: - """A "tough" version of DB-API 2 cursors.""" - - def __init__(self, con, *args, **kwargs): - """Create a "tough" DB-API 2 cursor.""" - # basic initialization to make finalizer work - self._cursor = None - self._closed = True - # proper initialization of the cursor - self._con = con - self._args, self._kwargs = args, kwargs - self._clearsizes() - try: - self._cursor = con._cursor(*args, **kwargs) - except AttributeError: - raise TypeError("%r is not a SteadyDBConnection." % (con,)) - self._closed = False - - def setinputsizes(self, sizes): - """Store input sizes in case cursor needs to be reopened.""" - self._inputsizes = sizes - - def setoutputsize(self, size, column=None): - """Store output sizes in case cursor needs to be reopened.""" - self._outputsizes[column] = size - - def _clearsizes(self): - """Clear stored input and output sizes.""" - self._inputsizes = [] - self._outputsizes = {} - - def _setsizes(self, cursor=None): - """Set stored input and output sizes for cursor execution.""" - if cursor is None: - cursor = self._cursor - if self._inputsizes: - cursor.setinputsizes(self._inputsizes) - for column, size in self._outputsizes.iteritems(): - if column is None: - cursor.setoutputsize(size) - else: - cursor.setoutputsize(size, column) - - def close(self): - """Close the tough cursor. - - It will not complain if you close it more than once. - - """ - if not self._closed: - try: - self._cursor.close() - except Exception: - pass - self._closed = True - - def _get_tough_method(self, name): - """Return a "tough" version of the given cursor method.""" - def tough_method(*args, **kwargs): - execute = name.startswith('execute') - con = self._con - transaction = con._transaction - if not transaction: - con._ping_check(4) - try: - if con._maxusage: - if con._usage >= con._maxusage: - # the connection was used too often - raise con._failure - if execute: - self._setsizes() - method = getattr(self._cursor, name) - result = method(*args, **kwargs) # try to execute - if execute: - self._clearsizes() - except con._failures, error: # execution error - if not transaction: - try: - cursor2 = con._cursor( - *self._args, **self._kwargs) # open new cursor - except Exception: - pass - else: - try: # and try one more time to execute - if execute: - self._setsizes(cursor2) - method = getattr(cursor2, name) - result = method(*args, **kwargs) - if execute: - self._clearsizes() - except Exception: - pass - else: - self.close() - self._cursor = cursor2 - con._usage += 1 - return result - try: - cursor2.close() - except Exception: - pass - try: # try to reopen the connection - con2 = con._create() - except Exception: - pass - else: - try: - cursor2 = con2.cursor( - *self._args, **self._kwargs) # open new cursor - except Exception: - pass - else: - if transaction: - self.close() - con._close() - con._store(con2) - self._cursor = cursor2 - raise error # raise the original error again - error2 = None - try: # try one more time to execute - if execute: - self._setsizes(cursor2) - method2 = getattr(cursor2, name) - result = method2(*args, **kwargs) - if execute: - self._clearsizes() - except error.__class__: # same execution error - use2 = False - error2 = error - except Exception, error: # other execution errors - use2 = True - error2 = error - else: - use2 = True - if use2: - self.close() - con._close() - con._store(con2) - self._cursor = cursor2 - con._usage += 1 - if error2: - raise error2 # raise the other error - return result - try: - cursor2.close() - except Exception: - pass - try: - con2.close() - except Exception: - pass - if transaction: - self._transaction = False - raise error # reraise the original error again - else: - con._usage += 1 - return result - return tough_method - - def __getattr__(self, name): - """Inherit methods and attributes of underlying cursor.""" - if self._cursor: - if name.startswith('execute') or name.startswith('call'): - # make execution methods "tough" - return self._get_tough_method(name) - else: - return getattr(self._cursor, name) - else: - raise InvalidCursor - - def __del__(self): - """Delete the steady cursor.""" - try: - self.close() # make sure the cursor is closed - except Exception: - pass diff --git a/DBUtils/SteadyPg.py b/DBUtils/SteadyPg.py deleted file mode 100644 index 1c8705d..0000000 --- a/DBUtils/SteadyPg.py +++ /dev/null @@ -1,318 +0,0 @@ -"""SteadyPg - hardened classic PyGreSQL connections. - -Implements steady connections to a PostgreSQL database -using the classic (not DB-API 2 compliant) PyGreSQL API. - -The connections are transparently reopened when they are -closed or the database connection has been lost or when -they are used more often than an optional usage limit. -Only connections which have been marked as being in a database -transaction with a begin() call will not be silently replaced. - -A typical situation where database connections are lost -is when the database server or an intervening firewall is -shutdown and restarted for maintenance reasons. In such a -case, all database connections would become unusable, even -though the database service may be already available again. - -The "hardened" connections provided by this module will -make the database connections immediately available again. - -This results in a steady PostgreSQL connection that can be used -by PooledPg or PersistentPg to create pooled or persistent -connections to a PostgreSQL database in a threaded environment -such as the application server of "Webware for Python." -Note, however, that the connections themselves are not thread-safe. - -For more information on PostgreSQL, see: - https://www.postgresql.org/ -For more information on PyGreSQL, see: - http://www.pygresql.org -For more information on Webware for Python, see: - https://cito.github.io/w4py/ - - -Usage: - -You can use the class SteadyPgConnection in the same way as you -would use the class DB from the classic PyGreSQL API module db. -The only difference is that you may specify a usage limit as the -first parameter when you open a connection (set it to None -if you prefer unlimited usage), and an optional list of commands -that may serve to prepare the session as the second parameter, -and you can specify whether is is allowed to close the connection -(by default this is true). When the connection to the PostgreSQL -database is lost or has been used too often, it will be automatically -reset, without further notice. - - from DBUtils.SteadyPg import SteadyPgConnection - db = SteadyPgConnection(10000, ["set datestyle to german"], - host=..., dbname=..., user=..., ...) - ... - result = db.query('...') - ... - db.close() - - -Ideas for improvement: - -* Alternatively to the maximum number of uses, - implement a maximum time to live for connections. -* Optionally log usage and loss of connection. - - -Copyright, credits and license: - -* Contributed as supplement for Webware for Python and PyGreSQL - by Christoph Zwerschke in September 2005 - -Licensed under the MIT license. - -""" - -__version__ = '1.1.1b1' - -from pg import DB as PgConnection - - -class SteadyPgError(Exception): - """General SteadyPg error.""" - -class InvalidConnection(SteadyPgError): - """Database connection is invalid.""" - - -class SteadyPgConnection: - """Class representing steady connections to a PostgreSQL database. - - Underlying the connection is a classic PyGreSQL pg API database - connection which is reset if the connection is lost or used too often. - Thus the resulting connection is steadier ("tough and self-healing"). - - If you want the connection to be persistent in a threaded environment, - then you should not deal with this class directly, but use either the - PooledPg module or the PersistentPg module to get the connections. - - """ - - version = __version__ - - def __init__(self, maxusage=None, setsession=None, closeable=True, - *args, **kwargs): - """Create a "tough" PostgreSQL connection. - - maxusage: maximum usage limit for the underlying PyGreSQL connection - (number of uses, 0 or None means unlimited usage) - When this limit is reached, the connection is automatically reset. - setsession: optional list of SQL commands that may serve to prepare - the session, e.g. ["set datestyle to ...", "set time zone ..."] - closeable: if this is set to false, then closing the connection will - be silently ignored, but by default the connection can be closed - args, kwargs: the parameters that shall be used to establish - the PostgreSQL connections with PyGreSQL using pg.DB() - - """ - # basic initialization to make finalizer work - self._con = None - self._closed = True - # proper initialization of the connection - if maxusage is None: - maxusage = 0 - if not isinstance(maxusage, (int, long)): - raise TypeError("'maxusage' must be an integer value.") - self._maxusage = maxusage - self._setsession_sql = setsession - self._closeable = closeable - self._con = PgConnection(*args, **kwargs) - self._transaction = False - self._closed = False - self._setsession() - self._usage = 0 - - def _setsession(self): - """Execute the SQL commands for session preparation.""" - if self._setsession_sql: - for sql in self._setsession_sql: - self._con.query(sql) - - def _close(self): - """Close the tough connection. - - You can always close a tough connection with this method - and it will not complain if you close it more than once. - - """ - if not self._closed: - try: - self._con.close() - except Exception: - pass - self._transaction = False - self._closed = True - - def close(self): - """Close the tough connection. - - You are allowed to close a tough connection by default - and it will not complain if you close it more than once. - - You can disallow closing connections by setting - the closeable parameter to something false. In this case, - closing tough connections will be silently ignored. - - """ - if self._closeable: - self._close() - elif self._transaction: - self.reset() - - def reopen(self): - """Reopen the tough connection. - - It will not complain if the connection cannot be reopened. - - """ - try: - self._con.reopen() - except Exception: - if self._transcation: - self._transaction = False - try: - self._con.query('rollback') - except Exception: - pass - else: - self._transaction = False - self._closed = False - self._setsession() - self._usage = 0 - - def reset(self): - """Reset the tough connection. - - If a reset is not possible, tries to reopen the connection. - It will not complain if the connection is already closed. - - """ - try: - self._con.reset() - self._transaction = False - self._setsession() - self._usage = 0 - except Exception: - try: - self.reopen() - except Exception: - try: - self.rollback() - except Exception: - pass - - def begin(self, sql=None): - """Begin a transaction.""" - self._transaction = True - try: - begin = self._con.begin - except AttributeError: - return self._con.query(sql or 'begin') - else: - # use existing method if available - if sql: - return begin(sql=sql) - else: - return begin() - - def end(self, sql=None): - """Commit the current transaction.""" - self._transaction = False - try: - end = self._con.end - except AttributeError: - return self._con.query(sql or 'end') - else: - if sql: - return end(sql=sql) - else: - return end() - - def commit(self, sql=None): - """Commit the current transaction.""" - self._transaction = False - try: - commit = self._con.commit - except AttributeError: - return self._con.query(sql or 'commit') - else: - if sql: - return commit(sql=sql) - else: - return commit() - - def rollback(self, sql=None): - """Rollback the current transaction.""" - self._transaction = False - try: - rollback = self._con.rollback - except AttributeError: - return self._con.query(sql or 'rollback') - else: - if sql: - return rollback(sql=sql) - else: - return rollback() - - def _get_tough_method(self, method): - """Return a "tough" version of a connection class method. - - The tough version checks whether the connection is bad (lost) - and automatically and transparently tries to reset the connection - if this is the case (for instance, the database has been restarted). - - """ - def tough_method(*args, **kwargs): - transaction = self._transaction - if not transaction: - try: # check whether connection status is bad - if not self._con.db.status: - raise AttributeError - if self._maxusage: # or connection used too often - if self._usage >= self._maxusage: - raise AttributeError - except Exception: - self.reset() # then reset the connection - try: - result = method(*args, **kwargs) # try connection method - except Exception: # error in query - if transaction: # inside a transaction - self._transaction = False - raise # propagate the error - elif self._con.db.status: # if it was not a connection problem - raise # then propagate the error - else: # otherwise - self.reset() # reset the connection - result = method(*args, **kwargs) # and try one more time - self._usage += 1 - return result - return tough_method - - def __getattr__(self, name): - """Inherit the members of the standard connection class. - - Some methods are made "tougher" than in the standard version. - - """ - if self._con: - attr = getattr(self._con, name) - if (name in ('query', 'get', 'insert', 'update', 'delete') - or name.startswith('get_')): - attr = self._get_tough_method(attr) - return attr - else: - raise InvalidConnection - - def __del__(self): - """Delete the steady connection.""" - try: - self._close() # make sure the connection is closed - except Exception: - pass diff --git a/DBUtils/Tests/TestPersistentDB.py b/DBUtils/Tests/TestPersistentDB.py deleted file mode 100644 index 6bd1f8a..0000000 --- a/DBUtils/Tests/TestPersistentDB.py +++ /dev/null @@ -1,292 +0,0 @@ -"""Test the PersistentDB module. - -Note: -We don't test performance here, so the test does not predicate -whether PersistentDB actually will help in improving performance or not. -We also assume that the underlying SteadyDB connections are tested. - -Copyright and credit info: - -* This test was contributed by Christoph Zwerschke - -""" - -__version__ = '1.1.1b1' - -import sys -import unittest - -sys.path.insert(1, '../..') -# The TestSteadyDB module serves as a mock object for the DB-API 2 module: -from DBUtils import ThreadingLocal -from DBUtils.Tests import TestSteadyDB as dbapi -from DBUtils.PersistentDB import PersistentDB - - -class TestPersistentDB(unittest.TestCase): - - def setUp(self): - dbapi.threadsafety = 1 - - def test0_CheckVersion(self): - from DBUtils import __version__ as DBUtilsVersion - self.assertEqual(DBUtilsVersion, __version__) - from DBUtils.PersistentDB import __version__ as PersistentDBVersion - self.assertEqual(PersistentDBVersion, __version__) - self.assertEqual(PersistentDB.version, __version__) - - def test1_NoThreadsafety(self): - from DBUtils.PersistentDB import NotSupportedError - for dbapi.threadsafety in (None, 0): - self.assertRaises(NotSupportedError, PersistentDB, dbapi) - - def test2_Close(self): - for closeable in (False, True): - persist = PersistentDB(dbapi, closeable=closeable) - db = persist.connection() - self.assert_(db._con.valid) - db.close() - self.assert_(closeable ^ db._con.valid) - db.close() - self.assert_(closeable ^ db._con.valid) - db._close() - self.assert_(not db._con.valid) - db._close() - self.assert_(not db._con.valid) - - def test3_Connection(self): - persist = PersistentDB(dbapi) - db = persist.connection() - db_con = db._con - self.assert_(db_con.database is None) - self.assert_(db_con.user is None) - db2 = persist.connection() - self.assertEqual(db, db2) - db3 = persist.dedicated_connection() - self.assertEqual(db, db3) - db3.close() - db2.close() - db.close() - - def test4_Threads(self): - numThreads = 3 - persist = PersistentDB(dbapi, closeable=True) - from Queue import Queue, Empty - queryQueue, resultQueue = [], [] - for i in range(numThreads): - queryQueue.append(Queue(1)) - resultQueue.append(Queue(1)) - def runQueries(i): - this_db = persist.connection() - while 1: - try: - try: - q = queryQueue[i].get(1, 1) - except TypeError: - q = queryQueue[i].get(1) - except Empty: - q = None - if not q: - break - db = persist.connection() - if db != this_db: - r = 'error - not persistent' - else: - if q == 'ping': - r = 'ok - thread alive' - elif q == 'close': - db.close() - r = 'ok - connection closed' - else: - cursor = db.cursor() - cursor.execute(q) - r = cursor.fetchone() - cursor.close() - r = '%d(%d): %s' % (i, db._usage, r) - try: - resultQueue[i].put(r, 1, 1) - except TypeError: - resultQueue[i].put(r, 1) - db.close() - from threading import Thread - threads = [] - for i in range(numThreads): - thread = Thread(target=runQueries, args=(i,)) - threads.append(thread) - thread.start() - for i in range(numThreads): - try: - queryQueue[i].put('ping', 1, 1) - except TypeError: - queryQueue[i].put('ping', 1) - for i in range(numThreads): - try: - r = resultQueue[i].get(1, 1) - except TypeError: - r = resultQueue[i].get(1) - self.assertEqual(r, '%d(0): ok - thread alive' % i) - self.assert_(threads[i].isAlive()) - for i in range(numThreads): - for j in range(i + 1): - try: - queryQueue[i].put('select test%d' % j, 1, 1) - r = resultQueue[i].get(1, 1) - except TypeError: - queryQueue[i].put('select test%d' % j, 1) - r = resultQueue[i].get(1) - self.assertEqual(r, '%d(%d): test%d' % (i, j + 1, j)) - try: - queryQueue[1].put('select test4', 1, 1) - except TypeError: - queryQueue[1].put('select test4', 1) - try: - r = resultQueue[1].get(1, 1) - except TypeError: - r = resultQueue[1].get(1) - self.assertEqual(r, '1(3): test4') - try: - queryQueue[1].put('close', 1, 1) - r = resultQueue[1].get(1, 1) - except TypeError: - queryQueue[1].put('close', 1) - r = resultQueue[1].get(1) - self.assertEqual(r, '1(3): ok - connection closed') - for j in range(2): - try: - queryQueue[1].put('select test%d' % j, 1, 1) - r = resultQueue[1].get(1, 1) - except TypeError: - queryQueue[1].put('select test%d' % j, 1) - r = resultQueue[1].get(1) - self.assertEqual(r, '1(%d): test%d' % (j + 1, j)) - for i in range(numThreads): - self.assert_(threads[i].isAlive()) - try: - queryQueue[i].put('ping', 1, 1) - except TypeError: - queryQueue[i].put('ping', 1) - for i in range(numThreads): - try: - r = resultQueue[i].get(1, 1) - except TypeError: - r = resultQueue[i].get(1) - self.assertEqual(r, '%d(%d): ok - thread alive' % (i, i + 1)) - self.assert_(threads[i].isAlive()) - for i in range(numThreads): - try: - queryQueue[i].put(None, 1, 1) - except TypeError: - queryQueue[i].put(None, 1) - - def test5_MaxUsage(self): - persist = PersistentDB(dbapi, 20) - db = persist.connection() - self.assertEqual(db._maxusage, 20) - for i in range(100): - cursor = db.cursor() - cursor.execute('select test%d' % i) - r = cursor.fetchone() - cursor.close() - self.assertEqual(r, 'test%d' % i) - self.assert_(db._con.valid) - j = i % 20 + 1 - self.assertEqual(db._usage, j) - self.assertEqual(db._con.num_uses, j) - self.assertEqual(db._con.num_queries, j) - - def test6_SetSession(self): - persist = PersistentDB(dbapi, 3, ('set datestyle',)) - db = persist.connection() - self.assertEqual(db._maxusage, 3) - self.assertEqual(db._setsession_sql, ('set datestyle',)) - self.assertEqual(db._con.session, ['datestyle']) - cursor = db.cursor() - cursor.execute('set test') - cursor.fetchone() - cursor.close() - for i in range(3): - self.assertEqual(db._con.session, ['datestyle', 'test']) - cursor = db.cursor() - cursor.execute('select test') - cursor.fetchone() - cursor.close() - self.assertEqual(db._con.session, ['datestyle']) - - def test7_ThreadLocal(self): - persist = PersistentDB(dbapi) - self.assert_(isinstance(persist.thread, ThreadingLocal.local)) - class threadlocal: - pass - persist = PersistentDB(dbapi, threadlocal=threadlocal) - self.assert_(isinstance(persist.thread, threadlocal)) - - def test8_PingCheck(self): - Connection = dbapi.Connection - Connection.has_ping = True - Connection.num_pings = 0 - persist = PersistentDB(dbapi, 0, None, None, 0, True) - db = persist.connection() - self.assert_(db._con.valid) - self.assertEqual(Connection.num_pings, 0) - db.close() - db = persist.connection() - self.assert_(not db._con.valid) - self.assertEqual(Connection.num_pings, 0) - persist = PersistentDB(dbapi, 0, None, None, 1, True) - db = persist.connection() - self.assert_(db._con.valid) - self.assertEqual(Connection.num_pings, 1) - db.close() - db = persist.connection() - self.assert_(db._con.valid) - self.assertEqual(Connection.num_pings, 2) - persist = PersistentDB(dbapi, 0, None, None, 2, True) - db = persist.connection() - self.assert_(db._con.valid) - self.assertEqual(Connection.num_pings, 2) - db.close() - db = persist.connection() - self.assert_(not db._con.valid) - self.assertEqual(Connection.num_pings, 2) - cursor = db.cursor() - self.assert_(db._con.valid) - self.assertEqual(Connection.num_pings, 3) - cursor.execute('select test') - self.assert_(db._con.valid) - self.assertEqual(Connection.num_pings, 3) - persist = PersistentDB(dbapi, 0, None, None, 4, True) - db = persist.connection() - self.assert_(db._con.valid) - self.assertEqual(Connection.num_pings, 3) - db.close() - db = persist.connection() - self.assert_(not db._con.valid) - self.assertEqual(Connection.num_pings, 3) - cursor = db.cursor() - db._con.close() - self.assert_(not db._con.valid) - self.assertEqual(Connection.num_pings, 3) - cursor.execute('select test') - self.assert_(db._con.valid) - self.assertEqual(Connection.num_pings, 4) - Connection.has_ping = False - Connection.num_pings = 0 - - def test9_FailedTransaction(self): - persist = PersistentDB(dbapi) - db = persist.connection() - cursor = db.cursor() - db._con.close() - cursor.execute('select test') - db.begin() - db._con.close() - self.assertRaises(dbapi.InternalError, cursor.execute, 'select test') - cursor.execute('select test') - db.begin() - db.cancel() - db._con.close() - cursor.execute('select test') - - -if __name__ == '__main__': - unittest.main() diff --git a/DBUtils/Tests/TestPersistentPg.py b/DBUtils/Tests/TestPersistentPg.py deleted file mode 100644 index c312dc8..0000000 --- a/DBUtils/Tests/TestPersistentPg.py +++ /dev/null @@ -1,196 +0,0 @@ -"""Test the PersistentPg module. - -Note: -We don't test performance here, so the test does not predicate -whether PersistentPg actually will help in improving performance or not. -We also assume that the underlying SteadyPg connections are tested. - -Copyright and credit info: - -* This test was contributed by Christoph Zwerschke - -""" - -__version__ = '1.1.1b1' - -import sys -import unittest - -sys.path.insert(1, '../..') -# The TestSteadyPg module serves as a mock object for the pg API module: -from DBUtils.Tests import TestSteadyPg as pg -from DBUtils.PersistentPg import PersistentPg - - -class TestPersistentPg(unittest.TestCase): - - def test0_CheckVersion(self): - from DBUtils import __version__ as DBUtilsVersion - self.assertEqual(DBUtilsVersion, __version__) - from DBUtils.PersistentPg import __version__ as PersistentPgVersion - self.assertEqual(PersistentPgVersion, __version__) - self.assertEqual(PersistentPg.version, __version__) - - def test1_Close(self): - for closeable in (False, True): - persist = PersistentPg(closeable=closeable) - db = persist.connection() - self.assert_(db._con.db and db._con.valid) - db.close() - self.assert_(closeable ^ - (db._con.db is not None and db._con.valid)) - db.close() - self.assert_(closeable ^ - (db._con.db is not None and db._con.valid)) - db._close() - self.assert_(not db._con.db or not db._con.valid) - db._close() - self.assert_(not db._con.db or not db._con.valid) - - def test2_Threads(self): - numThreads = 3 - persist = PersistentPg() - from Queue import Queue, Empty - queryQueue, resultQueue = [], [] - for i in range(numThreads): - queryQueue.append(Queue(1)) - resultQueue.append(Queue(1)) - def runQueries(i): - this_db = persist.connection().db - while 1: - try: - try: - q = queryQueue[i].get(1, 1) - except TypeError: - q = queryQueue[i].get(1) - except Empty: - q = None - if not q: - break - db = persist.connection() - if db.db != this_db: - r = 'error - not persistent' - else: - if q == 'ping': - r = 'ok - thread alive' - elif q == 'close': - db.db.close() - r = 'ok - connection closed' - else: - r = db.query(q) - r = '%d(%d): %s' % (i, db._usage, r) - try: - resultQueue[i].put(r, 1, 1) - except TypeError: - resultQueue[i].put(r, 1) - db.close() - from threading import Thread - threads = [] - for i in range(numThreads): - thread = Thread(target=runQueries, args=(i,)) - threads.append(thread) - thread.start() - for i in range(numThreads): - try: - queryQueue[i].put('ping', 1, 1) - except TypeError: - queryQueue[i].put('ping', 1) - for i in range(numThreads): - try: - r = resultQueue[i].get(1, 1) - except TypeError: - r = resultQueue[i].get(1) - self.assertEqual(r, '%d(0): ok - thread alive' % i) - self.assert_(threads[i].isAlive()) - for i in range(numThreads): - for j in range(i + 1): - try: - queryQueue[i].put('select test%d' % j, 1, 1) - r = resultQueue[i].get(1, 1) - except TypeError: - queryQueue[i].put('select test%d' % j, 1) - r = resultQueue[i].get(1) - self.assertEqual(r, '%d(%d): test%d' % (i, j + 1, j)) - try: - queryQueue[1].put('select test4', 1, 1) - r = resultQueue[1].get(1, 1) - except TypeError: - queryQueue[1].put('select test4', 1) - r = resultQueue[1].get(1) - self.assertEqual(r, '1(3): test4') - try: - queryQueue[1].put('close', 1, 1) - r = resultQueue[1].get(1, 1) - except TypeError: - queryQueue[1].put('close', 1) - r = resultQueue[1].get(1) - self.assertEqual(r, '1(3): ok - connection closed') - for j in range(2): - try: - queryQueue[1].put('select test%d' % j, 1, 1) - r = resultQueue[1].get(1, 1) - except TypeError: - queryQueue[1].put('select test%d' % j, 1) - r = resultQueue[1].get(1) - self.assertEqual(r, '1(%d): test%d' % (j + 1, j)) - for i in range(numThreads): - self.assert_(threads[i].isAlive()) - try: - queryQueue[i].put('ping', 1, 1) - except TypeError: - queryQueue[i].put('ping', 1) - for i in range(numThreads): - try: - r = resultQueue[i].get(1, 1) - except TypeError: - r = resultQueue[i].get(1) - self.assertEqual(r, '%d(%d): ok - thread alive' % (i, i + 1)) - self.assert_(threads[i].isAlive()) - for i in range(numThreads): - try: - queryQueue[i].put(None, 1, 1) - except TypeError: - queryQueue[i].put(None, 1) - - def test3_MaxUsage(self): - persist = PersistentPg(20) - db = persist.connection() - self.assertEqual(db._maxusage, 20) - for i in range(100): - r = db.query('select test%d' % i) - self.assertEqual(r, 'test%d' % i) - self.assert_(db.db.status) - j = i % 20 + 1 - self.assertEqual(db._usage, j) - self.assertEqual(db.num_queries, j) - - def test4_SetSession(self): - persist = PersistentPg(3, ('set datestyle',)) - db = persist.connection() - self.assertEqual(db._maxusage, 3) - self.assertEqual(db._setsession_sql, ('set datestyle',)) - self.assertEqual(db.db.session, ['datestyle']) - db.query('set test') - for i in range(3): - self.assertEqual(db.db.session, ['datestyle', 'test']) - db.query('select test') - self.assertEqual(db.db.session, ['datestyle']) - - def test5_FailedTransaction(self): - persist = PersistentPg() - db = persist.connection() - db._con.close() - self.assertEqual(db.query('select test'), 'test') - db.begin() - db._con.close() - self.assertRaises(pg.InternalError, db.query, 'select test') - self.assertEqual(db.query('select test'), 'test') - db.begin() - self.assertEqual(db.query('select test'), 'test') - db.rollback() - db._con.close() - self.assertEqual(db.query('select test'), 'test') - - -if __name__ == '__main__': - unittest.main() diff --git a/DBUtils/Tests/TestPooledDB.py b/DBUtils/Tests/TestPooledDB.py deleted file mode 100644 index 9e808cb..0000000 --- a/DBUtils/Tests/TestPooledDB.py +++ /dev/null @@ -1,1220 +0,0 @@ -"""Test the PooledDB module. - -Note: -We don't test performance here, so the test does not predicate -whether PooledDB actually will help in improving performance or not. -We also assume that the underlying SteadyDB connections are tested. - -Copyright and credit info: - -* This test was contributed by Christoph Zwerschke - -""" - -__version__ = '1.1.1b1' - -import sys -import unittest - -sys.path.insert(1, '../..') -# The TestSteadyDB module serves as a mock object for the DB-API 2 module: -from DBUtils.Tests import TestSteadyDB as dbapi -from DBUtils.PooledDB import (PooledDB, SharedDBConnection, - InvalidConnection, TooManyConnections) - - -class TestPooledDB(unittest.TestCase): - - def test00_CheckVersion(self): - from DBUtils import __version__ as DBUtilsVersion - self.assertEqual(DBUtilsVersion, __version__) - from DBUtils.PooledDB import __version__ as PooledDBVersion - self.assertEqual(PooledDBVersion, __version__) - self.assertEqual(PooledDB.version, __version__) - - def test01_NoThreadsafety(self): - from DBUtils.PooledDB import NotSupportedError - for threadsafety in (None, 0): - dbapi.threadsafety = threadsafety - self.assertRaises(NotSupportedError, PooledDB, dbapi) - - def test02_Threadsafety(self): - for threadsafety in (1, 2, 3): - dbapi.threadsafety = threadsafety - pool = PooledDB(dbapi, 0, 0, 1) - self.assert_(hasattr(pool, '_maxshared')) - if threadsafety > 1: - self.assertEqual(pool._maxshared, 1) - self.assert_(hasattr(pool, '_shared_cache')) - else: - self.assertEqual(pool._maxshared, 0) - self.assert_(not hasattr(pool, '_shared_cache')) - - def test03_CreateConnection(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - shareable = threadsafety > 1 - pool = PooledDB(dbapi, - 1, 1, 1, 0, False, None, None, True, None, None, - 'PooledDBTestDB', user='PooledDBTestUser') - self.assert_(hasattr(pool, '_idle_cache')) - self.assertEqual(len(pool._idle_cache), 1) - if shareable: - self.assert_(hasattr(pool, '_shared_cache')) - self.assertEqual(len(pool._shared_cache), 0) - else: - self.assert_(not hasattr(pool, '_shared_cache')) - self.assert_(hasattr(pool, '_maxusage')) - self.assertEqual(pool._maxusage, None) - self.assert_(hasattr(pool, '_setsession')) - self.assert_(pool._setsession is None) - con = pool._idle_cache[0] - from DBUtils.SteadyDB import SteadyDBConnection - self.assert_(isinstance(con, SteadyDBConnection)) - self.assert_(hasattr(con, '_maxusage')) - self.assertEqual(con._maxusage, 0) - self.assert_(hasattr(con, '_setsession_sql')) - self.assert_(con._setsession_sql is None) - db = pool.connection() - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 1) - self.assert_(hasattr(db, '_con')) - self.assertEqual(db._con, con) - self.assert_(hasattr(db, 'cursor')) - self.assert_(hasattr(db, '_usage')) - self.assertEqual(db._usage, 0) - self.assert_(hasattr(con, '_con')) - db_con = con._con - self.assert_(hasattr(db_con, 'database')) - self.assertEqual(db_con.database, 'PooledDBTestDB') - self.assert_(hasattr(db_con, 'user')) - self.assertEqual(db_con.user, 'PooledDBTestUser') - self.assert_(hasattr(db_con, 'open_cursors')) - self.assertEqual(db_con.open_cursors, 0) - self.assert_(hasattr(db_con, 'num_uses')) - self.assertEqual(db_con.num_uses, 0) - self.assert_(hasattr(db_con, 'num_queries')) - self.assertEqual(db_con.num_queries, 0) - cursor = db.cursor() - self.assertEqual(db_con.open_cursors, 1) - cursor.execute('select test') - r = cursor.fetchone() - cursor.close() - self.assertEqual(db_con.open_cursors, 0) - self.assertEqual(r, 'test') - self.assertEqual(db_con.num_queries, 1) - self.assertEqual(db._usage, 1) - cursor = db.cursor() - self.assertEqual(db_con.open_cursors, 1) - cursor.execute('set sessiontest') - cursor2 = db.cursor() - self.assertEqual(db_con.open_cursors, 2) - cursor2.close() - self.assertEqual(db_con.open_cursors, 1) - cursor.close() - self.assertEqual(db_con.open_cursors, 0) - self.assertEqual(db_con.num_queries, 1) - self.assertEqual(db._usage, 2) - self.assertEqual(db_con.session, - ['rollback', 'sessiontest']) - pool = PooledDB(dbapi, 1, 1, 1) - self.assertEqual(len(pool._idle_cache), 1) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - db = pool.connection() - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 1) - db.close() - self.assertEqual(len(pool._idle_cache), 1) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - db = pool.connection(True) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 1) - db.close() - self.assertEqual(len(pool._idle_cache), 1) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - db = pool.connection(False) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - self.assertEqual(db._usage, 0) - db_con = db._con._con - self.assert_(db_con.database is None) - self.assert_(db_con.user is None) - db.close() - self.assertEqual(len(pool._idle_cache), 1) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - db = pool.dedicated_connection() - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - self.assertEqual(db._usage, 0) - db_con = db._con._con - self.assert_(db_con.database is None) - self.assert_(db_con.user is None) - db.close() - self.assertEqual(len(pool._idle_cache), 1) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - pool = PooledDB(dbapi, 0, 0, 0, 0, False, 3, ('set datestyle',)) - self.assertEqual(pool._maxusage, 3) - self.assertEqual(pool._setsession, ('set datestyle',)) - con = pool.connection()._con - self.assertEqual(con._maxusage, 3) - self.assertEqual(con._setsession_sql, ('set datestyle',)) - - def test04_CloseConnection(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - shareable = threadsafety > 1 - pool = PooledDB(dbapi, - 0, 1, 1, 0, False, None, None, True, None, None, - 'PooledDBTestDB', user='PooledDBTestUser') - self.assert_(hasattr(pool, '_idle_cache')) - self.assertEqual(len(pool._idle_cache), 0) - db = pool.connection() - self.assert_(hasattr(db, '_con')) - con = db._con - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 1) - self.assert_(hasattr(db, '_shared_con')) - shared_con = db._shared_con - self.assertEqual(pool._shared_cache[0], shared_con) - self.assert_(hasattr(shared_con, 'shared')) - self.assertEqual(shared_con.shared, 1) - self.assert_(hasattr(shared_con, 'con')) - self.assertEqual(shared_con.con, con) - from DBUtils.SteadyDB import SteadyDBConnection - self.assert_(isinstance(con, SteadyDBConnection)) - self.assert_(hasattr(con, '_con')) - db_con = con._con - self.assert_(hasattr(db_con, 'num_queries')) - self.assertEqual(db._usage, 0) - self.assertEqual(db_con.num_queries, 0) - db.cursor().execute('select test') - self.assertEqual(db._usage, 1) - self.assertEqual(db_con.num_queries, 1) - db.close() - self.assertEqual(db._con, None) - if shareable: - self.assertEqual(db._shared_con, None) - self.assertEqual(shared_con.shared, 0) - self.assertRaises(InvalidConnection, getattr, db, '_usage') - self.assert_(not hasattr(db_con, '_num_queries')) - self.assertEqual(len(pool._idle_cache), 1) - self.assertEqual(pool._idle_cache[0]._con, db_con) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - db.close() - if shareable: - self.assertEqual(shared_con.shared, 0) - db = pool.connection() - self.assertEqual(db._con, con) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 1) - shared_con = db._shared_con - self.assertEqual(pool._shared_cache[0], shared_con) - self.assertEqual(shared_con.con, con) - self.assertEqual(shared_con.shared, 1) - self.assertEqual(db._usage, 1) - self.assertEqual(db_con.num_queries, 1) - self.assert_(hasattr(db_con, 'database')) - self.assertEqual(db_con.database, 'PooledDBTestDB') - self.assert_(hasattr(db_con, 'user')) - self.assertEqual(db_con.user, 'PooledDBTestUser') - db.cursor().execute('select test') - self.assertEqual(db_con.num_queries, 2) - db.cursor().execute('select test') - self.assertEqual(db_con.num_queries, 3) - db.close() - self.assertEqual(len(pool._idle_cache), 1) - self.assertEqual(pool._idle_cache[0]._con, db_con) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - db = pool.connection(False) - self.assertEqual(db._con, con) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - db.close() - self.assertEqual(len(pool._idle_cache), 1) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - - def test05_CloseAll(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - shareable = threadsafety > 1 - pool = PooledDB(dbapi, 10) - self.assertEqual(len(pool._idle_cache), 10) - pool.close() - self.assertEqual(len(pool._idle_cache), 0) - pool = PooledDB(dbapi, 10) - closed = ['no'] - def close(what=closed): - what[0] = 'yes' - pool._idle_cache[7]._con.close = close - self.assertEqual(closed, ['no']) - del pool - self.assertEqual(closed, ['yes']) - pool = PooledDB(dbapi, 10, 10, 5) - self.assertEqual(len(pool._idle_cache), 10) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - cache = [] - for i in range(5): - cache.append(pool.connection()) - self.assertEqual(len(pool._idle_cache), 5) - if shareable: - self.assertEqual(len(pool._shared_cache), 5) - else: - self.assertEqual(len(pool._idle_cache), 5) - pool.close() - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - pool = PooledDB(dbapi, 10, 10, 5) - closed = [] - def close_idle(what=closed): - what.append('idle') - def close_shared(what=closed): - what.append('shared') - if shareable: - cache = [] - for i in range(5): - cache.append(pool.connection()) - pool._shared_cache[3].con.close = close_shared - else: - pool._idle_cache[7]._con.close = close_shared - pool._idle_cache[3]._con.close = close_idle - self.assertEqual(closed, []) - del pool - if shareable: - del cache - self.assertEqual(closed, ['idle', 'shared']) - - def test06_ShareableConnection(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - shareable = threadsafety > 1 - pool = PooledDB(dbapi, 0, 1, 2) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - db1 = pool.connection() - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 1) - db2 = pool.connection() - self.assertNotEqual(db1._con, db2._con) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 2) - db3 = pool.connection() - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 2) - self.assertEqual(db3._con, db1._con) - self.assertEqual(db1._shared_con.shared, 2) - self.assertEqual(db2._shared_con.shared, 1) - else: - self.assertNotEqual(db3._con, db1._con) - self.assertNotEqual(db3._con, db2._con) - db4 = pool.connection() - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 2) - self.assertEqual(db4._con, db2._con) - self.assertEqual(db1._shared_con.shared, 2) - self.assertEqual(db2._shared_con.shared, 2) - else: - self.assertNotEqual(db4._con, db1._con) - self.assertNotEqual(db4._con, db2._con) - self.assertNotEqual(db4._con, db3._con) - db5 = pool.connection(False) - self.assertNotEqual(db5._con, db1._con) - self.assertNotEqual(db5._con, db2._con) - self.assertNotEqual(db5._con, db3._con) - self.assertNotEqual(db5._con, db4._con) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 2) - self.assertEqual(db1._shared_con.shared, 2) - self.assertEqual(db2._shared_con.shared, 2) - db5.close() - self.assertEqual(len(pool._idle_cache), 1) - db5 = pool.connection() - if shareable: - self.assertEqual(len(pool._idle_cache), 1) - self.assertEqual(len(pool._shared_cache), 2) - self.assertEqual(db5._shared_con.shared, 3) - else: - self.assertEqual(len(pool._idle_cache), 0) - pool = PooledDB(dbapi, 0, 0, 1) - self.assertEqual(len(pool._idle_cache), 0) - db1 = pool.connection(False) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - db2 = pool.connection() - if shareable: - self.assertEqual(len(pool._shared_cache), 1) - db3 = pool.connection() - if shareable: - self.assertEqual(len(pool._shared_cache), 1) - self.assertEqual(db2._con, db3._con) - else: - self.assertNotEqual(db2._con, db3._con) - del db3 - if shareable: - self.assertEqual(len(pool._idle_cache), 0) - self.assertEqual(len(pool._shared_cache), 1) - else: - self.assertEqual(len(pool._idle_cache), 1) - del db2 - if shareable: - self.assertEqual(len(pool._idle_cache), 1) - self.assertEqual(len(pool._shared_cache), 0) - else: - self.assertEqual(len(pool._idle_cache), 2) - del db1 - if shareable: - self.assertEqual(len(pool._idle_cache), 2) - self.assertEqual(len(pool._shared_cache), 0) - else: - self.assertEqual(len(pool._idle_cache), 3) - - def test08_MinMaxCached(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - shareable = threadsafety > 1 - pool = PooledDB(dbapi, 3) - self.assertEqual(len(pool._idle_cache), 3) - cache = [pool.connection() for i in range(3)] - self.assertEqual(len(pool._idle_cache), 0) - del cache - self.assertEqual(len(pool._idle_cache), 3) - cache = [pool.connection() for i in range(6)] - self.assertEqual(len(pool._idle_cache), 0) - del cache - self.assertEqual(len(pool._idle_cache), 6) - pool = PooledDB(dbapi, 0, 3) - self.assertEqual(len(pool._idle_cache), 0) - cache = [pool.connection() for i in range(3)] - self.assertEqual(len(pool._idle_cache), 0) - del cache - self.assertEqual(len(pool._idle_cache), 3) - cache = [pool.connection() for i in range(6)] - self.assertEqual(len(pool._idle_cache), 0) - del cache - self.assertEqual(len(pool._idle_cache), 3) - pool = PooledDB(dbapi, 3, 3) - self.assertEqual(len(pool._idle_cache), 3) - cache = [pool.connection() for i in range(3)] - self.assertEqual(len(pool._idle_cache), 0) - del cache - self.assertEqual(len(pool._idle_cache), 3) - cache = [pool.connection() for i in range(6)] - self.assertEqual(len(pool._idle_cache), 0) - del cache - self.assertEqual(len(pool._idle_cache), 3) - pool = PooledDB(dbapi, 3, 2) - self.assertEqual(len(pool._idle_cache), 3) - cache = [pool.connection() for i in range(4)] - self.assertEqual(len(pool._idle_cache), 0) - del cache - self.assertEqual(len(pool._idle_cache), 3) - pool = PooledDB(dbapi, 2, 5) - self.assertEqual(len(pool._idle_cache), 2) - cache = [pool.connection() for i in range(10)] - self.assertEqual(len(pool._idle_cache), 0) - del cache - self.assertEqual(len(pool._idle_cache), 5) - pool = PooledDB(dbapi, 1, 2, 3) - self.assertEqual(len(pool._idle_cache), 1) - cache = [pool.connection(False) for i in range(4)] - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - del cache - self.assertEqual(len(pool._idle_cache), 2) - cache = [pool.connection() for i in range(10)] - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 3) - del cache - self.assertEqual(len(pool._idle_cache), 2) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - pool = PooledDB(dbapi, 1, 3, 2) - self.assertEqual(len(pool._idle_cache), 1) - cache = [pool.connection(False) for i in range(4)] - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - del cache - self.assertEqual(len(pool._idle_cache), 3) - cache = [pool.connection() for i in range(10)] - if shareable: - self.assertEqual(len(pool._idle_cache), 1) - self.assertEqual(len(pool._shared_cache), 2) - else: - self.assertEqual(len(pool._idle_cache), 0) - del cache - self.assertEqual(len(pool._idle_cache), 3) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - - def test08_MaxShared(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - shareable = threadsafety > 1 - pool = PooledDB(dbapi) - self.assertEqual(len(pool._idle_cache), 0) - cache = [pool.connection() for i in range(10)] - self.assertEqual(len(pool._idle_cache), 0) - pool = PooledDB(dbapi, 1, 1, 0) - self.assertEqual(len(pool._idle_cache), 1) - cache = [pool.connection() for i in range(10)] - self.assertEqual(len(pool._idle_cache), 0) - pool = PooledDB(dbapi, 0, 0, 1) - cache = [pool.connection() for i in range(10)] - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 1) - pool = PooledDB(dbapi, 1, 1, 1) - self.assertEqual(len(pool._idle_cache), 1) - cache = [pool.connection() for i in range(10)] - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 1) - pool = PooledDB(dbapi, 0, 0, 7) - cache = [pool.connection(False) for i in range(3)] - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - cache = [pool.connection() for i in range(10)] - self.assertEqual(len(pool._idle_cache), 3) - if shareable: - self.assertEqual(len(pool._shared_cache), 7) - - def test09_SortShared(self): - dbapi.threadsafety = 2 - pool = PooledDB(dbapi, 0, 4, 4) - cache = [] - for i in range(6): - db = pool.connection() - db.cursor().execute('select test') - cache.append(db) - for i, db in enumerate(cache): - self.assertEqual(db._shared_con.shared, 2 <= i < 4 and 1 or 2) - cache[2].begin() - cache[3].begin() - db = pool.connection() - self.assert_(db._con is cache[0]._con) - db.close() - cache[3].rollback() - db = pool.connection() - self.assert_(db._con is cache[3]._con) - - def test10_EquallyShared(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - shareable = threadsafety > 1 - pool = PooledDB(dbapi, 5, 5, 5) - self.assertEqual(len(pool._idle_cache), 5) - for i in range(15): - db = pool.connection(False) - db.cursor().execute('select test') - db.close() - self.assertEqual(len(pool._idle_cache), 5) - for i in range(5): - con = pool._idle_cache[i] - self.assertEqual(con._usage, 3) - self.assertEqual(con._con.num_queries, 3) - cache = [] - for i in range(35): - db = pool.connection() - db.cursor().execute('select test') - cache.append(db) - del db - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 5) - for i in range(5): - con = pool._shared_cache[i] - self.assertEqual(con.shared, 7) - con = con.con - self.assertEqual(con._usage, 10) - self.assertEqual(con._con.num_queries, 10) - del cache - self.assertEqual(len(pool._idle_cache), 5) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - - def test11_ManyShared(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - shareable = threadsafety > 1 - pool = PooledDB(dbapi, 0, 0, 5) - cache = [] - for i in range(35): - db = pool.connection() - db.cursor().execute('select test1') - db.cursor().execute('select test2') - db.cursor().callproc('test3') - cache.append(db) - del db - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 5) - for i in range(5): - con = pool._shared_cache[i] - self.assertEqual(con.shared, 7) - con = con.con - self.assertEqual(con._usage, 21) - self.assertEqual(con._con.num_queries, 14) - cache[3] = cache[8] = cache[33] = None - cache[12] = cache[17] = cache[34] = None - self.assertEqual(len(pool._shared_cache), 5) - self.assertEqual(pool._shared_cache[0].shared, 7) - self.assertEqual(pool._shared_cache[1].shared, 7) - self.assertEqual(pool._shared_cache[2].shared, 5) - self.assertEqual(pool._shared_cache[3].shared, 4) - self.assertEqual(pool._shared_cache[4].shared, 6) - for db in cache: - if db: - db.cursor().callproc('test4') - for i in range(6): - db = pool.connection() - db.cursor().callproc('test4') - cache.append(db) - del db - for i in range(5): - con = pool._shared_cache[i] - self.assertEqual(con.shared, 7) - con = con.con - self.assertEqual(con._usage, 28) - self.assertEqual(con._con.num_queries, 14) - del cache - if shareable: - self.assertEqual(len(pool._idle_cache), 5) - self.assertEqual(len(pool._shared_cache), 0) - else: - self.assertEqual(len(pool._idle_cache), 35) - - def test12_Rollback(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - pool = PooledDB(dbapi, 0, 1) - self.assertEqual(len(pool._idle_cache), 0) - db = pool.connection(False) - self.assertEqual(len(pool._idle_cache), 0) - self.assertEqual(db._con._con.open_cursors, 0) - cursor = db.cursor() - self.assertEqual(db._con._con.open_cursors, 1) - cursor.execute('set doit1') - db.commit() - cursor.execute('set dont1') - cursor.close() - self.assertEqual(db._con._con.open_cursors, 0) - del db - self.assertEqual(len(pool._idle_cache), 1) - db = pool.connection(False) - self.assertEqual(len(pool._idle_cache), 0) - self.assertEqual(db._con._con.open_cursors, 0) - cursor = db.cursor() - self.assertEqual(db._con._con.open_cursors, 1) - cursor.execute('set doit2') - cursor.close() - self.assertEqual(db._con._con.open_cursors, 0) - db.commit() - session = db._con._con.session - db.close() - self.assertEqual(session, [ - 'doit1', 'commit', 'dont1', 'rollback', - 'doit2', 'commit', 'rollback']) - - def test13_MaxConnections(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - shareable = threadsafety > 1 - pool = PooledDB(dbapi, 1, 2, 2, 3) - self.assert_(hasattr(pool, '_maxconnections')) - self.assertEqual(pool._maxconnections, 3) - self.assert_(hasattr(pool, '_connections')) - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 1) - cache = [] - for i in range(3): - cache.append(pool.connection(False)) - self.assertEqual(pool._connections, 3) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - self.assertRaises(TooManyConnections, pool.connection, 0) - self.assertRaises(TooManyConnections, pool.connection) - cache = [] - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 2) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - for i in range(3): - cache.append(pool.connection()) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(pool._connections, 2) - self.assertEqual(len(pool._shared_cache), 2) - cache.append(pool.connection(False)) - self.assertEqual(pool._connections, 3) - self.assertEqual(len(pool._shared_cache), 2) - else: - self.assertEqual(pool._connections, 3) - self.assertRaises(TooManyConnections, pool.connection, 0) - if shareable: - cache.append(pool.connection(True)) - self.assertEqual(pool._connections, 3) - else: - self.assertRaises(TooManyConnections, pool.connection) - del cache - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 2) - pool = PooledDB(dbapi, 0, 1, 1, 1) - self.assertEqual(pool._maxconnections, 1) - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 0) - db = pool.connection(False) - self.assertEqual(pool._connections, 1) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - self.assertRaises(TooManyConnections, pool.connection, 0) - self.assertRaises(TooManyConnections, pool.connection) - del db - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 1) - cache = [pool.connection()] - self.assertEqual(pool._connections, 1) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 1) - cache.append(pool.connection()) - self.assertEqual(pool._connections, 1) - self.assertEqual(len(pool._shared_cache), 1) - self.assertEqual(pool._shared_cache[0].shared, 2) - else: - self.assertRaises(TooManyConnections, pool.connection) - self.assertRaises(TooManyConnections, pool.connection, 0) - if shareable: - cache.append(pool.connection(True)) - self.assertEqual(pool._connections, 1) - self.assertEqual(len(pool._shared_cache), 1) - self.assertEqual(pool._shared_cache[0].shared, 3) - else: - self.assertRaises(TooManyConnections, pool.connection, 1) - del cache - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 1) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - db = pool.connection(False) - self.assertEqual(pool._connections, 1) - self.assertEqual(len(pool._idle_cache), 0) - del db - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 1) - pool = PooledDB(dbapi, 1, 2, 2, 1) - self.assertEqual(pool._maxconnections, 2) - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 1) - cache = [] - cache.append(pool.connection(False)) - self.assertEqual(pool._connections, 1) - self.assertEqual(len(pool._idle_cache), 0) - cache.append(pool.connection(False)) - self.assertEqual(pool._connections, 2) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - self.assertRaises(TooManyConnections, pool.connection, 0) - self.assertRaises(TooManyConnections, pool.connection) - pool = PooledDB(dbapi, 4, 3, 2, 1, False) - self.assertEqual(pool._maxconnections, 4) - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 4) - cache = [] - for i in range(4): - cache.append(pool.connection(False)) - self.assertEqual(pool._connections, 4) - self.assertEqual(len(pool._idle_cache), 0) - self.assertRaises(TooManyConnections, pool.connection, 0) - self.assertRaises(TooManyConnections, pool.connection) - pool = PooledDB(dbapi, 1, 2, 3, 4, False) - self.assertEqual(pool._maxconnections, 4) - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 1) - for i in range(4): - cache.append(pool.connection()) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(pool._connections, 3) - self.assertEqual(len(pool._shared_cache), 3) - cache.append(pool.connection()) - self.assertEqual(pool._connections, 3) - cache.append(pool.connection(False)) - self.assertEqual(pool._connections, 4) - else: - self.assertEqual(pool._connections, 4) - self.assertRaises(TooManyConnections, pool.connection) - self.assertRaises(TooManyConnections, pool.connection, 0) - pool = PooledDB(dbapi, 0, 0, 3, 3, False) - self.assertEqual(pool._maxconnections, 3) - self.assertEqual(pool._connections, 0) - cache = [] - for i in range(3): - cache.append(pool.connection(False)) - self.assertEqual(pool._connections, 3) - self.assertRaises(TooManyConnections, pool.connection, 0) - self.assertRaises(TooManyConnections, pool.connection, 1) - cache = [] - self.assertEqual(pool._connections, 0) - for i in range(3): - cache.append(pool.connection()) - self.assertEqual(pool._connections, 3) - if shareable: - for i in range(3): - cache.append(pool.connection()) - self.assertEqual(pool._connections, 3) - else: - self.assertRaises(TooManyConnections, pool.connection) - self.assertRaises(TooManyConnections, pool.connection, 0) - pool = PooledDB(dbapi, 0, 0, 3) - self.assertEqual(pool._maxconnections, 0) - self.assertEqual(pool._connections, 0) - cache = [] - for i in range(10): - cache.append(pool.connection(False)) - cache.append(pool.connection()) - if shareable: - self.assertEqual(pool._connections, 13) - self.assertEqual(len(pool._shared_cache), 3) - else: - self.assertEqual(pool._connections, 20) - pool = PooledDB(dbapi, 1, 1, 1, 1, True) - self.assertEqual(pool._maxconnections, 1) - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 1) - db = pool.connection(False) - self.assertEqual(pool._connections, 1) - self.assertEqual(len(pool._idle_cache), 0) - def connection(): - db = pool.connection() - cursor = db.cursor() - cursor.execute('set thread') - cursor.close() - db.close() - from threading import Thread - thread = Thread(target=connection) - thread.start() - thread.join(0.1) - self.assert_(thread.isAlive()) - self.assertEqual(pool._connections, 1) - self.assertEqual(len(pool._idle_cache), 0) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - session = db._con._con.session - self.assertEqual(session, ['rollback']) - del db - thread.join(0.1) - self.assert_(not thread.isAlive()) - self.assertEqual(pool._connections, 0) - self.assertEqual(len(pool._idle_cache), 1) - if shareable: - self.assertEqual(len(pool._shared_cache), 0) - db = pool.connection(False) - self.assertEqual(pool._connections, 1) - self.assertEqual(len(pool._idle_cache), 0) - self.assertEqual(session, ['rollback', - 'rollback', 'thread', 'rollback']) - - def test14_MaxUsage(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - for maxusage in (0, 3, 7): - pool = PooledDB(dbapi, 0, 0, 0, 1, False, maxusage) - self.assertEqual(pool._maxusage, maxusage) - self.assertEqual(len(pool._idle_cache), 0) - db = pool.connection(False) - self.assertEqual(db._con._maxusage, maxusage) - self.assertEqual(len(pool._idle_cache), 0) - self.assertEqual(db._con._con.open_cursors, 0) - self.assertEqual(db._usage, 0) - self.assertEqual(db._con._con.num_uses, 0) - self.assertEqual(db._con._con.num_queries, 0) - for i in range(20): - cursor=db.cursor() - self.assertEqual(db._con._con.open_cursors, 1) - cursor.execute('select test%i' % i) - r = cursor.fetchone() - self.assertEqual(r, 'test%i' % i) - cursor.close() - self.assertEqual(db._con._con.open_cursors, 0) - if maxusage: - j = i % maxusage + 1 - else: - j = i + 1 - self.assertEqual(db._usage, j) - self.assertEqual(db._con._con.num_uses, j) - self.assertEqual(db._con._con.num_queries, j) - db.cursor().callproc('test') - self.assertEqual(db._con._con.open_cursors, 0) - self.assertEqual(db._usage, j + 1) - self.assertEqual(db._con._con.num_uses, j + 1) - self.assertEqual(db._con._con.num_queries, j) - - def test15_SetSession(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - setsession = ('set time zone', 'set datestyle') - pool = PooledDB(dbapi, 0, 0, 0, 1, False, None, setsession) - self.assertEqual(pool._setsession, setsession) - db = pool.connection(False) - self.assertEqual(db._setsession_sql, setsession) - self.assertEqual(db._con._con.session, - ['time zone', 'datestyle']) - db.cursor().execute('select test') - db.cursor().execute('set test1') - self.assertEqual(db._usage, 2) - self.assertEqual(db._con._con.num_uses, 4) - self.assertEqual(db._con._con.num_queries, 1) - self.assertEqual(db._con._con.session, - ['time zone', 'datestyle', 'test1']) - db.close() - db = pool.connection(False) - self.assertEqual(db._setsession_sql, setsession) - self.assertEqual(db._con._con.session, - ['time zone', 'datestyle', 'test1', 'rollback']) - db._con._con.close() - db.cursor().execute('select test') - db.cursor().execute('set test2') - self.assertEqual(db._con._con.session, - ['time zone', 'datestyle', 'test2']) - - def test16_OneThreadTwoConnections(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - shareable = threadsafety > 1 - pool = PooledDB(dbapi, 2) - db1 = pool.connection() - for i in range(5): - db1.cursor().execute('select test') - db2 = pool.connection() - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - for i in range(7): - db2.cursor().execute('select test') - self.assertEqual(db1._con._con.num_queries, 5) - self.assertEqual(db2._con._con.num_queries, 7) - del db1 - db1 = pool.connection() - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - for i in range(3): - db1.cursor().execute('select test') - self.assertEqual(db1._con._con.num_queries, 8) - db2.cursor().execute('select test') - self.assertEqual(db2._con._con.num_queries, 8) - pool = PooledDB(dbapi, 0, 0, 2) - db1 = pool.connection() - for i in range(5): - db1.cursor().execute('select test') - db2 = pool.connection() - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - for i in range(7): - db2.cursor().execute('select test') - self.assertEqual(db1._con._con.num_queries, 5) - self.assertEqual(db2._con._con.num_queries, 7) - del db1 - db1 = pool.connection() - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - for i in range(3): - db1.cursor().execute('select test') - self.assertEqual(db1._con._con.num_queries, 8) - db2.cursor().execute('select test') - self.assertEqual(db2._con._con.num_queries, 8) - pool = PooledDB(dbapi, 0, 0, 1) - db1 = pool.connection() - db2 = pool.connection() - self.assertNotEqual(db1, db2) - if shareable: - self.assertEqual(db1._con, db2._con) - else: - self.assertNotEqual(db1._con, db2._con) - del db1 - db1 = pool.connection(False) - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - - def test17_ThreeThreadsTwoConnections(self): - for threadsafety in (1, 2): - dbapi.threadsafety = threadsafety - pool = PooledDB(dbapi, 2, 2, 0, 2, True) - from Queue import Queue, Empty - queue = Queue(3) - def connection(): - try: - queue.put(pool.connection(), 1, 1) - except Exception: - queue.put(pool.connection(), 1) - from threading import Thread - for i in range(3): - Thread(target=connection).start() - try: - db1 = queue.get(1, 1) - db2 = queue.get(1, 1) - except TypeError: - db1 = queue.get(1) - db2 = queue.get(1) - self.assertNotEqual(db1, db2) - db1_con = db1._con - db2_con = db2._con - self.assertNotEqual(db1_con, db2_con) - try: - self.assertRaises(Empty, queue.get, 1, 0.1) - except TypeError: - self.assertRaises(Empty, queue.get, 0) - del db1 - try: - db1 = queue.get(1, 1) - except TypeError: - db1 = queue.get(1) - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - self.assertEqual(db1._con, db1_con) - pool = PooledDB(dbapi, 2, 2, 1, 2, True) - db1 = pool.connection(False) - db2 = pool.connection(False) - self.assertNotEqual(db1, db2) - db1_con = db1._con - db2_con = db2._con - self.assertNotEqual(db1_con, db2_con) - Thread(target=connection).start() - try: - self.assertRaises(Empty, queue.get, 1, 0.1) - except TypeError: - self.assertRaises(Empty, queue.get, 0) - del db1 - try: - db1 = queue.get(1, 1) - except TypeError: - db1 = queue.get(1) - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - self.assertEqual(db1._con, db1_con) - - def test18_PingCheck(self): - Connection = dbapi.Connection - Connection.has_ping = True - Connection.num_pings = 0 - dbapi.threadsafety = 2 - pool = PooledDB(dbapi, 1, 1, 0, 0, False, None, None, True, None, 0) - db = pool.connection() - self.assert_(db._con._con.valid) - self.assertEqual(Connection.num_pings, 0) - db._con.close() - db.close() - db = pool.connection() - self.assert_(not db._con._con.valid) - self.assertEqual(Connection.num_pings, 0) - pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 0) - db = pool.connection() - self.assert_(db._con._con.valid) - self.assertEqual(Connection.num_pings, 0) - db._con.close() - db = pool.connection() - self.assert_(not db._con._con.valid) - self.assertEqual(Connection.num_pings, 0) - pool = PooledDB(dbapi, 1, 1, 0, 0, False, None, None, True, None, 1) - db = pool.connection() - self.assert_(db._con._con.valid) - self.assertEqual(Connection.num_pings, 1) - db._con.close() - db.close() - db = pool.connection() - self.assert_(db._con._con.valid) - self.assertEqual(Connection.num_pings, 2) - pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 1) - db = pool.connection() - self.assert_(db._con._con.valid) - self.assertEqual(Connection.num_pings, 3) - db._con.close() - db = pool.connection() - self.assert_(db._con._con.valid) - self.assertEqual(Connection.num_pings, 4) - pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 2) - db = pool.connection() - self.assert_(db._con._con.valid) - self.assertEqual(Connection.num_pings, 4) - db._con.close() - db = pool.connection() - self.assert_(not db._con._con.valid) - self.assertEqual(Connection.num_pings, 4) - db.cursor() - self.assert_(db._con._con.valid) - self.assertEqual(Connection.num_pings, 5) - pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 4) - db = pool.connection() - self.assert_(db._con._con.valid) - self.assertEqual(Connection.num_pings, 5) - db._con.close() - db = pool.connection() - self.assert_(not db._con._con.valid) - self.assertEqual(Connection.num_pings, 5) - cursor = db.cursor() - db._con.close() - self.assert_(not db._con._con.valid) - self.assertEqual(Connection.num_pings, 5) - cursor.execute('select test') - self.assert_(db._con._con.valid) - self.assertEqual(Connection.num_pings, 6) - Connection.has_ping = False - Connection.num_pings = 0 - - def test19_FailedTransaction(self): - dbapi.threadsafety = 2 - pool = PooledDB(dbapi, 0, 1, 1) - db = pool.connection() - cursor = db.cursor() - db._con._con.close() - cursor.execute('select test') - db.begin() - db._con._con.close() - self.assertRaises(dbapi.InternalError, cursor.execute, 'select test') - cursor.execute('select test') - db.begin() - db.cancel() - db._con._con.close() - cursor.execute('select test') - pool = PooledDB(dbapi, 1, 1, 0) - db = pool.connection() - cursor = db.cursor() - db._con._con.close() - cursor.execute('select test') - db.begin() - db._con._con.close() - self.assertRaises(dbapi.InternalError, cursor.execute, 'select test') - cursor.execute('select test') - db.begin() - db.cancel() - db._con._con.close() - cursor.execute('select test') - - def test20_SharedInTransaction(self): - dbapi.threadsafety = 2 - pool = PooledDB(dbapi, 0, 1, 1) - db = pool.connection() - db.begin() - pool.connection(False) - self.assertRaises(TooManyConnections, pool.connection) - pool = PooledDB(dbapi, 0, 2, 2) - db1 = pool.connection() - db2 = pool.connection() - self.assert_(db2._con is not db1._con) - db2.close() - db2 = pool.connection() - self.assert_(db2._con is not db1._con) - db = pool.connection() - self.assert_(db._con is db1._con) - db.close() - db1.begin() - db = pool.connection() - self.assert_(db._con is db2._con) - db.close() - db2.begin() - pool.connection(False) - self.assertRaises(TooManyConnections, pool.connection) - db1.rollback() - db = pool.connection() - self.assert_(db._con is db1._con) - - def test21_ResetTransaction(self): - pool = PooledDB(dbapi, 1, 1, 0) - db = pool.connection() - db.begin() - con = db._con - self.assert_(con._transaction) - self.assertEqual(con._con.session, ['rollback']) - db.close() - self.assert_(pool.connection()._con is con) - self.assert_(not con._transaction) - self.assertEqual(con._con.session, ['rollback'] * 3) - pool = PooledDB(dbapi, 1, 1, 0, reset=False) - db = pool.connection() - db.begin() - con = db._con - self.assert_(con._transaction) - self.assertEqual(con._con.session, []) - db.close() - self.assert_(pool.connection()._con is con) - self.assert_(not con._transaction) - self.assertEqual(con._con.session, ['rollback']) - - -class TestSharedDBConnection(unittest.TestCase): - - def test01_CreateConnection(self): - db_con = dbapi.connect() - con = SharedDBConnection(db_con) - self.assertEqual(con.con, db_con) - self.assertEqual(con.shared, 1) - - def test01_ShareAndUnshare(self): - con = SharedDBConnection(dbapi.connect()) - self.assertEqual(con.shared, 1) - con.share() - self.assertEqual(con.shared, 2) - con.share() - self.assertEqual(con.shared, 3) - con.unshare() - self.assertEqual(con.shared, 2) - con.unshare() - self.assertEqual(con.shared, 1) - - def test02_Comparison(self): - con1 = SharedDBConnection(dbapi.connect()) - con1.con._transaction = False - con2 = SharedDBConnection(dbapi.connect()) - con2.con._transaction = False - self.assert_(con1 == con2) - self.assert_(con1 <= con2) - self.assert_(con1 >= con2) - self.assert_(not con1 != con2) - self.assert_(not con1 < con2) - self.assert_(not con1 > con2) - con2.share() - self.assert_(not con1 == con2) - self.assert_(con1 <= con2) - self.assert_(not con1 >= con2) - self.assert_(con1 != con2) - self.assert_(con1 < con2) - self.assert_(not con1 > con2) - con1.con._transaction = True - self.assert_(not con1 == con2) - self.assert_(not con1 <= con2) - self.assert_(con1 >= con2) - self.assert_(con1 != con2) - self.assert_(not con1 < con2) - self.assert_(con1 > con2) - - -if __name__ == '__main__': - unittest.main() diff --git a/DBUtils/Tests/TestPooledPg.py b/DBUtils/Tests/TestPooledPg.py deleted file mode 100644 index 6d39acf..0000000 --- a/DBUtils/Tests/TestPooledPg.py +++ /dev/null @@ -1,316 +0,0 @@ -"""Test the PooledPg module. - -Note: -We don't test performance here, so the test does not predicate -whether PooledPg actually will help in improving performance or not. -We also assume that the underlying SteadyPg connections are tested. - -Copyright and credit info: - -* This test was contributed by Christoph Zwerschke - -""" - -__version__ = '1.1.1b1' - -import sys -import unittest - -sys.path.insert(1, '../..') -# The TestSteadyPg module serves as a mock object for the pg API module: -from DBUtils.Tests import TestSteadyPg -from DBUtils.PooledPg import PooledPg, InvalidConnection - - -class TestPooledPg(unittest.TestCase): - - def test0_CheckVersion(self): - from DBUtils import __version__ as DBUtilsVersion - self.assertEqual(DBUtilsVersion, __version__) - from DBUtils.PooledPg import __version__ as PooledPgVersion - self.assertEqual(PooledPgVersion, __version__) - self.assertEqual(PooledPg.version, __version__) - - def test1_CreateConnection(self): - pool = PooledPg(1, 1, 0, False, None, None, False, - 'PooledPgTestDB', user='PooledPgTestUser') - self.assert_(hasattr(pool, '_cache')) - self.assertEqual(pool._cache.qsize(), 1) - self.assert_(hasattr(pool, '_maxusage')) - self.assert_(pool._maxusage is None) - self.assert_(hasattr(pool, '_setsession')) - self.assert_(pool._setsession is None) - self.assert_(hasattr(pool, '_reset')) - self.assertEqual(pool._reset, False) - db_con = pool._cache.get(0) - pool._cache.put(db_con, 0) - from DBUtils.SteadyPg import SteadyPgConnection - self.assert_(isinstance(db_con, SteadyPgConnection)) - db = pool.connection() - self.assertEqual(pool._cache.qsize(), 0) - self.assert_(hasattr(db, '_con')) - self.assertEqual(db._con, db_con) - self.assert_(hasattr(db, 'query')) - self.assert_(hasattr(db, 'num_queries')) - self.assertEqual(db.num_queries, 0) - self.assert_(hasattr(db, '_maxusage')) - self.assertEqual(db._maxusage, 0) - self.assert_(hasattr(db, '_setsession_sql')) - self.assert_(db._setsession_sql is None) - self.assert_(hasattr(db, 'dbname')) - self.assertEqual(db.dbname, 'PooledPgTestDB') - self.assert_(hasattr(db, 'user')) - self.assertEqual(db.user, 'PooledPgTestUser') - db.query('select test') - self.assertEqual(db.num_queries, 1) - pool = PooledPg(1) - db = pool.connection() - self.assert_(hasattr(db, 'dbname')) - self.assert_(db.dbname is None) - self.assert_(hasattr(db, 'user')) - self.assert_(db.user is None) - self.assert_(hasattr(db, 'num_queries')) - self.assertEqual(db.num_queries, 0) - pool = PooledPg(0, 0, 0, False, 3, ('set datestyle',),) - self.assertEqual(pool._maxusage, 3) - self.assertEqual(pool._setsession, ('set datestyle',)) - db = pool.connection() - self.assertEqual(db._maxusage, 3) - self.assertEqual(db._setsession_sql, ('set datestyle',)) - - def test2_CloseConnection(self): - pool = PooledPg(0, 1, 0, False, None, None, False, - 'PooledPgTestDB', user='PooledPgTestUser') - db = pool.connection() - self.assert_(hasattr(db, '_con')) - db_con = db._con - from DBUtils.SteadyPg import SteadyPgConnection - self.assert_(isinstance(db_con, SteadyPgConnection)) - self.assert_(hasattr(pool, '_cache')) - self.assertEqual(pool._cache.qsize(), 0) - self.assertEqual(db.num_queries, 0) - db.query('select test') - self.assertEqual(db.num_queries, 1) - db.close() - self.assertRaises(InvalidConnection, getattr, db, 'num_queries') - db = pool.connection() - self.assert_(hasattr(db, 'dbname')) - self.assertEqual(db.dbname, 'PooledPgTestDB') - self.assert_(hasattr(db, 'user')) - self.assertEqual(db.user, 'PooledPgTestUser') - self.assertEqual(db.num_queries, 1) - db.query('select test') - self.assertEqual(db.num_queries, 2) - db = pool.connection() - self.assertEqual(pool._cache.qsize(), 1) - self.assertEqual(pool._cache.get(0), db_con) - - def test3_MinMaxCached(self): - pool = PooledPg(3) - self.assert_(hasattr(pool, '_cache')) - self.assertEqual(pool._cache.qsize(), 3) - cache = [] - for i in range(3): - cache.append(pool.connection()) - self.assertEqual(pool._cache.qsize(), 0) - for i in range(3): - cache.pop().close() - self.assertEqual(pool._cache.qsize(), 3) - for i in range(6): - cache.append(pool.connection()) - self.assertEqual(pool._cache.qsize(), 0) - for i in range(6): - cache.pop().close() - self.assertEqual(pool._cache.qsize(), 6) - pool = PooledPg(3, 4) - self.assert_(hasattr(pool, '_cache')) - self.assertEqual(pool._cache.qsize(), 3) - cache = [] - for i in range(3): - cache.append(pool.connection()) - self.assertEqual(pool._cache.qsize(), 0) - for i in range(3): - cache.pop().close() - self.assertEqual(pool._cache.qsize(), 3) - for i in range(6): - cache.append(pool.connection()) - self.assertEqual(pool._cache.qsize(), 0) - for i in range(6): - cache.pop().close() - self.assertEqual(pool._cache.qsize(), 4) - pool = PooledPg(3, 2) - self.assert_(hasattr(pool, '_cache')) - self.assertEqual(pool._cache.qsize(), 3) - cache = [] - for i in range(4): - cache.append(pool.connection()) - self.assertEqual(pool._cache.qsize(), 0) - for i in range(4): - cache.pop().close() - self.assertEqual(pool._cache.qsize(), 3) - pool = PooledPg(2, 5) - self.assert_(hasattr(pool, '_cache')) - self.assertEqual(pool._cache.qsize(), 2) - cache = [] - for i in range(10): - cache.append(pool.connection()) - self.assertEqual(pool._cache.qsize(), 0) - for i in range(10): - cache.pop().close() - self.assertEqual(pool._cache.qsize(), 5) - - def test4_MaxConnections(self): - from DBUtils.PooledPg import TooManyConnections - pool = PooledPg(1, 2, 3) - self.assertEqual(pool._cache.qsize(), 1) - cache = [] - for i in range(3): - cache.append(pool.connection()) - self.assertEqual(pool._cache.qsize(), 0) - self.assertRaises(TooManyConnections, pool.connection) - pool = PooledPg(0, 1, 1, False) - self.assertEqual(pool._blocking, 0) - self.assertEqual(pool._cache.qsize(), 0) - db = pool.connection() - self.assertEqual(pool._cache.qsize(), 0) - self.assertRaises(TooManyConnections, pool.connection) - pool = PooledPg(1, 2, 1) - self.assertEqual(pool._cache.qsize(), 1) - cache = [] - cache.append(pool.connection()) - self.assertEqual(pool._cache.qsize(), 0) - cache.append(pool.connection()) - self.assertEqual(pool._cache.qsize(), 0) - self.assertRaises(TooManyConnections, pool.connection) - pool = PooledPg(3, 2, 1, False) - self.assertEqual(pool._cache.qsize(), 3) - cache = [] - for i in range(3): - cache.append(pool.connection()) - self.assertEqual(pool._cache.qsize(), 0) - self.assertRaises(TooManyConnections, pool.connection) - pool = PooledPg(1, 1, 1, True) - self.assertEqual(pool._blocking, 1) - self.assertEqual(pool._cache.qsize(), 1) - db = pool.connection() - self.assertEqual(pool._cache.qsize(), 0) - def connection(): - pool.connection().query('set thread') - from threading import Thread - thread = Thread(target=connection) - thread.start() - thread.join(0.1) - self.assert_(thread.isAlive()) - self.assertEqual(pool._cache.qsize(), 0) - session = db._con.session - self.assertEqual(session, []) - del db - thread.join(0.1) - self.assert_(not thread.isAlive()) - self.assertEqual(pool._cache.qsize(), 1) - db = pool.connection() - self.assertEqual(pool._cache.qsize(), 0) - self.assertEqual(session, ['thread']) - - def test5_OneThreadTwoConnections(self): - pool = PooledPg(2) - db1 = pool.connection() - for i in range(5): - db1.query('select test') - db2 = pool.connection() - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - for i in range(7): - db2.query('select test') - self.assertEqual(db1.num_queries, 5) - self.assertEqual(db2.num_queries, 7) - del db1 - db1 = pool.connection() - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - self.assert_(hasattr(db1, 'query')) - for i in range(3): - db1.query('select test') - self.assertEqual(db1.num_queries, 8) - db2.query('select test') - self.assertEqual(db2.num_queries, 8) - - def test6_ThreeThreadsTwoConnections(self): - pool = PooledPg(2, 2, 2, True) - from Queue import Queue, Empty - queue = Queue(3) - def connection(): - try: - queue.put(pool.connection(), 1, 1) - except TypeError: - queue.put(pool.connection(), 1) - from threading import Thread - for i in range(3): - Thread(target=connection).start() - try: - db1 = queue.get(1, 1) - db2 = queue.get(1, 1) - except TypeError: - db1 = queue.get(1) - db2 = queue.get(1) - db1_con = db1._con - db2_con = db2._con - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1_con, db2_con) - try: - self.assertRaises(Empty, queue.get, 1, 0.1) - except TypeError: - self.assertRaises(Empty, queue.get, 0) - del db1 - try: - db1 = queue.get(1, 1) - except TypeError: - db1 = queue.get(1) - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - self.assertEqual(db1._con, db1_con) - - def test7_ResetTransaction(self): - pool = PooledPg(1) - db = pool.connection() - db.begin() - con = db._con - self.assert_(con._transaction) - db.query('select test') - self.assertEqual(con.num_queries, 1) - db.close() - self.assert_(pool.connection()._con is con) - self.assert_(not con._transaction) - self.assertEqual(con.session, ['begin', 'rollback']) - self.assertEqual(con.num_queries, 1) - pool = PooledPg(1, reset=1) - db = pool.connection() - db.begin() - con = db._con - self.assert_(con._transaction) - self.assertEqual(con.session, ['rollback', 'begin']) - db.query('select test') - self.assertEqual(con.num_queries, 1) - db.close() - self.assert_(pool.connection()._con is con) - self.assert_(not con._transaction) - self.assertEqual(con.session, - ['rollback', 'begin', 'rollback', 'rollback']) - self.assertEqual(con.num_queries, 1) - pool = PooledPg(1, reset=2) - db = pool.connection() - db.begin() - con = db._con - self.assert_(con._transaction) - self.assertEqual(con.session, ['begin']) - db.query('select test') - self.assertEqual(con.num_queries, 1) - db.close() - self.assert_(pool.connection()._con is con) - self.assert_(not con._transaction) - self.assertEqual(con.session, []) - self.assertEqual(con.num_queries, 0) - - -if __name__ == '__main__': - unittest.main() diff --git a/DBUtils/Tests/TestSimplePooledDB.py b/DBUtils/Tests/TestSimplePooledDB.py deleted file mode 100644 index 1e6203d..0000000 --- a/DBUtils/Tests/TestSimplePooledDB.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Test the SimplePooledDB module. - -Note: -We don't test performance here, so the test does not predicate -whether SimplePooledDB actually will help in improving performance or not. -We also do not test any real world DB-API 2 module, we just -mock the basic connection functionality of an arbitrary module. - -Copyright and credit info: - -* This test was contributed by Christoph Zwerschke - -""" - -__version__ = '1.1.1b1' - -import sys - -# This module also serves as a mock object for an arbitrary DB-API 2 module: - -dbModule = sys.modules[__name__] - -threadsafety = 1 - - -def connect(database, user): - return Connection(database, user) - - -class Connection: - - def __init__(self, database, user): - self.database = database - self.user = user - self.open_cursors = 0 - - def close(self): - self.open_cursors = 0 - - def cursor(self): - self.open_cursors += 1 - - -import unittest - -sys.path.insert(1, '../..') -from DBUtils import SimplePooledDB - - -def versionString(version): - """Create version string.""" - ver = map(str, version) - numbers, rest = ver[:ver[2] == '0' and 2 or 3], ver[3:] - return '.'.join(numbers) + '-'.join(rest) - - -class TestSimplePooledDB(unittest.TestCase): - - def my_dbpool(self, mythreadsafety, maxConnections): - global threadsafety - threadsafety = mythreadsafety - return SimplePooledDB.PooledDB(dbModule, maxConnections, - 'SimplePooledDBTestDB', 'SimplePooledDBTestUser') - - def test0_check_version(self): - from DBUtils import __version__ as DBUtilsVersion - self.assertEqual(DBUtilsVersion, __version__) - from DBUtils.Properties import version - self.assertEqual(versionString(version), __version__) - self.assertEqual(SimplePooledDB.__version__, __version__) - self.assertEqual(SimplePooledDB.PooledDB.version, __version__) - - def test1_no_threadsafety(self): - for threadsafety in (None, -1, 0, 4): - self.assertRaises(SimplePooledDB.NotSupportedError, - self.my_dbpool, threadsafety, 1) - - def test2_create_connection(self): - for threadsafety in (1, 2, 3): - dbpool = self.my_dbpool(threadsafety, 1) - db = dbpool.connection() - self.assert_(hasattr(db, 'cursor')) - self.assert_(hasattr(db, 'open_cursors')) - self.assertEqual(db.open_cursors, 0) - self.assert_(hasattr(db, 'database')) - self.assertEqual(db.database, 'SimplePooledDBTestDB') - self.assert_(hasattr(db, 'user')) - self.assertEqual(db.user, 'SimplePooledDBTestUser') - db.cursor() - self.assertEqual(db.open_cursors, 1) - - def test3_close_connection(self): - for threadsafety in (1, 2, 3): - dbpool = self.my_dbpool(threadsafety, 1) - db = dbpool.connection() - self.assertEqual(db.open_cursors, 0) - db.cursor() - self.assertEqual(db.open_cursors, 1) - db.close() - self.assert_(not hasattr(db, 'open_cursors')) - db = dbpool.connection() - self.assert_(hasattr(db, 'database')) - self.assertEqual(db.database, 'SimplePooledDBTestDB') - self.assert_(hasattr(db, 'user')) - self.assertEqual(db.user, 'SimplePooledDBTestUser') - self.assertEqual(db.open_cursors, 1) - db.cursor() - self.assertEqual(db.open_cursors, 2) - - def test4_two_connections(self): - for threadsafety in (1, 2, 3): - dbpool = self.my_dbpool(threadsafety, 2) - db1 = dbpool.connection() - for i in range(5): - db1.cursor() - db2 = dbpool.connection() - self.assertNotEqual(db1, db2) - for i in range(7): - db2.cursor() - self.assertEqual(db1.open_cursors, 5) - self.assertEqual(db2.open_cursors, 7) - db1.close() - db1 = dbpool.connection() - self.assertNotEqual(db1, db2) - self.assert_(hasattr(db1, 'cursor')) - for i in range(3): - db1.cursor() - self.assertEqual(db1.open_cursors, 8) - db2.cursor() - self.assertEqual(db2.open_cursors, 8) - - def test5_threadsafety_1(self): - dbpool = self.my_dbpool(1, 2) - from Queue import Queue, Empty - queue = Queue(3) - def connection(): - queue.put(dbpool.connection()) - from threading import Thread - thread1 = Thread(target=connection).start() - thread2 = Thread(target=connection).start() - thread3 = Thread(target=connection).start() - try: - db1 = queue.get(1, 1) - db2 = queue.get(1, 1) - except TypeError: - db1 = queue.get(1) - db2 = queue.get(1) - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - try: - self.assertRaises(Empty, queue.get, 1, 0.1) - except TypeError: - self.assertRaises(Empty, queue.get, 0) - db2.close() - try: - db3 = queue.get(1, 1) - except TypeError: - db3 = queue.get(1) - self.assertNotEqual(db1, db3) - self.assertNotEqual(db1._con, db3._con) - - def test6_threadsafety_2(self): - for threadsafety in (2, 3): - dbpool = self.my_dbpool(threadsafety, 2) - db1 = dbpool.connection() - db2 = dbpool.connection() - for i in xrange(100): - dbpool.connection().cursor() - self.assertEqual(db1.open_cursors, 50) - self.assertEqual(db2.open_cursors, 50) - - -if __name__ == '__main__': - unittest.main() diff --git a/DBUtils/Tests/TestSimplePooledPg.py b/DBUtils/Tests/TestSimplePooledPg.py deleted file mode 100644 index 723f83b..0000000 --- a/DBUtils/Tests/TestSimplePooledPg.py +++ /dev/null @@ -1,139 +0,0 @@ -"""Test the SimplePooledPg module. - -Note: -We don't test performance here, so the test does not predicate -whether SimplePooledPg actually will help in improving performance or not. - - -Copyright and credit info: - -* This test was contributed by Christoph Zwerschke - -""" - -__version__ = '1.1.1b1' - -import sys - -# This module also serves as a mock object for the pg API module: - -sys.modules['pg'] = sys.modules[__name__] - -class DB: - - def __init__(self, dbname, user): - self.dbname = dbname - self.user = user - self.num_queries = 0 - - def close(self): - self.num_queries = 0 - - def query(self): - self.num_queries += 1 - - -import unittest - -sys.path.insert(1, '../..') -from DBUtils import SimplePooledPg - - -class TestSimplePooledPg(unittest.TestCase): - - def my_dbpool(self, maxConnections): - return SimplePooledPg.PooledPg(maxConnections, - 'SimplePooledPgTestDB', 'SimplePooledPgTestUser') - - def test0_check_version(self): - from DBUtils import __version__ as DBUtilsVersion - self.assertEqual(DBUtilsVersion, __version__) - self.assertEqual(SimplePooledPg.__version__, __version__) - self.assertEqual(SimplePooledPg.PooledPg.version, __version__) - - def test1_create_connection(self): - dbpool = self.my_dbpool(1) - db = dbpool.connection() - self.assert_(hasattr(db, 'query')) - self.assert_(hasattr(db, 'num_queries')) - self.assertEqual(db.num_queries, 0) - self.assert_(hasattr(db, 'dbname')) - self.assertEqual(db.dbname, 'SimplePooledPgTestDB') - self.assert_(hasattr(db, 'user')) - self.assertEqual(db.user, 'SimplePooledPgTestUser') - db.query() - self.assertEqual(db.num_queries, 1) - - def test2_close_connection(self): - dbpool = self.my_dbpool(1) - db = dbpool.connection() - self.assertEqual(db.num_queries, 0) - db.query() - self.assertEqual(db.num_queries, 1) - db.close() - self.assert_(not hasattr(db, 'num_queries')) - db = dbpool.connection() - self.assert_(hasattr(db, 'dbname')) - self.assertEqual(db.dbname, 'SimplePooledPgTestDB') - self.assert_(hasattr(db, 'user')) - self.assertEqual(db.user, 'SimplePooledPgTestUser') - self.assertEqual(db.num_queries, 1) - db.query() - self.assertEqual(db.num_queries, 2) - - def test3_two_connections(self): - dbpool = self.my_dbpool(2) - db1 = dbpool.connection() - for i in range(5): - db1.query() - db2 = dbpool.connection() - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - for i in range(7): - db2.query() - self.assertEqual(db1.num_queries, 5) - self.assertEqual(db2.num_queries, 7) - db1.close() - db1 = dbpool.connection() - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - self.assert_(hasattr(db1, 'query')) - for i in range(3): - db1.query() - self.assertEqual(db1.num_queries, 8) - db2.query() - self.assertEqual(db2.num_queries, 8) - - def test4_threads(self): - dbpool = self.my_dbpool(2) - from Queue import Queue, Empty - queue = Queue(3) - def connection(): - queue.put(dbpool.connection()) - from threading import Thread - thread1 = Thread(target=connection).start() - thread2 = Thread(target=connection).start() - thread3 = Thread(target=connection).start() - try: - db1 = queue.get(1, 1) - db2 = queue.get(1, 1) - except TypeError: - db1 = queue.get(1) - db2 = queue.get(1) - self.assertNotEqual(db1, db2) - self.assertNotEqual(db1._con, db2._con) - try: - self.assertRaises(Empty, queue.get, 1, 0.1) - except TypeError: - self.assertRaises(Empty, queue.get, 0) - db2.close() - try: - db3 = queue.get(1, 1) - except TypeError: - db3 = queue.get(1) - self.assertNotEqual(db1, db3) - self.assertNotEqual(db1._con, db3._con) - - -if __name__ == '__main__': - unittest.main() diff --git a/DBUtils/Tests/TestSteadyDB.py b/DBUtils/Tests/TestSteadyDB.py deleted file mode 100644 index ddbe956..0000000 --- a/DBUtils/Tests/TestSteadyDB.py +++ /dev/null @@ -1,753 +0,0 @@ -"""Test the SteadyDB module. - -Note: -We do not test any real DB-API 2 module, but we just -mock the basic DB-API 2 connection functionality. - -Copyright and credit info: - -* This test was contributed by Christoph Zwerschke - -""" - -__version__ = '1.1.1b1' - -import sys - -# This module also serves as a mock object for the DB-API 2 module: - -dbapi = sys.modules[__name__] - -threadsafety = 2 - -class Error(StandardError): pass -class DatabaseError(Error): pass -class OperationalError(DatabaseError): pass -class InternalError(DatabaseError): pass -class ProgrammingError(DatabaseError): pass - - -def connect(database=None, user=None): - return Connection(database, user) - - -class Connection: - - has_ping = False - num_pings = 0 - - def __init__(self, database=None, user=None): - self.database = database - self.user = user - self.valid = False - if database == 'error': - raise OperationalError - self.open_cursors = 0 - self.num_uses = 0 - self.num_queries = 0 - self.num_pings = 0 - self.session = [] - self.valid = True - - def close(self): - if not self.valid: - raise InternalError - self.open_cursors = 0 - self.num_uses = 0 - self.num_queries = 0 - self.session = [] - self.valid = False - - def commit(self): - if not self.valid: - raise InternalError - self.session.append('commit') - - def rollback(self): - if not self.valid: - raise InternalError - self.session.append('rollback') - - def ping(self): - cls = self.__class__ - cls.num_pings += 1 - if not cls.has_ping: - raise AttributeError - if not self.valid: - raise OperationalError - - def cursor(self, name=None): - if not self.valid: - raise InternalError - return Cursor(self, name) - - -class Cursor: - - def __init__(self, con, name=None): - self.con = con - self.valid = False - if name == 'error': - raise OperationalError - self.result = None - self.inputsizes = [] - self.outputsizes = {} - con.open_cursors += 1 - self.valid = True - - def close(self): - if not self.valid: - raise InternalError - self.con.open_cursors -= 1 - self.valid = False - - def execute(self, operation): - if not self.valid or not self.con.valid: - raise InternalError - self.con.num_uses += 1 - if operation.startswith('select '): - self.con.num_queries += 1 - self.result = operation[7:] - elif operation.startswith('set '): - self.con.session.append(operation[4:]) - self.result = None - elif operation == 'get sizes': - self.result = (self.inputsizes, self.outputsizes) - self.inputsizes = [] - self.outputsizes = {} - else: - raise ProgrammingError - - def fetchone(self): - if not self.valid: - raise InternalError - result = self.result - self.result = None - return result - - def callproc(self, procname): - if not self.valid or not self.con.valid: - raise InternalError - self.con.num_uses += 1 - - def setinputsizes(self, sizes): - if not self.valid: - raise InternalError - self.inputsizes = sizes - - def setoutputsize(self, size, column=None): - if not self.valid: - raise InternalError - self.outputsizes[column] = size - - def __del__(self): - if self.valid: - self.close() - - -import unittest - -sys.path.insert(1, '../..') -from DBUtils.SteadyDB import connect as SteadyDBconnect -from DBUtils.SteadyDB import SteadyDBConnection, SteadyDBCursor - - -class TestSteadyDB(unittest.TestCase): - - def test00_CheckVersion(self): - from DBUtils import __version__ as DBUtilsVersion - self.assertEqual(DBUtilsVersion, __version__) - from DBUtils.SteadyDB import __version__ as SteadyDBVersion - self.assertEqual(SteadyDBVersion, __version__) - self.assertEqual(SteadyDBConnection.version, __version__) - - def test01_MockedConnection(self): - db = connect('SteadyDBTestDB', - user='SteadyDBTestUser') - db.__class__.has_ping = False - db.__class__.num_pings = 0 - self.assert_(hasattr(db, 'database')) - self.assertEqual(db.database, 'SteadyDBTestDB') - self.assert_(hasattr(db, 'user')) - self.assertEqual(db.user, 'SteadyDBTestUser') - self.assert_(hasattr(db, 'cursor')) - self.assert_(hasattr(db, 'close')) - self.assert_(hasattr(db, 'open_cursors')) - self.assert_(hasattr(db, 'num_uses')) - self.assert_(hasattr(db, 'num_queries')) - self.assert_(hasattr(db, 'session')) - self.assert_(hasattr(db, 'valid')) - self.assert_(db.valid) - self.assertEqual(db.open_cursors, 0) - for i in range(3): - cursor = db.cursor() - self.assertEqual(db.open_cursors, 1) - cursor.close() - self.assertEqual(db.open_cursors, 0) - cursor = [] - for i in range(3): - cursor.append(db.cursor()) - self.assertEqual(db.open_cursors, i + 1) - del cursor - self.assertEqual(db.open_cursors, 0) - cursor = db.cursor() - self.assert_(hasattr(cursor, 'execute')) - self.assert_(hasattr(cursor, 'fetchone')) - self.assert_(hasattr(cursor, 'callproc')) - self.assert_(hasattr(cursor, 'close')) - self.assert_(hasattr(cursor, 'valid')) - self.assert_(cursor.valid) - self.assertEqual(db.open_cursors, 1) - for i in range(3): - self.assertEqual(db.num_uses, i) - self.assertEqual(db.num_queries, i) - cursor.execute('select test%d' % i) - self.assertEqual(cursor.fetchone(), 'test%d' % i) - self.assert_(cursor.valid) - self.assertEqual(db.open_cursors, 1) - for i in range(4): - cursor.callproc('test') - cursor.close() - self.assert_(not cursor.valid) - self.assertEqual(db.open_cursors, 0) - self.assertEqual(db.num_uses, 7) - self.assertEqual(db.num_queries, 3) - self.assertRaises(InternalError, cursor.close) - self.assertRaises(InternalError, cursor.execute, 'select test') - self.assert_(db.valid) - self.assert_(not db.__class__.has_ping) - self.assertEqual(db.__class__.num_pings, 0) - self.assertRaises(AttributeError, db.ping) - self.assertEqual(db.__class__.num_pings, 1) - db.__class__.has_ping = True - self.assert_(db.ping() is None) - self.assertEqual(db.__class__.num_pings, 2) - db.close() - self.assert_(not db.valid) - self.assertEqual(db.num_uses, 0) - self.assertEqual(db.num_queries, 0) - self.assertRaises(InternalError, db.close) - self.assertRaises(InternalError, db.cursor) - self.assertRaises(OperationalError, db.ping) - self.assertEqual(db.__class__.num_pings, 3) - db.__class__.has_ping = False - db.__class__.num_pings = 0 - - def test02_BrokenConnection(self): - self.assertRaises(TypeError, SteadyDBConnection, None) - self.assertRaises(TypeError, SteadyDBCursor, None) - db = SteadyDBconnect(dbapi, database='ok') - for i in range(3): - db.close() - del db - self.assertRaises(OperationalError, SteadyDBconnect, - dbapi, database='error') - db = SteadyDBconnect(dbapi, database='ok') - cursor = db.cursor() - for i in range(3): - cursor.close() - cursor = db.cursor('ok') - for i in range(3): - cursor.close() - self.assertRaises(OperationalError, db.cursor, 'error') - - def test03_Close(self): - for closeable in (False, True): - db = SteadyDBconnect(dbapi, closeable=closeable) - self.assert_(db._con.valid) - db.close() - self.assert_(closeable ^ db._con.valid) - db.close() - self.assert_(closeable ^ db._con.valid) - db._close() - self.assert_(not db._con.valid) - db._close() - self.assert_(not db._con.valid) - - def test04_Connection(self): - db = SteadyDBconnect(dbapi, 0, None, None, None, True, - 'SteadyDBTestDB', user='SteadyDBTestUser') - self.assert_(isinstance(db, SteadyDBConnection)) - self.assert_(hasattr(db, '_con')) - self.assert_(hasattr(db, '_usage')) - self.assertEqual(db._usage, 0) - self.assert_(hasattr(db._con, 'valid')) - self.assert_(db._con.valid) - self.assert_(hasattr(db._con, 'cursor')) - self.assert_(hasattr(db._con, 'close')) - self.assert_(hasattr(db._con, 'open_cursors')) - self.assert_(hasattr(db._con, 'num_uses')) - self.assert_(hasattr(db._con, 'num_queries')) - self.assert_(hasattr(db._con, 'session')) - self.assert_(hasattr(db._con, 'database')) - self.assertEqual(db._con.database, 'SteadyDBTestDB') - self.assert_(hasattr(db._con, 'user')) - self.assertEqual(db._con.user, 'SteadyDBTestUser') - self.assert_(hasattr(db, 'cursor')) - self.assert_(hasattr(db, 'close')) - self.assertEqual(db._con.open_cursors, 0) - for i in range(3): - cursor = db.cursor() - self.assertEqual(db._con.open_cursors, 1) - cursor.close() - self.assertEqual(db._con.open_cursors, 0) - cursor = [] - for i in range(3): - cursor.append(db.cursor()) - self.assertEqual(db._con.open_cursors, i + 1) - del cursor - self.assertEqual(db._con.open_cursors, 0) - cursor = db.cursor() - self.assert_(hasattr(cursor, 'execute')) - self.assert_(hasattr(cursor, 'fetchone')) - self.assert_(hasattr(cursor, 'callproc')) - self.assert_(hasattr(cursor, 'close')) - self.assert_(hasattr(cursor, 'valid')) - self.assert_(cursor.valid) - self.assertEqual(db._con.open_cursors, 1) - for i in range(3): - self.assertEqual(db._usage, i) - self.assertEqual(db._con.num_uses, i) - self.assertEqual(db._con.num_queries, i) - cursor.execute('select test%d' % i) - self.assertEqual(cursor.fetchone(), 'test%d' % i) - self.assert_(cursor.valid) - self.assertEqual(db._con.open_cursors, 1) - for i in range(4): - cursor.callproc('test') - cursor.close() - self.assert_(not cursor.valid) - self.assertEqual(db._con.open_cursors, 0) - self.assertEqual(db._usage, 7) - self.assertEqual(db._con.num_uses, 7) - self.assertEqual(db._con.num_queries, 3) - cursor.close() - cursor.execute('select test8') - self.assert_(cursor.valid) - self.assertEqual(db._con.open_cursors, 1) - self.assertEqual(cursor.fetchone(), 'test8') - self.assertEqual(db._usage, 8) - self.assertEqual(db._con.num_uses, 8) - self.assertEqual(db._con.num_queries, 4) - self.assert_(db._con.valid) - db.close() - self.assert_(not db._con.valid) - self.assertEqual(db._con.open_cursors, 0) - self.assertEqual(db._usage, 8) - self.assertEqual(db._con.num_uses, 0) - self.assertEqual(db._con.num_queries, 0) - self.assertRaises(InternalError, db._con.close) - db.close() - self.assertRaises(InternalError, db._con.cursor) - cursor = db.cursor() - self.assert_(db._con.valid) - cursor.execute('select test11') - self.assertEqual(cursor.fetchone(), 'test11') - cursor.execute('select test12') - self.assertEqual(cursor.fetchone(), 'test12') - cursor.callproc('test') - self.assertEqual(db._usage, 3) - self.assertEqual(db._con.num_uses, 3) - self.assertEqual(db._con.num_queries, 2) - cursor2 = db.cursor() - self.assertEqual(db._con.open_cursors, 2) - cursor2.execute('select test13') - self.assertEqual(cursor2.fetchone(), 'test13') - self.assertEqual(db._con.num_queries, 3) - db.close() - self.assertEqual(db._con.open_cursors, 0) - self.assertEqual(db._con.num_queries, 0) - cursor = db.cursor() - self.assert_(cursor.valid) - cursor.callproc('test') - cursor._cursor.valid = False - self.assert_(not cursor.valid) - self.assertRaises(InternalError, cursor._cursor.callproc, 'test') - cursor.callproc('test') - self.assert_(cursor.valid) - cursor._cursor.callproc('test') - self.assertEqual(db._usage, 2) - self.assertEqual(db._con.num_uses, 3) - db._con.valid = cursor._cursor.valid = False - cursor.callproc('test') - self.assert_(cursor.valid) - self.assertEqual(db._usage, 1) - self.assertEqual(db._con.num_uses, 1) - cursor.execute('set doit') - db.commit() - cursor.execute('set dont') - db.rollback() - self.assertEqual(db._con.session, - ['doit', 'commit', 'dont', 'rollback']) - - def test05_ConnectionCreatorFunction(self): - db1 = SteadyDBconnect(dbapi, 0, None, None, None, True, - 'SteadyDBTestDB', user='SteadyDBTestUser') - db2 = SteadyDBconnect(connect, 0, None, None, None, True, - 'SteadyDBTestDB', user='SteadyDBTestUser') - self.assertEqual(db1.dbapi(), db2.dbapi()) - self.assertEqual(db1.threadsafety(), db2.threadsafety()) - self.assertEqual(db1._creator, db2._creator) - self.assertEqual(db1._args, db2._args) - self.assertEqual(db1._kwargs, db2._kwargs) - db2.close() - db1.close() - - def test06_ConnectionMaxUsage(self): - db = SteadyDBconnect(dbapi, 10) - cursor = db.cursor() - for i in range(100): - cursor.execute('select test%d' % i) - r = cursor.fetchone() - self.assertEqual(r, 'test%d' % i) - self.assert_(db._con.valid) - j = i % 10 + 1 - self.assertEqual(db._usage, j) - self.assertEqual(db._con.num_uses, j) - self.assertEqual(db._con.num_queries, j) - self.assertEqual(db._con.open_cursors, 1) - for i in range(100): - cursor.callproc('test') - self.assert_(db._con.valid) - j = i % 10 + 1 - self.assertEqual(db._usage, j) - self.assertEqual(db._con.num_uses, j) - self.assertEqual(db._con.num_queries, 0) - for i in range(10): - if i == 7: - db._con.valid = cursor._cursor.valid = False - cursor.execute('select test%d' % i) - r = cursor.fetchone() - self.assertEqual(r, 'test%d' % i) - j = i % 7 + 1 - self.assertEqual(db._usage, j) - self.assertEqual(db._con.num_uses, j) - self.assertEqual(db._con.num_queries, j) - for i in range(10): - if i == 5: - db._con.valid = cursor._cursor.valid = False - cursor.callproc('test') - j = (i + (i < 5 and 3 or -5)) % 10 + 1 - self.assertEqual(db._usage, j) - self.assertEqual(db._con.num_uses, j) - j = i < 5 and 3 or 0 - self.assertEqual(db._con.num_queries, j) - db.close() - cursor.execute('select test1') - self.assertEqual(cursor.fetchone(), 'test1') - self.assertEqual(db._usage, 1) - self.assertEqual(db._con.num_uses, 1) - self.assertEqual(db._con.num_queries, 1) - - def test07_ConnectionSetSession(self): - db = SteadyDBconnect(dbapi, 3, ('set time zone', 'set datestyle')) - self.assert_(hasattr(db, '_usage')) - self.assertEqual(db._usage, 0) - self.assert_(hasattr(db._con, 'open_cursors')) - self.assertEqual(db._con.open_cursors, 0) - self.assert_(hasattr(db._con, 'num_uses')) - self.assertEqual(db._con.num_uses, 2) - self.assert_(hasattr(db._con, 'num_queries')) - self.assertEqual(db._con.num_queries, 0) - self.assert_(hasattr(db._con, 'session')) - self.assertEqual(tuple(db._con.session), ('time zone', 'datestyle')) - for i in range(11): - db.cursor().execute('select test') - self.assertEqual(db._con.open_cursors, 0) - self.assertEqual(db._usage, 2) - self.assertEqual(db._con.num_uses, 4) - self.assertEqual(db._con.num_queries, 2) - self.assertEqual(db._con.session, ['time zone', 'datestyle']) - db.cursor().execute('set test') - self.assertEqual(db._con.open_cursors, 0) - self.assertEqual(db._usage, 3) - self.assertEqual(db._con.num_uses, 5) - self.assertEqual(db._con.num_queries, 2) - self.assertEqual(db._con.session, ['time zone', 'datestyle', 'test']) - db.cursor().execute('select test') - self.assertEqual(db._con.open_cursors, 0) - self.assertEqual(db._usage, 1) - self.assertEqual(db._con.num_uses, 3) - self.assertEqual(db._con.num_queries, 1) - self.assertEqual(db._con.session, ['time zone', 'datestyle']) - db.cursor().execute('set test') - self.assertEqual(db._con.open_cursors, 0) - self.assertEqual(db._usage, 2) - self.assertEqual(db._con.num_uses, 4) - self.assertEqual(db._con.num_queries, 1) - self.assertEqual(db._con.session, ['time zone', 'datestyle', 'test']) - db.cursor().execute('select test') - self.assertEqual(db._con.open_cursors, 0) - self.assertEqual(db._usage, 3) - self.assertEqual(db._con.num_uses, 5) - self.assertEqual(db._con.num_queries, 2) - self.assertEqual(db._con.session, ['time zone', 'datestyle', 'test']) - db.close() - db.cursor().execute('set test') - self.assertEqual(db._con.open_cursors, 0) - self.assertEqual(db._usage, 1) - self.assertEqual(db._con.num_uses, 3) - self.assertEqual(db._con.num_queries, 0) - self.assertEqual(db._con.session, ['time zone', 'datestyle', 'test']) - db.close() - db.cursor().execute('select test') - self.assertEqual(db._con.open_cursors, 0) - self.assertEqual(db._usage, 1) - self.assertEqual(db._con.num_uses, 3) - self.assertEqual(db._con.num_queries, 1) - self.assertEqual(db._con.session, ['time zone', 'datestyle']) - - def test08_ConnectionFailures(self): - db = SteadyDBconnect(dbapi) - db.close() - db.cursor() - db = SteadyDBconnect(dbapi, failures=InternalError) - db.close() - db.cursor() - db = SteadyDBconnect(dbapi, failures=OperationalError) - db.close() - self.assertRaises(InternalError, db.cursor) - db = SteadyDBconnect(dbapi, - failures=(OperationalError, InternalError)) - db.close() - db.cursor() - - def test09_ConnectionFailureError(self): - db = SteadyDBconnect(dbapi) - cursor = db.cursor() - db.close() - cursor.execute('select test') - cursor = db.cursor() - db.close() - self.assertRaises(ProgrammingError, cursor.execute, 'error') - - def test10_ConnectionSetSizes(self): - db = SteadyDBconnect(dbapi) - cursor = db.cursor() - cursor.execute('get sizes') - result = cursor.fetchone() - self.assertEqual(result, ([], {})) - cursor.setinputsizes([7, 42, 6]) - cursor.setoutputsize(9) - cursor.setoutputsize(15, 3) - cursor.setoutputsize(42, 7) - cursor.execute('get sizes') - result = cursor.fetchone() - self.assertEqual(result, ([7, 42, 6], {None: 9, 3: 15, 7: 42})) - cursor.execute('get sizes') - result = cursor.fetchone() - self.assertEqual(result, ([], {})) - cursor.setinputsizes([6, 42, 7]) - cursor.setoutputsize(7) - cursor.setoutputsize(15, 3) - cursor.setoutputsize(42, 9) - db.close() - cursor.execute('get sizes') - result = cursor.fetchone() - self.assertEqual(result, ([6, 42, 7], {None: 7, 3: 15, 9: 42})) - - def test11_ConnectionPingCheck(self): - Connection.has_ping = False - Connection.num_pings = 0 - db = SteadyDBconnect(dbapi) - db.cursor().execute('select test') - self.assertEqual(Connection.num_pings, 0) - db.close() - db.cursor().execute('select test') - self.assertEqual(Connection.num_pings, 0) - self.assert_(db._ping_check() is None) - self.assertEqual(Connection.num_pings, 1) - db = SteadyDBconnect(dbapi, ping=7) - db.cursor().execute('select test') - self.assertEqual(Connection.num_pings, 2) - db.close() - db.cursor().execute('select test') - self.assertEqual(Connection.num_pings, 2) - self.assert_(db._ping_check() is None) - self.assertEqual(Connection.num_pings, 2) - Connection.has_ping = True - db = SteadyDBconnect(dbapi) - db.cursor().execute('select test') - self.assertEqual(Connection.num_pings, 2) - db.close() - db.cursor().execute('select test') - self.assertEqual(Connection.num_pings, 2) - self.assert_(db._ping_check()) - self.assertEqual(Connection.num_pings, 3) - db = SteadyDBconnect(dbapi, ping=1) - db.cursor().execute('select test') - self.assertEqual(Connection.num_pings, 3) - db.close() - db.cursor().execute('select test') - self.assertEqual(Connection.num_pings, 3) - self.assert_(db._ping_check()) - self.assertEqual(Connection.num_pings, 4) - db.close() - self.assert_(db._ping_check()) - self.assertEqual(Connection.num_pings, 5) - db = SteadyDBconnect(dbapi, ping=7) - db.cursor().execute('select test') - self.assertEqual(Connection.num_pings, 7) - db.close() - db.cursor().execute('select test') - self.assertEqual(Connection.num_pings, 9) - db = SteadyDBconnect(dbapi, ping=3) - self.assertEqual(Connection.num_pings, 9) - db.cursor() - self.assertEqual(Connection.num_pings, 10) - db.close() - cursor = db.cursor() - self.assertEqual(Connection.num_pings, 11) - cursor.execute('select test') - self.assertEqual(Connection.num_pings, 11) - db = SteadyDBconnect(dbapi, ping=5) - self.assertEqual(Connection.num_pings, 11) - db.cursor() - self.assertEqual(Connection.num_pings, 11) - db.close() - cursor = db.cursor() - self.assertEqual(Connection.num_pings, 11) - cursor.execute('select test') - self.assertEqual(Connection.num_pings, 12) - db.close() - cursor = db.cursor() - self.assertEqual(Connection.num_pings, 12) - cursor.execute('select test') - self.assertEqual(Connection.num_pings, 13) - db = SteadyDBconnect(dbapi, ping=7) - self.assertEqual(Connection.num_pings, 13) - db.cursor() - self.assertEqual(Connection.num_pings, 14) - db.close() - cursor = db.cursor() - self.assertEqual(Connection.num_pings, 15) - cursor.execute('select test') - self.assertEqual(Connection.num_pings, 16) - db.close() - cursor = db.cursor() - self.assertEqual(Connection.num_pings, 17) - cursor.execute('select test') - self.assertEqual(Connection.num_pings, 18) - db.close() - cursor.execute('select test') - self.assertEqual(Connection.num_pings, 20) - Connection.has_ping = False - Connection.num_pings = 0 - - def test12_BeginTransaction(self): - db = SteadyDBconnect(dbapi, database='ok') - cursor = db.cursor() - cursor.close() - cursor.execute('select test12') - self.assertEqual(cursor.fetchone(), 'test12') - db.begin() - cursor = db.cursor() - cursor.close() - self.assertRaises(InternalError, cursor.execute, 'select test12') - cursor.execute('select test12') - self.assertEqual(cursor.fetchone(), 'test12') - db.close() - db.begin() - self.assertRaises(InternalError, cursor.execute, 'select test12') - cursor.execute('select test12') - self.assertEqual(cursor.fetchone(), 'test12') - db.begin() - self.assertRaises(ProgrammingError, cursor.execute, 'error') - cursor.close() - cursor.execute('select test12') - self.assertEqual(cursor.fetchone(), 'test12') - - def test13_WithBeginExtension(self): - db = SteadyDBconnect(dbapi, database='ok') - db._con._begin_called_with = None - def begin(a, b=None, c=7): - db._con._begin_called_with = (a, b, c) - db._con.begin = begin - db.begin(42, 6) - cursor = db.cursor() - cursor.execute('select test13') - self.assertEqual(cursor.fetchone(), 'test13') - self.assertEqual(db._con._begin_called_with, (42, 6, 7)) - - def test14_CancelTransaction(self): - db = SteadyDBconnect(dbapi, database='ok') - cursor = db.cursor() - db.begin() - cursor.execute('select test14') - self.assertEqual(cursor.fetchone(), 'test14') - db.cancel() - cursor.execute('select test14') - self.assertEqual(cursor.fetchone(), 'test14') - - def test15_WithCancelExtension(self): - db = SteadyDBconnect(dbapi, database='ok') - db._con._cancel_called = None - def cancel(): - db._con._cancel_called = 'yes' - db._con.cancel = cancel - db.begin() - cursor = db.cursor() - cursor.execute('select test15') - self.assertEqual(cursor.fetchone(), 'test15') - db.cancel() - self.assertEqual(db._con._cancel_called, 'yes') - - def test16_ResetTransaction(self): - db = SteadyDBconnect(dbapi, database='ok') - db.begin() - self.assert_(not db._con.session) - db.close() - self.assert_(not db._con.session) - db = SteadyDBconnect(dbapi, database='ok', closeable=False) - db.begin() - self.assert_(not db._con.session) - db.close() - self.assertEqual(db._con.session, ['rollback']) - - def test17_CommitError(self): - db = SteadyDBconnect(dbapi, database='ok') - db.begin() - self.assert_(not db._con.session) - self.assert_(db._con.valid) - db.commit() - self.assertEqual(db._con.session, ['commit']) - self.assert_(db._con.valid) - db.begin() - db._con.valid = False - con = db._con - self.assertRaises(InternalError, db.commit) - self.assert_(not db._con.session) - self.assert_(db._con.valid) - self.assert_(con is not db._con) - db.begin() - self.assert_(not db._con.session) - self.assert_(db._con.valid) - db.commit() - self.assertEqual(db._con.session, ['commit']) - self.assert_(db._con.valid) - - def test18_RollbackError(self): - db = SteadyDBconnect(dbapi, database='ok') - db.begin() - self.assert_(not db._con.session) - self.assert_(db._con.valid) - db.rollback() - self.assertEqual(db._con.session, ['rollback']) - self.assert_(db._con.valid) - db.begin() - db._con.valid = False - con = db._con - self.assertRaises(InternalError, db.rollback) - self.assert_(not db._con.session) - self.assert_(db._con.valid) - self.assert_(con is not db._con) - db.begin() - self.assert_(not db._con.session) - self.assert_(db._con.valid) - db.rollback() - self.assertEqual(db._con.session, ['rollback']) - self.assert_(db._con.valid) - - -if __name__ == '__main__': - unittest.main() diff --git a/DBUtils/Tests/TestSteadyPg.py b/DBUtils/Tests/TestSteadyPg.py deleted file mode 100644 index ee871f3..0000000 --- a/DBUtils/Tests/TestSteadyPg.py +++ /dev/null @@ -1,403 +0,0 @@ -"""Test the SteadyPg module. - -Note: -We do not test the real PyGreSQL module, but we just -mock the basic connection functionality of that module. -We assume that the PyGreSQL module will detect lost -connections correctly and set the status flag accordingly. - -Copyright and credit info: - -* This test was contributed by Christoph Zwerschke - -""" - -__version__ = '1.1.1b1' - -import sys - -# This module also serves as a mock object for the pg API module: - -sys.modules['pg'] = sys.modules[__name__] - -class Error(StandardError): pass -class DatabaseError(Error): pass -class InternalError(DatabaseError): pass -class ProgrammingError(DatabaseError): pass - - -def connect(*args, **kwargs): - return pgConnection(*args, **kwargs) - - -class pgConnection: - """The underlying pg API connection class.""" - - def __init__(self, dbname=None, user=None): - self.db = dbname - self.user = user - self.num_queries = 0 - self.session = [] - if dbname == 'error': - self.status = False - self.valid = False - raise InternalError - self.status = True - self.valid = True - - def close(self): - if not self.valid: - raise InternalError - self.num_queries = 0 - self.session = [] - self.status = False - self.valid = False - - def reset(self): - self.num_queries = 0 - self.session = [] - self.status = True - self.valid = True - - def query(self, qstr): - if not self.valid: - raise InternalError - if qstr in ('begin', 'end', 'commit', 'rollback'): - self.session.append(qstr) - elif qstr.startswith('select '): - self.num_queries += 1 - return qstr[7:] - elif qstr.startswith('set '): - self.session.append(qstr[4:]) - else: - raise ProgrammingError - - -class DB: - """Wrapper class for the pg API connection class.""" - - def __init__(self, *args, **kw): - self.db = connect(*args, **kw) - self.dbname = self.db.db - self.__args = args, kw - - def __getattr__(self, name): - if self.db: - return getattr(self.db, name) - else: - raise AttributeError - - def close(self): - if self.db: - self.db.close() - self.db = None - else: - raise InternalError - - def reopen(self): - if self.db: - self.close() - try: - self.db = connect(*self.__args[0], **self.__args[1]) - except Exception: - self.db = None - raise - - def query(self, qstr): - if not self.db: - raise InternalError - return self.db.query(qstr) - - def get_tables(self): - if not self.db: - raise InternalError - return 'test' - - -import unittest - -sys.path.insert(1, '../..') -from DBUtils.SteadyPg import SteadyPgConnection - - -class TestSteadyPg(unittest.TestCase): - - def test0_CheckVersion(self): - from DBUtils import __version__ as DBUtilsVersion - self.assertEqual(DBUtilsVersion, __version__) - from DBUtils.SteadyPg import __version__ as SteadyPgVersion - self.assertEqual(SteadyPgVersion, __version__) - self.assertEqual(SteadyPgConnection.version, __version__) - - def test1_MockedConnection(self): - PgConnection = DB - db = PgConnection('SteadyPgTestDB', - user='SteadyPgTestUser') - self.assert_(hasattr(db, 'db')) - self.assert_(hasattr(db.db, 'status')) - self.assert_(db.db.status) - self.assert_(hasattr(db.db, 'query')) - self.assert_(hasattr(db.db, 'close')) - self.assert_(not hasattr(db.db, 'reopen')) - self.assert_(hasattr(db, 'reset')) - self.assert_(hasattr(db.db, 'num_queries')) - self.assert_(hasattr(db.db, 'session')) - self.assert_(not hasattr(db.db, 'get_tables')) - self.assert_(hasattr(db.db, 'db')) - self.assertEqual(db.db.db, 'SteadyPgTestDB') - self.assert_(hasattr(db.db, 'user')) - self.assertEqual(db.db.user, 'SteadyPgTestUser') - self.assert_(hasattr(db, 'query')) - self.assert_(hasattr(db, 'close')) - self.assert_(hasattr(db, 'reopen')) - self.assert_(hasattr(db, 'reset')) - self.assert_(hasattr(db, 'num_queries')) - self.assert_(hasattr(db, 'session')) - self.assert_(hasattr(db, 'get_tables')) - self.assert_(hasattr(db, 'dbname')) - self.assertEqual(db.dbname, 'SteadyPgTestDB') - self.assert_(hasattr(db, 'user')) - self.assertEqual(db.user, 'SteadyPgTestUser') - for i in range(3): - self.assertEqual(db.num_queries, i) - self.assertEqual(db.query('select test%d' % i), - 'test%d' % i) - self.assert_(db.db.status) - db.reopen() - self.assert_(db.db.status) - self.assertEqual(db.num_queries, 0) - self.assertEqual(db.query('select test4'), 'test4') - self.assertEqual(db.get_tables(), 'test') - db.close() - try: - status = db.db.status - except AttributeError: - status = False - self.assert_(not status) - self.assertRaises(InternalError, db.close) - self.assertRaises(InternalError, db.query, 'select test') - self.assertRaises(InternalError, db.get_tables) - - def test2_BrokenConnection(self): - self.assertRaises(TypeError, SteadyPgConnection, 'wrong') - db = SteadyPgConnection(dbname='ok') - InternalError = sys.modules[db._con.__module__].InternalError - for i in range(3): - db.close() - del db - self.assertRaises(InternalError, SteadyPgConnection, dbname='error') - - def test3_Close(self): - for closeable in (False, True): - db = SteadyPgConnection(closeable=closeable) - self.assert_(db._con.db and db._con.valid) - db.close() - self.assert_(closeable ^ - (db._con.db is not None and db._con.valid)) - db.close() - self.assert_(closeable ^ - (db._con.db is not None and db._con.valid)) - db._close() - self.assert_(not db._con.db or not db._con.valid) - db._close() - self.assert_(not db._con.db or not db._con.valid) - - def test4_Connection(self): - db = SteadyPgConnection(0, None, 1, - 'SteadyPgTestDB', user='SteadyPgTestUser') - self.assert_(hasattr(db, 'db')) - self.assert_(hasattr(db, '_con')) - self.assertEqual(db.db, db._con.db) - self.assert_(hasattr(db, '_usage')) - self.assertEqual(db._usage, 0) - self.assert_(hasattr(db.db, 'status')) - self.assert_(db.db.status) - self.assert_(hasattr(db.db, 'query')) - self.assert_(hasattr(db.db, 'close')) - self.assert_(not hasattr(db.db, 'reopen')) - self.assert_(hasattr(db.db, 'reset')) - self.assert_(hasattr(db.db, 'num_queries')) - self.assert_(hasattr(db.db, 'session')) - self.assert_(hasattr(db.db, 'db')) - self.assertEqual(db.db.db, 'SteadyPgTestDB') - self.assert_(hasattr(db.db, 'user')) - self.assertEqual(db.db.user, 'SteadyPgTestUser') - self.assert_(not hasattr(db.db, 'get_tables')) - self.assert_(hasattr(db, 'query')) - self.assert_(hasattr(db, 'close')) - self.assert_(hasattr(db, 'reopen')) - self.assert_(hasattr(db, 'reset')) - self.assert_(hasattr(db, 'num_queries')) - self.assert_(hasattr(db, 'session')) - self.assert_(hasattr(db, 'dbname')) - self.assertEqual(db.dbname, 'SteadyPgTestDB') - self.assert_(hasattr(db, 'user')) - self.assertEqual(db.user, 'SteadyPgTestUser') - self.assert_(hasattr(db, 'get_tables')) - for i in range(3): - self.assertEqual(db._usage, i) - self.assertEqual(db.num_queries, i) - self.assertEqual(db.query('select test%d' % i), - 'test%d' % i) - self.assert_(db.db.status) - self.assertEqual(db.get_tables(), 'test') - self.assert_(db.db.status) - self.assertEqual(db._usage, 4) - self.assertEqual(db.num_queries, 3) - db.reopen() - self.assert_(db.db.status) - self.assertEqual(db._usage, 0) - self.assertEqual(db.num_queries, 0) - self.assertEqual(db.query('select test'), 'test') - self.assert_(db.db.status) - self.assert_(hasattr(db._con, 'status')) - self.assert_(db._con.status) - self.assert_(hasattr(db._con, 'close')) - self.assert_(hasattr(db._con, 'query')) - db.close() - try: - status = db.db.status - except AttributeError: - status = False - self.assert_(not status) - self.assert_(hasattr(db._con, 'close')) - self.assert_(hasattr(db._con, 'query')) - InternalError = sys.modules[db._con.__module__].InternalError - self.assertRaises(InternalError, db._con.close) - self.assertRaises(InternalError, db._con.query, 'select test') - self.assertEqual(db.query('select test'), 'test') - self.assert_(db.db.status) - self.assertEqual(db._usage, 1) - self.assertEqual(db.num_queries, 1) - db.db.status = False - self.assert_(not db.db.status) - self.assertEqual(db.query('select test'), 'test') - self.assert_(db.db.status) - self.assertEqual(db._usage, 1) - self.assertEqual(db.num_queries, 1) - db.db.status = False - self.assert_(not db.db.status) - self.assertEqual(db.get_tables(), 'test') - self.assert_(db.db.status) - self.assertEqual(db._usage, 1) - self.assertEqual(db.num_queries, 0) - - def test5_ConnectionMaxUsage(self): - db = SteadyPgConnection(10) - for i in range(100): - r = db.query('select test%d' % i) - self.assertEqual(r, 'test%d' % i) - self.assert_(db.db.status) - j = i % 10 + 1 - self.assertEqual(db._usage, j) - self.assertEqual(db.num_queries, j) - for i in range(100): - r = db.get_tables() - self.assertEqual(r, 'test') - self.assert_(db.db.status) - j = i % 10 + 1 - self.assertEqual(db._usage, j) - self.assertEqual(db.num_queries, 0) - for i in range(10): - if i == 7: - db.db.status = False - r = db.query('select test%d' % i) - self.assertEqual(r, 'test%d' % i) - j = i % 7 + 1 - self.assertEqual(db._usage, j) - self.assertEqual(db.num_queries, j) - for i in range(10): - if i == 5: - db.db.status = False - r = db.get_tables() - self.assertEqual(r, 'test') - j = (i + (i < 5 and 3 or -5)) % 10 + 1 - self.assertEqual(db._usage, j) - j = i < 5 and 3 or 0 - self.assertEqual(db.num_queries, j) - db.close() - self.assertEqual(db.query('select test1'), 'test1') - self.assertEqual(db._usage, 1) - self.assertEqual(db.num_queries, 1) - db.reopen() - self.assertEqual(db._usage, 0) - self.assertEqual(db.num_queries, 0) - self.assertEqual(db.query('select test2'), 'test2') - self.assertEqual(db._usage, 1) - self.assertEqual(db.num_queries, 1) - - def test6_ConnectionSetSession(self): - db = SteadyPgConnection(3, ('set time zone', 'set datestyle')) - self.assert_(hasattr(db, 'num_queries')) - self.assertEqual(db.num_queries, 0) - self.assert_(hasattr(db, 'session')) - self.assertEqual(tuple(db.session), ('time zone', 'datestyle')) - for i in range(11): - db.query('select test') - self.assertEqual(db.num_queries, 2) - self.assertEqual(db.session, ['time zone', 'datestyle']) - db.query('set test') - self.assertEqual(db.num_queries, 2) - self.assertEqual(db.session, ['time zone', 'datestyle', 'test']) - db.query('select test') - self.assertEqual(db.num_queries, 1) - self.assertEqual(db.session, ['time zone', 'datestyle']) - db.close() - db.query('set test') - self.assertEqual(db.num_queries, 0) - self.assertEqual(db.session, ['time zone', 'datestyle', 'test']) - - def test7_Begin(self): - for closeable in (False, True): - db = SteadyPgConnection(closeable=closeable) - db.begin() - self.assertEqual(db.session, ['begin']) - db.query('select test') - self.assertEqual(db.num_queries, 1) - db.close() - db.query('select test') - self.assertEqual(db.num_queries, 1) - db.begin() - self.assertEqual(db.session, ['begin']) - db.db.close() - self.assertRaises(InternalError, db.query, 'select test') - self.assertEqual(db.num_queries, 0) - db.query('select test') - self.assertEqual(db.num_queries, 1) - self.assertEqual(db.begin('select sql:begin'), 'sql:begin') - self.assertEqual(db.num_queries, 2) - - def test8_End(self): - for closeable in (False, True): - db = SteadyPgConnection(closeable=closeable) - db.begin() - db.query('select test') - db.end() - self.assertEqual(db.session, ['begin', 'end']) - db.db.close() - db.query('select test') - self.assertEqual(db.num_queries, 1) - self.assertEqual(db.begin('select sql:end'), 'sql:end') - self.assertEqual(db.num_queries, 2) - db.begin() - db.query('select test') - db.commit() - self.assertEqual(db.session, ['begin', 'commit']) - db.db.close() - db.query('select test') - self.assertEqual(db.num_queries, 1) - self.assertEqual(db.begin('select sql:commit'), 'sql:commit') - self.assertEqual(db.num_queries, 2) - db.begin() - db.query('select test') - db.rollback() - self.assertEqual(db.session, ['begin', 'rollback']) - db.db.close() - db.query('select test') - self.assertEqual(db.num_queries, 1) - self.assertEqual(db.begin('select sql:rollback'), 'sql:rollback') - self.assertEqual(db.num_queries, 2) - - -if __name__ == '__main__': - unittest.main() diff --git a/DBUtils/Tests/TestThreadingLocal.py b/DBUtils/Tests/TestThreadingLocal.py deleted file mode 100644 index 6921502..0000000 --- a/DBUtils/Tests/TestThreadingLocal.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Test the ThreadingLocal module.""" - -__version__ = '1.1.1b1' - -import sys -from threading import Thread -import unittest - -sys.path.insert(1, '../..') -from DBUtils.ThreadingLocal import local - - -class TestThreadingLocal(unittest.TestCase): - - def test0_GetAttr(self): - mydata = local() - mydata.number = 42 - self.assertEqual(mydata.number, 42) - - def test1_Dict(self): - mydata = local() - mydata.number = 42 - self.assertEqual(mydata.__dict__, {'number': 42}) - mydata.__dict__.setdefault('widgets', []) - self.assertEqual(mydata.widgets, []) - - def test2_ThreadLocal(self): - def f(): - items = mydata.__dict__.items() - items.sort() - log.append(items) - mydata.number = 11 - log.append(mydata.number) - mydata = local() - mydata.number = 42 - log = [] - thread = Thread(target=f) - thread.start() - thread.join() - self.assertEqual(log, [[], 11]) - self.assertEqual(mydata.number, 42) - - def test3_SubClass(self): - class MyLocal(local): - number = 2 - initialized = 0 - def __init__(self, **kw): - if self.initialized: - raise SystemError - self.initialized = 1 - self.__dict__.update(kw) - def squared(self): - return self.number ** 2 - mydata = MyLocal(color='red') - self.assertEqual(mydata.number, 2) - self.assertEqual(mydata.color, 'red') - del mydata.color - self.assertEqual(mydata.squared(), 4) - def f(): - items = mydata.__dict__.items() - items.sort() - log.append(items) - mydata.number = 7 - log.append(mydata.number) - log = [] - thread = Thread(target=f) - thread.start() - thread.join() - self.assertEqual(log, - [[('color', 'red'), ('initialized', 1)], 7]) - self.assertEqual(mydata.number, 2) - self.assert_(not hasattr(mydata, 'color')) - class MyLocal(local): - __slots__ = 'number' - mydata = MyLocal() - mydata.number = 42 - mydata.color = 'red' - thread = Thread(target=f) - thread.start() - thread.join() - self.assertEqual(mydata.number, 7) - - -if __name__ == '__main__': - unittest.main() diff --git a/DBUtils/Tests/__init__.py b/DBUtils/Tests/__init__.py deleted file mode 100644 index 3195867..0000000 --- a/DBUtils/Tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# DBUtils Tests diff --git a/DBUtils/ThreadingLocal.py b/DBUtils/ThreadingLocal.py deleted file mode 100644 index b15ce03..0000000 --- a/DBUtils/ThreadingLocal.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Thread-local objects. - -This module provides a Python version of the threading.local class. -It is available as _threading_local in the standard library since Python 2.4. - -Depending on the version of Python you're using, there may be a faster -threading.local class available in the standard library. - -However, the C implementation turned out to be unusable with mod_wsgi, -since it does not keep the thread-local data between requests. -To have a reliable solution that works the same with all Python versions, -we fall back to this Python implementation in DBUtils. - -""" - -__all__ = ["local"] - - -try: - from threading import current_thread -except ImportError: # Python >2.5 - from threading import currentThread as current_thread -from threading import RLock, enumerate - - -class _localbase(object): - __slots__ = '_local__key', '_local__args', '_local__lock' - - def __new__(cls, *args, **kw): - self = object.__new__(cls) - key = '_local__key', 'thread.local.' + str(id(self)) - object.__setattr__(self, '_local__key', key) - object.__setattr__(self, '_local__args', (args, kw)) - object.__setattr__(self, '_local__lock', RLock()) - if args or kw and (cls.__init__ is object.__init__): - raise TypeError("Initialization arguments are not supported") - dict = object.__getattribute__(self, '__dict__') - current_thread().__dict__[key] = dict - return self - - -def _patch(self): - key = object.__getattribute__(self, '_local__key') - d = current_thread().__dict__.get(key) - if d is None: - d = {} - current_thread().__dict__[key] = d - object.__setattr__(self, '__dict__', d) - cls = type(self) - if cls.__init__ is not object.__init__: - args, kw = object.__getattribute__(self, '_local__args') - cls.__init__(self, *args, **kw) - else: - object.__setattr__(self, '__dict__', d) - - -class local(_localbase): - """A class that represents thread-local data.""" - - def __getattribute__(self, name): - lock = object.__getattribute__(self, '_local__lock') - lock.acquire() - try: - _patch(self) - return object.__getattribute__(self, name) - finally: - lock.release() - - def __setattr__(self, name, value): - lock = object.__getattribute__(self, '_local__lock') - lock.acquire() - try: - _patch(self) - return object.__setattr__(self, name, value) - finally: - lock.release() - - def __delattr__(self, name): - lock = object.__getattribute__(self, '_local__lock') - lock.acquire() - try: - _patch(self) - return object.__delattr__(self, name) - finally: - lock.release() - - def __del__(self): - try: - key = object.__getattribute__(self, '_local__key') - threads = list(enumerate()) - except: - return - for thread in threads: - try: - __dict__ = thread.__dict__ - except AttributeError: - continue - if key in __dict__: - try: - del __dict__[key] - except KeyError: - pass diff --git a/DBUtils/__init__.py b/DBUtils/__init__.py deleted file mode 100644 index a29b906..0000000 --- a/DBUtils/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# DBUtils package - -__all__ = [ - 'SimplePooledPg', 'SteadyPg', 'PooledPg', 'PersistentPg', - 'SimplePooledDB', 'SteadyDB', 'PooledDB', 'PersistentDB' -] - -__version__ = '1.1.1b1' - - -def InstallInWebKit(appServer): - pass diff --git a/DBUtils/Docs/Doc.css b/Doc.css similarity index 100% rename from DBUtils/Docs/Doc.css rename to Doc.css diff --git a/DBUtils/Docs/DocUtils.css b/DocUtils.css similarity index 100% rename from DBUtils/Docs/DocUtils.css rename to DocUtils.css diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 413324c..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -exclude Release.txt setversion.py -include DBUtils/Docs/* diff --git a/DBUtils/Docs/RelNotes-0.8.1.html b/RelNotes-0.8.1.html similarity index 100% rename from DBUtils/Docs/RelNotes-0.8.1.html rename to RelNotes-0.8.1.html diff --git a/DBUtils/Docs/RelNotes-0.9.1.html b/RelNotes-0.9.1.html similarity index 100% rename from DBUtils/Docs/RelNotes-0.9.1.html rename to RelNotes-0.9.1.html diff --git a/DBUtils/Docs/RelNotes-0.9.2.html b/RelNotes-0.9.2.html similarity index 100% rename from DBUtils/Docs/RelNotes-0.9.2.html rename to RelNotes-0.9.2.html diff --git a/DBUtils/Docs/RelNotes-0.9.3.html b/RelNotes-0.9.3.html similarity index 100% rename from DBUtils/Docs/RelNotes-0.9.3.html rename to RelNotes-0.9.3.html diff --git a/DBUtils/Docs/RelNotes-0.9.4.html b/RelNotes-0.9.4.html similarity index 100% rename from DBUtils/Docs/RelNotes-0.9.4.html rename to RelNotes-0.9.4.html diff --git a/DBUtils/Docs/RelNotes-1.0.html b/RelNotes-1.0.html similarity index 100% rename from DBUtils/Docs/RelNotes-1.0.html rename to RelNotes-1.0.html diff --git a/DBUtils/Docs/RelNotes-1.1.html b/RelNotes-1.1.html similarity index 100% rename from DBUtils/Docs/RelNotes-1.1.html rename to RelNotes-1.1.html diff --git a/Release.md b/Release.md deleted file mode 100644 index 317ce2e..0000000 --- a/Release.md +++ /dev/null @@ -1,58 +0,0 @@ -Create a new DBUtils release: -============================= - -* Check the documentation. If possible, update all translations. - (Chinese translation was too old and has been removed for the time being.) - -* Run all tests in DBUtils/Tests with Python version from 2.3 to 2.7. - -* Check the examples in DBUtils/Examples with the current Webware version. - -* Update and check the Release Notes and copyright information. - -* Set version number and release date with `setversion.py`. - -* Revert to old version number for translations that have not been updated. - -* Build html pages using `buildhtml.py`. - -* Create a tag in the SVN repository. - -* Create a source tarball with: - - python setup.py sdist - - You will find the tarball in the "dist" folder. - - Under Windows, this will be a .zip file, otherwise a .tar.gz file. - You can force .tar.gz under Windows with `--formats=gztar`, - but you need to use Cygwin or have a tar binary installed. - Generally, it is better to create the release under Unix to avoid - problems with DOS linefeeds and missing file permission. - -* Upload to the Python Package Index (PyPI, aka "cheese shop"): - - In your (Unix/Cygwin) home directory, create a .pypirc file as follows: - - echo "[server-login] - username:myusername - password:mypassword" > .pypirc - -* Register the project with: - - python setup.py register - -* Upload the source package with: - - python setup.py sdist upload - - You have to install setuptools to make this work. - Alternatively, you can simply upload using the web interface. - - See also: http://www.python.org/~jeremy/weblog/030924.html - -* Also, don't forget to update the Webware homepage: - - * http://www.w4py.org/DBUtils - * http://www.w4py.org/DBUtils/Docs/ - * http://www.w4py.org/downloads/DBUtils/ diff --git a/DBUtils/Docs/UsersGuide.de.html b/UsersGuide.de.html similarity index 100% rename from DBUtils/Docs/UsersGuide.de.html rename to UsersGuide.de.html diff --git a/DBUtils/Docs/UsersGuide.de.rst b/UsersGuide.de.rst similarity index 100% rename from DBUtils/Docs/UsersGuide.de.rst rename to UsersGuide.de.rst diff --git a/DBUtils/Docs/UsersGuide.html b/UsersGuide.html similarity index 100% rename from DBUtils/Docs/UsersGuide.html rename to UsersGuide.html diff --git a/DBUtils/Docs/UsersGuide.rst b/UsersGuide.rst similarity index 100% rename from DBUtils/Docs/UsersGuide.rst rename to UsersGuide.rst diff --git a/DBUtils/Docs/dbdep.gif b/dbdep.gif similarity index 100% rename from DBUtils/Docs/dbdep.gif rename to dbdep.gif diff --git a/favicon16.png b/favicon16.png new file mode 100644 index 0000000000000000000000000000000000000000..145c2704e42a5975fe2f5342cf3707f400ba3f87 GIT binary patch literal 1905 zcmai!3s@7!7RS$qfC(T$KtS;U^{QA+vb&N5VoR?Pg{W`=BkHYHLs&ut0wtjov`AGH zt5)w7twpS&D56$Dl!|~#D=4KtFlto@4-rs#)>^#^$#kOacWJ+`U+3GIGqdyipL6E1 zyD2mzaJcPgTL2g?4+;p!*3nWqR=D-%^A=#kR?iHc2~fPvZcu5B$Am6CPzEQijd_d* zo~oemV1Tqq02%86Ug1|6j{uTMfF~;eq`Lv!5_8Xmy#v6y7dl@t8w1GS2H|$~R3rej zBN%l8iUV{!c0dO`spH#}MB`$jVF^(_kN9RTe6JaIA_Tg$4sFYbI}xn?G9rE&aVpFP zp;4q8bf~Qm;?yfxs11q&iI}$uR1cGQKmbg01+;TMD$=3Sb?96cs@Q-|XP~e3D1Qao zIt#^)MjPKnr5UJhFZ!_nH58%;hf)0wbU6#1Nkmy5C>2aOVW?)iISU3>!XvFq|Hts) z1EOIO(Zay}2;x*AaecW}?K0w|3~Hjsl%-3nR=QM@&=qUnzemuUYTG;)?nV&pafI=G z!Vqe;SprvBMQ1Vdv<3K*Fss%N}`#8r4xuFa=4iYxj}fV zz}pE_63C8(l8tcbFw|Xu+ETc%3y!8hrUF8zfeY7y;bO;uaihU|B20V>T%CYzIYCQ7 zoq6c?9&{@Q-Q9s2x1fiasB;Z!N=0{d=vE9e%s>aNpXbgp*Qn491~o;Z$4k-A3sIjO zwTaL@H)LR$3ZRuCdbV(%A9EV`*41=>r1>$=+&s)=AYN6$FNfgCdU&{o=-ms?&ce%U zFxU()Z@{xM==};>(}^<~Q>wSP+&*OAa-P$E-KMExSp5O+#m^l|KXHFhe?ntlsB*C(pU-OQ&CUb4_P><{x;bR=dG=&R~nR$zWxwWW78Ly} zm-b`+H_K*RI!vA~oLo`tRZ%>q>WF*YLD$B;&OJHy{oAYuFFK%_5oQC=++=4q4nsHC zsEjqRkLX(iO)2nVAE0u;Hw|3^^GSI2g=L?|PtokcW|clX#PZerZE^HdrmfBRFnqF& zZB$j)E3@f&Q=Ta7d9{<;I5yHaR@QsgLDHSi4*XvQH{eADcY0e6dxCMjcW3tGj$K~2 zO{>}RgY_MKPk-t%o=@}Oc>2u?50&wqS=s@^LL8fjoEI1XS=M7Mw}_h0n`_U+HhJ8&@n(Cj%pa10DOTyUiD=+R@xzdj+KI|7{M%|BUGe5&O1nbL3028TGq z@PCDtohz@XJb&S0)wf~cE--R|;=4=LHJ7i{eqZ;)!U$JzTeSG9ad)DA_F}y%iBakq zeiUwiCP_a&erR6>MM-IiR3ze)K2nnG9xUGUyTC$9->9&FlJH3yLkeoTRAdoMB}puB zEg{SPAc#+h(nP2JwLsmQW%&WWPU#IhG$j$ArW6z{^}#{JZ&GSoSFOc@IhHl(5~B4f z$|MHl@k%ultW;}Mv7TaqMBvN!^Tav&@JSJ$qCLYXv1g24pC}ayQ&Lg{8VijprUeN} z>c$N{*de%EJSt+6<0GZgfCN=?Jfqc1rJ)$r0abv?g@0mR$COHA7>znc50oFxgQQH% zxDE018?#6grH`>7hT~owjEowB1Z(0MeQF{Dud@;+YSr(ZiU|3WpI`;1fb#Jb)09{& z5j`y|dVvw%5I+kvX$+7uS@r?{Zaft+WNKl!9HKRG3|_u4UaOBwPz$4#A8AwxTAlF! zCn;1>bhN~e@}+5&AFZS)iO6@lh@@$;m}Hc`Dn@`60-WE|sH~rH1dz`T2{<8JvG!Mr C?c>h? literal 0 HcmV?d00001 diff --git a/favicon32.png b/favicon32.png new file mode 100644 index 0000000000000000000000000000000000000000..3a553fceffeeef9cd5f167d5a84abe68cd3b7ddc GIT binary patch literal 2841 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 D$}Plh literal 0 HcmV?d00001 diff --git a/favicon96.png b/favicon96.png new file mode 100644 index 0000000000000000000000000000000000000000..5cfa97f762d1fd7cba28dd0cda98b2ad0003d420 GIT binary patch literal 10868 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 + + + + + DBUtils + + + + + + + + + + + +
+

DBUtils

+

Multi-threaded database connections

+ +
+ +

Outsource your database connection handling

+ +
+ +
+ +
+

Documentation

+
+

User's Guide

+
English | German
+ +

Release Notes:

+
+ 0.8.1 | + 0.9.1 | + 0.9.2 | + 0.9.3 | + 0.9.4 +
+
+ 1.0 | + 1.1 +
+
+
+
+ +
+

Downloads

+
+
Current/Recommended Version:
+ Download DBUtils 1.1 +
+
+
Older Versions:
+ Download DBUtils 1.0 +
+
+ +
+

Development

+ +
+ +
+ +
+ +
The DBUtils Homepage is hosted by GitHub.
+ + + diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..686992b490cb805a50c715d9f8325fc1f973c9ee GIT binary patch literal 6518 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:border-box;flex:1 1 auto;padding-left:.6em;padding-bottom:.6em}.flex[class*="one"]>*,.flex[class*="two"]>*,.flex[class*="three"]>*,.flex[class*="four"]>*,.flex[class*="five"]>*,.flex[class*="six"]>*,.flex[class*="seven"]>*,.flex[class*="eight"]>*,.flex[class*="nine"]>*,.flex[class*="ten"]>*,.flex[class*="eleven"]>*,.flex[class*="twelve"]>*{flex-grow:0}.flex.grow>*{flex-grow:1}.center{justify-content:center}.one>*{width:100%}.two>*{width:50%}.three>*{width:33.33333%}.four>*{width:25%}.five>*{width:20%}.six>*{width:16.66666%}.seven>*{width:14.28571%}.eight>*{width:12.5%}.nine>*{width:11.11111%}.ten>*{width:10%}.eleven>*{width:9.09091%}.twelve>*{width:8.33333%}@media all and (min-width: 500px){.one-500>*{width:100%}.two-500>*{width:50%}.three-500>*{width:33.33333%}.four-500>*{width:25%}.five-500>*{width:20%}.six-500>*{width:16.66666%}.seven-500>*{width:14.28571%}.eight-500>*{width:12.5%}.nine-500>*{width:11.11111%}.ten-500>*{width:10%}.eleven-500>*{width:9.09091%}.twelve-500>*{width:8.33333%}}@media all and (min-width: 600px){.one-600>*{width:100%}.two-600>*{width:50%}.three-600>*{width:33.33333%}.four-600>*{width:25%}.five-600>*{width:20%}.six-600>*{width:16.66666%}.seven-600>*{width:14.28571%}.eight-600>*{width:12.5%}.nine-600>*{width:11.11111%}.ten-600>*{width:10%}.eleven-600>*{width:9.09091%}.twelve-600>*{width:8.33333%}}@media all and (min-width: 700px){.one-700>*{width:100%}.two-700>*{width:50%}.three-700>*{width:33.33333%}.four-700>*{width:25%}.five-700>*{width:20%}.six-700>*{width:16.66666%}.seven-700>*{width:14.28571%}.eight-700>*{width:12.5%}.nine-700>*{width:11.11111%}.ten-700>*{width:10%}.eleven-700>*{width:9.09091%}.twelve-700>*{width:8.33333%}}@media all and (min-width: 800px){.one-800>*{width:100%}.two-800>*{width:50%}.three-800>*{width:33.33333%}.four-800>*{width:25%}.five-800>*{width:20%}.six-800>*{width:16.66666%}.seven-800>*{width:14.28571%}.eight-800>*{width:12.5%}.nine-800>*{width:11.11111%}.ten-800>*{width:10%}.eleven-800>*{width:9.09091%}.twelve-800>*{width:8.33333%}}@media all and (min-width: 900px){.one-900>*{width:100%}.two-900>*{width:50%}.three-900>*{width:33.33333%}.four-900>*{width:25%}.five-900>*{width:20%}.six-900>*{width:16.66666%}.seven-900>*{width:14.28571%}.eight-900>*{width:12.5%}.nine-900>*{width:11.11111%}.ten-900>*{width:10%}.eleven-900>*{width:9.09091%}.twelve-900>*{width:8.33333%}}@media all and (min-width: 1000px){.one-1000>*{width:100%}.two-1000>*{width:50%}.three-1000>*{width:33.33333%}.four-1000>*{width:25%}.five-1000>*{width:20%}.six-1000>*{width:16.66666%}.seven-1000>*{width:14.28571%}.eight-1000>*{width:12.5%}.nine-1000>*{width:11.11111%}.ten-1000>*{width:10%}.eleven-1000>*{width:9.09091%}.twelve-1000>*{width:8.33333%}}@media all and (min-width: 1100px){.one-1100>*{width:100%}.two-1100>*{width:50%}.three-1100>*{width:33.33333%}.four-1100>*{width:25%}.five-1100>*{width:20%}.six-1100>*{width:16.66666%}.seven-1100>*{width:14.28571%}.eight-1100>*{width:12.5%}.nine-1100>*{width:11.11111%}.ten-1100>*{width:10%}.eleven-1100>*{width:9.09091%}.twelve-1100>*{width:8.33333%}}@media all and (min-width: 1200px){.one-1200>*{width:100%}.two-1200>*{width:50%}.three-1200>*{width:33.33333%}.four-1200>*{width:25%}.five-1200>*{width:20%}.six-1200>*{width:16.66666%}.seven-1200>*{width:14.28571%}.eight-1200>*{width:12.5%}.nine-1200>*{width:11.11111%}.ten-1200>*{width:10%}.eleven-1200>*{width:9.09091%}.twelve-1200>*{width:8.33333%}}@media all and (min-width: 1300px){.one-1300>*{width:100%}.two-1300>*{width:50%}.three-1300>*{width:33.33333%}.four-1300>*{width:25%}.five-1300>*{width:20%}.six-1300>*{width:16.66666%}.seven-1300>*{width:14.28571%}.eight-1300>*{width:12.5%}.nine-1300>*{width:11.11111%}.ten-1300>*{width:10%}.eleven-1300>*{width:9.09091%}.twelve-1300>*{width:8.33333%}}@media all and (min-width: 1400px){.one-1400>*{width:100%}.two-1400>*{width:50%}.three-1400>*{width:33.33333%}.four-1400>*{width:25%}.five-1400>*{width:20%}.six-1400>*{width:16.66666%}.seven-1400>*{width:14.28571%}.eight-1400>*{width:12.5%}.nine-1400>*{width:11.11111%}.ten-1400>*{width:10%}.eleven-1400>*{width:9.09091%}.twelve-1400>*{width:8.33333%}}@media all and (min-width: 1500px){.one-1500>*{width:100%}.two-1500>*{width:50%}.three-1500>*{width:33.33333%}.four-1500>*{width:25%}.five-1500>*{width:20%}.six-1500>*{width:16.66666%}.seven-1500>*{width:14.28571%}.eight-1500>*{width:12.5%}.nine-1500>*{width:11.11111%}.ten-1500>*{width:10%}.eleven-1500>*{width:9.09091%}.twelve-1500>*{width:8.33333%}}@media all and (min-width: 1600px){.one-1600>*{width:100%}.two-1600>*{width:50%}.three-1600>*{width:33.33333%}.four-1600>*{width:25%}.five-1600>*{width:20%}.six-1600>*{width:16.66666%}.seven-1600>*{width:14.28571%}.eight-1600>*{width:12.5%}.nine-1600>*{width:11.11111%}.ten-1600>*{width:10%}.eleven-1600>*{width:9.09091%}.twelve-1600>*{width:8.33333%}}@media all and (min-width: 1700px){.one-1700>*{width:100%}.two-1700>*{width:50%}.three-1700>*{width:33.33333%}.four-1700>*{width:25%}.five-1700>*{width:20%}.six-1700>*{width:16.66666%}.seven-1700>*{width:14.28571%}.eight-1700>*{width:12.5%}.nine-1700>*{width:11.11111%}.ten-1700>*{width:10%}.eleven-1700>*{width:9.09091%}.twelve-1700>*{width:8.33333%}}@media all and (min-width: 1800px){.one-1800>*{width:100%}.two-1800>*{width:50%}.three-1800>*{width:33.33333%}.four-1800>*{width:25%}.five-1800>*{width:20%}.six-1800>*{width:16.66666%}.seven-1800>*{width:14.28571%}.eight-1800>*{width:12.5%}.nine-1800>*{width:11.11111%}.ten-1800>*{width:10%}.eleven-1800>*{width:9.09091%}.twelve-1800>*{width:8.33333%}}@media all and (min-width: 1900px){.one-1900>*{width:100%}.two-1900>*{width:50%}.three-1900>*{width:33.33333%}.four-1900>*{width:25%}.five-1900>*{width:20%}.six-1900>*{width:16.66666%}.seven-1900>*{width:14.28571%}.eight-1900>*{width:12.5%}.nine-1900>*{width:11.11111%}.ten-1900>*{width:10%}.eleven-1900>*{width:9.09091%}.twelve-1900>*{width:8.33333%}}@media all and (min-width: 2000px){.one-2000>*{width:100%}.two-2000>*{width:50%}.three-2000>*{width:33.33333%}.four-2000>*{width:25%}.five-2000>*{width:20%}.six-2000>*{width:16.66666%}.seven-2000>*{width:14.28571%}.eight-2000>*{width:12.5%}.nine-2000>*{width:11.11111%}.ten-2000>*{width:10%}.eleven-2000>*{width:9.09091%}.twelve-2000>*{width:8.33333%}}.full{width:100%}.half{width:50%}.third{width:33.33333%}.two-third{width:66.66666%}.fourth{width:25%}.three-fourth{width:75%}.fifth{width:20%}.two-fifth{width:40%}.three-fifth{width:60%}.four-fifth{width:80%}.sixth{width:16.66666%}.none{display:none}@media all and (min-width: 500px){.full-500{width:100%;display:block}.half-500{width:50%;display:block}.third-500{width:33.33333%;display:block}.two-third-500{width:66.66666%;display:block}.fourth-500{width:25%;display:block}.three-fourth-500{width:75%;display:block}.fifth-500{width:20%;display:block}.two-fifth-500{width:40%;display:block}.three-fifth-500{width:60%;display:block}.four-fifth-500{width:80%;display:block}.sixth-500{width:16.66666%;display:block}.none-500{display:none}}@media all and (min-width: 600px){.full-600{width:100%;display:block}.half-600{width:50%;display:block}.third-600{width:33.33333%;display:block}.two-third-600{width:66.66666%;display:block}.fourth-600{width:25%;display:block}.three-fourth-600{width:75%;display:block}.fifth-600{width:20%;display:block}.two-fifth-600{width:40%;display:block}.three-fifth-600{width:60%;display:block}.four-fifth-600{width:80%;display:block}.sixth-600{width:16.66666%;display:block}.none-600{display:none}}@media all and (min-width: 700px){.full-700{width:100%;display:block}.half-700{width:50%;display:block}.third-700{width:33.33333%;display:block}.two-third-700{width:66.66666%;display:block}.fourth-700{width:25%;display:block}.three-fourth-700{width:75%;display:block}.fifth-700{width:20%;display:block}.two-fifth-700{width:40%;display:block}.three-fifth-700{width:60%;display:block}.four-fifth-700{width:80%;display:block}.sixth-700{width:16.66666%;display:block}.none-700{display:none}}@media all and (min-width: 800px){.full-800{width:100%;display:block}.half-800{width:50%;display:block}.third-800{width:33.33333%;display:block}.two-third-800{width:66.66666%;display:block}.fourth-800{width:25%;display:block}.three-fourth-800{width:75%;display:block}.fifth-800{width:20%;display:block}.two-fifth-800{width:40%;display:block}.three-fifth-800{width:60%;display:block}.four-fifth-800{width:80%;display:block}.sixth-800{width:16.66666%;display:block}.none-800{display:none}}@media all and (min-width: 900px){.full-900{width:100%;display:block}.half-900{width:50%;display:block}.third-900{width:33.33333%;display:block}.two-third-900{width:66.66666%;display:block}.fourth-900{width:25%;display:block}.three-fourth-900{width:75%;display:block}.fifth-900{width:20%;display:block}.two-fifth-900{width:40%;display:block}.three-fifth-900{width:60%;display:block}.four-fifth-900{width:80%;display:block}.sixth-900{width:16.66666%;display:block}.none-900{display:none}}@media all and (min-width: 1000px){.full-1000{width:100%;display:block}.half-1000{width:50%;display:block}.third-1000{width:33.33333%;display:block}.two-third-1000{width:66.66666%;display:block}.fourth-1000{width:25%;display:block}.three-fourth-1000{width:75%;display:block}.fifth-1000{width:20%;display:block}.two-fifth-1000{width:40%;display:block}.three-fifth-1000{width:60%;display:block}.four-fifth-1000{width:80%;display:block}.sixth-1000{width:16.66666%;display:block}.none-1000{display:none}}@media all and (min-width: 1100px){.full-1100{width:100%;display:block}.half-1100{width:50%;display:block}.third-1100{width:33.33333%;display:block}.two-third-1100{width:66.66666%;display:block}.fourth-1100{width:25%;display:block}.three-fourth-1100{width:75%;display:block}.fifth-1100{width:20%;display:block}.two-fifth-1100{width:40%;display:block}.three-fifth-1100{width:60%;display:block}.four-fifth-1100{width:80%;display:block}.sixth-1100{width:16.66666%;display:block}.none-1100{display:none}}@media all and (min-width: 1200px){.full-1200{width:100%;display:block}.half-1200{width:50%;display:block}.third-1200{width:33.33333%;display:block}.two-third-1200{width:66.66666%;display:block}.fourth-1200{width:25%;display:block}.three-fourth-1200{width:75%;display:block}.fifth-1200{width:20%;display:block}.two-fifth-1200{width:40%;display:block}.three-fifth-1200{width:60%;display:block}.four-fifth-1200{width:80%;display:block}.sixth-1200{width:16.66666%;display:block}.none-1200{display:none}}@media all and (min-width: 1300px){.full-1300{width:100%;display:block}.half-1300{width:50%;display:block}.third-1300{width:33.33333%;display:block}.two-third-1300{width:66.66666%;display:block}.fourth-1300{width:25%;display:block}.three-fourth-1300{width:75%;display:block}.fifth-1300{width:20%;display:block}.two-fifth-1300{width:40%;display:block}.three-fifth-1300{width:60%;display:block}.four-fifth-1300{width:80%;display:block}.sixth-1300{width:16.66666%;display:block}.none-1300{display:none}}@media all and (min-width: 1400px){.full-1400{width:100%;display:block}.half-1400{width:50%;display:block}.third-1400{width:33.33333%;display:block}.two-third-1400{width:66.66666%;display:block}.fourth-1400{width:25%;display:block}.three-fourth-1400{width:75%;display:block}.fifth-1400{width:20%;display:block}.two-fifth-1400{width:40%;display:block}.three-fifth-1400{width:60%;display:block}.four-fifth-1400{width:80%;display:block}.sixth-1400{width:16.66666%;display:block}.none-1400{display:none}}@media all and (min-width: 1500px){.full-1500{width:100%;display:block}.half-1500{width:50%;display:block}.third-1500{width:33.33333%;display:block}.two-third-1500{width:66.66666%;display:block}.fourth-1500{width:25%;display:block}.three-fourth-1500{width:75%;display:block}.fifth-1500{width:20%;display:block}.two-fifth-1500{width:40%;display:block}.three-fifth-1500{width:60%;display:block}.four-fifth-1500{width:80%;display:block}.sixth-1500{width:16.66666%;display:block}.none-1500{display:none}}@media all and (min-width: 1600px){.full-1600{width:100%;display:block}.half-1600{width:50%;display:block}.third-1600{width:33.33333%;display:block}.two-third-1600{width:66.66666%;display:block}.fourth-1600{width:25%;display:block}.three-fourth-1600{width:75%;display:block}.fifth-1600{width:20%;display:block}.two-fifth-1600{width:40%;display:block}.three-fifth-1600{width:60%;display:block}.four-fifth-1600{width:80%;display:block}.sixth-1600{width:16.66666%;display:block}.none-1600{display:none}}@media all and (min-width: 1700px){.full-1700{width:100%;display:block}.half-1700{width:50%;display:block}.third-1700{width:33.33333%;display:block}.two-third-1700{width:66.66666%;display:block}.fourth-1700{width:25%;display:block}.three-fourth-1700{width:75%;display:block}.fifth-1700{width:20%;display:block}.two-fifth-1700{width:40%;display:block}.three-fifth-1700{width:60%;display:block}.four-fifth-1700{width:80%;display:block}.sixth-1700{width:16.66666%;display:block}.none-1700{display:none}}@media all and (min-width: 1800px){.full-1800{width:100%;display:block}.half-1800{width:50%;display:block}.third-1800{width:33.33333%;display:block}.two-third-1800{width:66.66666%;display:block}.fourth-1800{width:25%;display:block}.three-fourth-1800{width:75%;display:block}.fifth-1800{width:20%;display:block}.two-fifth-1800{width:40%;display:block}.three-fifth-1800{width:60%;display:block}.four-fifth-1800{width:80%;display:block}.sixth-1800{width:16.66666%;display:block}.none-1800{display:none}}@media all and (min-width: 1900px){.full-1900{width:100%;display:block}.half-1900{width:50%;display:block}.third-1900{width:33.33333%;display:block}.two-third-1900{width:66.66666%;display:block}.fourth-1900{width:25%;display:block}.three-fourth-1900{width:75%;display:block}.fifth-1900{width:20%;display:block}.two-fifth-1900{width:40%;display:block}.three-fifth-1900{width:60%;display:block}.four-fifth-1900{width:80%;display:block}.sixth-1900{width:16.66666%;display:block}.none-1900{display:none}}@media all and (min-width: 2000px){.full-2000{width:100%;display:block}.half-2000{width:50%;display:block}.third-2000{width:33.33333%;display:block}.two-third-2000{width:66.66666%;display:block}.fourth-2000{width:25%;display:block}.three-fourth-2000{width:75%;display:block}.fifth-2000{width:20%;display:block}.two-fifth-2000{width:40%;display:block}.three-fifth-2000{width:60%;display:block}.four-fifth-2000{width:80%;display:block}.sixth-2000{width:16.66666%;display:block}.none-2000{display:none}}.off-half{margin-left:50%}.off-third{margin-left:33.33333%}.off-two-third{margin-left:66.66666%}.off-fourth{margin-left:25%}.off-three-fourth{margin-left:75%}.off-fifth{margin-left:20%}.off-two-fifth{margin-left:40%}.off-three-fifth{margin-left:60%}.off-four-fifth{margin-left:80%}.off-sixth{margin-left:16.66666%}.off-none{margin-left:0}@media all and (min-width: 500px){.off-full-500{margin-left:100%}.off-half-500{margin-left:50%}.off-third-500{margin-left:33.33333%}.off-two-third-500{margin-left:66.66666%}.off-fourth-500{margin-left:25%}.off-three-fourth-500{margin-left:75%}.off-fifth-500{margin-left:20%}.off-two-fifth-500{margin-left:40%}.off-three-fifth-500{margin-left:60%}.off-four-fifth-500{margin-left:80%}.off-sixth-500{margin-left:16.66666%}.off-none-500{margin-left:0}}@media all and (min-width: 600px){.off-full-600{margin-left:100%}.off-half-600{margin-left:50%}.off-third-600{margin-left:33.33333%}.off-two-third-600{margin-left:66.66666%}.off-fourth-600{margin-left:25%}.off-three-fourth-600{margin-left:75%}.off-fifth-600{margin-left:20%}.off-two-fifth-600{margin-left:40%}.off-three-fifth-600{margin-left:60%}.off-four-fifth-600{margin-left:80%}.off-sixth-600{margin-left:16.66666%}.off-none-600{margin-left:0}}@media all and (min-width: 700px){.off-full-700{margin-left:100%}.off-half-700{margin-left:50%}.off-third-700{margin-left:33.33333%}.off-two-third-700{margin-left:66.66666%}.off-fourth-700{margin-left:25%}.off-three-fourth-700{margin-left:75%}.off-fifth-700{margin-left:20%}.off-two-fifth-700{margin-left:40%}.off-three-fifth-700{margin-left:60%}.off-four-fifth-700{margin-left:80%}.off-sixth-700{margin-left:16.66666%}.off-none-700{margin-left:0}}@media all and (min-width: 800px){.off-full-800{margin-left:100%}.off-half-800{margin-left:50%}.off-third-800{margin-left:33.33333%}.off-two-third-800{margin-left:66.66666%}.off-fourth-800{margin-left:25%}.off-three-fourth-800{margin-left:75%}.off-fifth-800{margin-left:20%}.off-two-fifth-800{margin-left:40%}.off-three-fifth-800{margin-left:60%}.off-four-fifth-800{margin-left:80%}.off-sixth-800{margin-left:16.66666%}.off-none-800{margin-left:0}}@media all and (min-width: 900px){.off-full-900{margin-left:100%}.off-half-900{margin-left:50%}.off-third-900{margin-left:33.33333%}.off-two-third-900{margin-left:66.66666%}.off-fourth-900{margin-left:25%}.off-three-fourth-900{margin-left:75%}.off-fifth-900{margin-left:20%}.off-two-fifth-900{margin-left:40%}.off-three-fifth-900{margin-left:60%}.off-four-fifth-900{margin-left:80%}.off-sixth-900{margin-left:16.66666%}.off-none-900{margin-left:0}}@media all and (min-width: 1000px){.off-full-1000{margin-left:100%}.off-half-1000{margin-left:50%}.off-third-1000{margin-left:33.33333%}.off-two-third-1000{margin-left:66.66666%}.off-fourth-1000{margin-left:25%}.off-three-fourth-1000{margin-left:75%}.off-fifth-1000{margin-left:20%}.off-two-fifth-1000{margin-left:40%}.off-three-fifth-1000{margin-left:60%}.off-four-fifth-1000{margin-left:80%}.off-sixth-1000{margin-left:16.66666%}.off-none-1000{margin-left:0}}@media all and (min-width: 1100px){.off-full-1100{margin-left:100%}.off-half-1100{margin-left:50%}.off-third-1100{margin-left:33.33333%}.off-two-third-1100{margin-left:66.66666%}.off-fourth-1100{margin-left:25%}.off-three-fourth-1100{margin-left:75%}.off-fifth-1100{margin-left:20%}.off-two-fifth-1100{margin-left:40%}.off-three-fifth-1100{margin-left:60%}.off-four-fifth-1100{margin-left:80%}.off-sixth-1100{margin-left:16.66666%}.off-none-1100{margin-left:0}}@media all and (min-width: 1200px){.off-full-1200{margin-left:100%}.off-half-1200{margin-left:50%}.off-third-1200{margin-left:33.33333%}.off-two-third-1200{margin-left:66.66666%}.off-fourth-1200{margin-left:25%}.off-three-fourth-1200{margin-left:75%}.off-fifth-1200{margin-left:20%}.off-two-fifth-1200{margin-left:40%}.off-three-fifth-1200{margin-left:60%}.off-four-fifth-1200{margin-left:80%}.off-sixth-1200{margin-left:16.66666%}.off-none-1200{margin-left:0}}@media all and (min-width: 1300px){.off-full-1300{margin-left:100%}.off-half-1300{margin-left:50%}.off-third-1300{margin-left:33.33333%}.off-two-third-1300{margin-left:66.66666%}.off-fourth-1300{margin-left:25%}.off-three-fourth-1300{margin-left:75%}.off-fifth-1300{margin-left:20%}.off-two-fifth-1300{margin-left:40%}.off-three-fifth-1300{margin-left:60%}.off-four-fifth-1300{margin-left:80%}.off-sixth-1300{margin-left:16.66666%}.off-none-1300{margin-left:0}}@media all and (min-width: 1400px){.off-full-1400{margin-left:100%}.off-half-1400{margin-left:50%}.off-third-1400{margin-left:33.33333%}.off-two-third-1400{margin-left:66.66666%}.off-fourth-1400{margin-left:25%}.off-three-fourth-1400{margin-left:75%}.off-fifth-1400{margin-left:20%}.off-two-fifth-1400{margin-left:40%}.off-three-fifth-1400{margin-left:60%}.off-four-fifth-1400{margin-left:80%}.off-sixth-1400{margin-left:16.66666%}.off-none-1400{margin-left:0}}@media all and (min-width: 1500px){.off-full-1500{margin-left:100%}.off-half-1500{margin-left:50%}.off-third-1500{margin-left:33.33333%}.off-two-third-1500{margin-left:66.66666%}.off-fourth-1500{margin-left:25%}.off-three-fourth-1500{margin-left:75%}.off-fifth-1500{margin-left:20%}.off-two-fifth-1500{margin-left:40%}.off-three-fifth-1500{margin-left:60%}.off-four-fifth-1500{margin-left:80%}.off-sixth-1500{margin-left:16.66666%}.off-none-1500{margin-left:0}}@media all and (min-width: 1600px){.off-full-1600{margin-left:100%}.off-half-1600{margin-left:50%}.off-third-1600{margin-left:33.33333%}.off-two-third-1600{margin-left:66.66666%}.off-fourth-1600{margin-left:25%}.off-three-fourth-1600{margin-left:75%}.off-fifth-1600{margin-left:20%}.off-two-fifth-1600{margin-left:40%}.off-three-fifth-1600{margin-left:60%}.off-four-fifth-1600{margin-left:80%}.off-sixth-1600{margin-left:16.66666%}.off-none-1600{margin-left:0}}@media all and (min-width: 1700px){.off-full-1700{margin-left:100%}.off-half-1700{margin-left:50%}.off-third-1700{margin-left:33.33333%}.off-two-third-1700{margin-left:66.66666%}.off-fourth-1700{margin-left:25%}.off-three-fourth-1700{margin-left:75%}.off-fifth-1700{margin-left:20%}.off-two-fifth-1700{margin-left:40%}.off-three-fifth-1700{margin-left:60%}.off-four-fifth-1700{margin-left:80%}.off-sixth-1700{margin-left:16.66666%}.off-none-1700{margin-left:0}}@media all and (min-width: 1800px){.off-full-1800{margin-left:100%}.off-half-1800{margin-left:50%}.off-third-1800{margin-left:33.33333%}.off-two-third-1800{margin-left:66.66666%}.off-fourth-1800{margin-left:25%}.off-three-fourth-1800{margin-left:75%}.off-fifth-1800{margin-left:20%}.off-two-fifth-1800{margin-left:40%}.off-three-fifth-1800{margin-left:60%}.off-four-fifth-1800{margin-left:80%}.off-sixth-1800{margin-left:16.66666%}.off-none-1800{margin-left:0}}@media all and (min-width: 1900px){.off-full-1900{margin-left:100%}.off-half-1900{margin-left:50%}.off-third-1900{margin-left:33.33333%}.off-two-third-1900{margin-left:66.66666%}.off-fourth-1900{margin-left:25%}.off-three-fourth-1900{margin-left:75%}.off-fifth-1900{margin-left:20%}.off-two-fifth-1900{margin-left:40%}.off-three-fifth-1900{margin-left:60%}.off-four-fifth-1900{margin-left:80%}.off-sixth-1900{margin-left:16.66666%}.off-none-1900{margin-left:0}}@media all and (min-width: 2000px){.off-full-2000{margin-left:100%}.off-half-2000{margin-left:50%}.off-third-2000{margin-left:33.33333%}.off-two-third-2000{margin-left:66.66666%}.off-fourth-2000{margin-left:25%}.off-three-fourth-2000{margin-left:75%}.off-fifth-2000{margin-left:20%}.off-two-fifth-2000{margin-left:40%}.off-three-fifth-2000{margin-left:60%}.off-four-fifth-2000{margin-left:80%}.off-sixth-2000{margin-left:16.66666%}.off-none-2000{margin-left:0}}nav{position:fixed;top:0;left:0;right:0;height:3em;padding:0;background:#fff;box-shadow:0 0 0.2em rgba(17,17,17,0.2);z-index:10;transition:all .3s;transform-style:preserve-3d}nav .brand,nav .menu,nav .burger{float:right;position:relative;top:50%;transform:translateY(-50%)}nav .brand{font-weight:700;float:left;padding:0 .6em;max-width:50%;white-space:nowrap;color:#111}nav .brand *{vertical-align:middle}nav .logo{height:2em;margin-right:.3em}nav .select::after{height:calc(100% - 1px);padding:0;line-height:2.4em}nav .menu>*{margin-right:.6em}.burger{display:none}@media all and (max-width: 60em){nav .burger{display:inline-block;cursor:pointer;bottom:-1000em;margin:0}nav .menu,nav .show:checked ~ .burger{position:fixed;min-height:100%;width:0;overflow:hidden;top:0;right:0;bottom:-1000em;margin:0;background:#fff;transition:all .3s ease;transform:none}nav .show:checked ~ .burger{color:transparent;width:100%;border-radius:0;background:rgba(0,0,0,0.2);transition:all .3s ease}nav .show:checked ~ .menu{width:70%;overflow:auto;transition:all .3s ease}nav .menu>*{display:block;margin:.3em;text-align:left}nav .menu>a{padding:.3em .9em}}.stack,.stack .toggle{margin-top:0;margin-bottom:0;display:block;width:100%;text-align:left;border-radius:0}.stack:first-child,.stack:first-child .toggle{border-top-left-radius:.2em;border-top-right-radius:.2em}.stack:last-child,.stack:last-child .toggle{border-bottom-left-radius:.2em;border-bottom-right-radius:.2em}input.stack,textarea.stack,select.stack{transition:border-bottom 0 ease 0;border-bottom-width:0}input.stack:last-child,textarea.stack:last-child,select.stack:last-child{border-bottom-width:1px}input.stack:focus+input,input.stack:focus+textarea,input.stack:focus+select,textarea.stack:focus+input,textarea.stack:focus+textarea,textarea.stack:focus+select,select.stack:focus+input,select.stack:focus+textarea,select.stack:focus+select{border-top-color:#0074d9}.card,.modal .overlay ~ *{position:relative;box-shadow:0;border-radius:.2em;border:1px solid #ccc;overflow:hidden;text-align:left;background:#fff;margin-bottom:.6em;padding:0;transition:all .3s ease}.hidden.card,.modal .overlay ~ .hidden,:checked+.card,.modal .overlay ~ :checked+*,.modal .overlay:checked+*{font-size:0;padding:0;margin:0;border:0}.card>*,.modal .overlay ~ *>*{max-width:100%;display:block}.card>*:last-child,.modal .overlay ~ *>*:last-child{margin-bottom:0}.card header,.modal .overlay ~ * header,.card section,.modal .overlay ~ * section,.card>p,.modal .overlay ~ *>p{padding:.6em .8em}.card section,.modal .overlay ~ * section{padding:.6em .8em 0}.card hr,.modal .overlay ~ * hr{border:none;height:1px;background-color:#eee}.card header,.modal .overlay ~ * header{font-weight:bold;position:relative;border-bottom:1px solid #eee}.card header h1,.modal .overlay ~ * header h1,.card header h2,.modal .overlay ~ * header h2,.card header h3,.modal .overlay ~ * header h3,.card header h4,.modal .overlay ~ * header h4,.card header h5,.modal .overlay ~ * header h5,.card header h6,.modal .overlay ~ * header h6{padding:0;margin:0 2em 0 0;line-height:1;display:inline-block;vertical-align:text-bottom}.card header:last-child,.modal .overlay ~ * header:last-child{border-bottom:0}.card footer,.modal .overlay ~ * footer{padding:.8em}.card p,.modal .overlay ~ * p{margin:.3em 0}.card p:first-child,.modal .overlay ~ * p:first-child{margin-top:0}.card p:last-child,.modal .overlay ~ * p:last-child{margin-bottom:0}.card>p,.modal .overlay ~ *>p{margin:0;padding-right:2.5em}.card .close,.modal .overlay ~ * .close{position:absolute;top:.4em;right:.3em;font-size:1.2em;padding:0 .5em;cursor:pointer;width:auto}.card .close:hover,.modal .overlay ~ * .close:hover{color:#ff4136}.card h1+.close,.modal .overlay ~ * h1+.close{margin:.2em}.card h2+.close,.modal .overlay ~ * h2+.close{margin:.1em}.card .dangerous,.modal .overlay ~ * .dangerous{background:#ff4136;float:right}.modal{text-align:center}.modal>input{display:none}.modal>input ~ *{opacity:0;max-height:0;overflow:hidden}.modal .overlay{top:0;left:0;bottom:0;right:0;position:fixed;margin:0;border-radius:0;background:rgba(17,17,17,0.6);transition:all 0.3s;z-index:99}.modal .overlay:before,.modal .overlay:after{display:none}.modal .overlay ~ *{border:0;position:fixed;top:50%;left:50%;transform:translateX(-50%) translateY(-50%) scale(0.2, 0.2);z-index:100;transition:all 0.3s}.modal>input:checked ~ *{display:block;opacity:1;max-height:10000px;transition:all 0.3s}.modal>input:checked ~ .overlay ~ *{max-height:90%;overflow:auto;-webkit-transform:translateX(-50%) translateY(-50%) scale(1, 1);transform:translateX(-50%) translateY(-50%) scale(1, 1)}@media (max-width: 60em){.modal .overlay ~ *{min-width:90%}}.dropimage{position:relative;display:block;padding:0;padding-bottom:56.25%;overflow:hidden;cursor:pointer;border:0;background-color:#ddd;background-size:cover;background-position:center center;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDAiIGhlaWdodD0iNjQwIiB2ZXJzaW9uPSIxLjEiPjxnIHN0eWxlPSJmaWxsOiMzMzMiPjxwYXRoIGQ9Ik0gMTg3IDIzMCBDIDE3NSAyMzAgMTY1IDI0MCAxNjUgMjUyIEwgMTY1IDMwMCBMIDE2NSA0MDggQyAxNjUgNDIwIDE3NSA0MzAgMTg3IDQzMCBMIDQ2MyA0MzAgQyA0NzUgNDMwIDQ4NSA0MjAgNDg1IDQwOCBMIDQ4NSAzMDAgTCA0ODUgMjUyIEMgNDg1IDI0MCA0NzUgMjMwIDQ2MyAyMzAgTCAxODcgMjMwIHogTSAzNjAgMjU2IEEgNzAgNzIgMCAwIDEgNDMwIDMyOCBBIDcwIDcyIDAgMCAxIDM2MCA0MDAgQSA3MCA3MiAwIDAgMSAyOTAgMzI4IEEgNzAgNzIgMCAwIDEgMzYwIDI1NiB6Ii8+PGNpcmNsZSBjeD0iMzYwIiBjeT0iMzMwIiByPSI0MSIvPjxwYXRoIGQ9Im0yMDUgMjI1IDUtMTAgMjAgMCA1IDEwLTMwIDAiLz48cGF0aCBkPSJNMjg1IDIwMEwyNzAgMjI1IDM3NiAyMjUgMzYxIDIwMCAyODUgMjAwek0zMTAgMjA1TDMzNyAyMDUgMzM3IDIxOCAzMTAgMjE4IDMxMCAyMDV6Ii8+PHBhdGggZD0ibTQwNSAyMjUgNS0xMCAyMCAwIDUgMTAtMzAgMCIvPjwvZz48L3N2Zz4=)}.dropimage input{left:0;width:100%;height:100%;border:0;margin:0;padding:0;opacity:0;position:absolute}.tabs{position:relative;overflow:hidden}.tabs>label img{float:left;margin-left:.6em}.tabs>.row{width:calc(100% + 2 * .6em);display:table;table-layout:fixed;position:relative;padding-left:0;transition:all .3s;border-spacing:0;margin:0}.tabs>.row:before,.tabs>.row:after{display:none}.tabs>.row>*,.tabs>.row img{display:table-cell;vertical-align:top;margin:0;width:100%}.tabs>input{display:none}.tabs>input+*{width:100%}.tabs>input+label{width:auto}.two.tabs>.row{width:200%;left:-100%}.two.tabs>input:nth-of-type(1):checked ~ .row{margin-left:100%}.two.tabs>label img{width:48%;margin:4% 0 4% 4%}.three.tabs>.row{width:300%;left:-200%}.three.tabs>input:nth-of-type(1):checked ~ .row{margin-left:200%}.three.tabs>input:nth-of-type(2):checked ~ .row{margin-left:100%}.three.tabs>label img{width:30%;margin:5% 0 5% 5%}.four.tabs>.row{width:400%;left:-300%}.four.tabs>input:nth-of-type(1):checked ~ .row{margin-left:300%}.four.tabs>input:nth-of-type(2):checked ~ .row{margin-left:200%}.four.tabs>input:nth-of-type(3):checked ~ .row{margin-left:100%}.four.tabs>label img{width:22%;margin:4% 0 4% 4%}.tabs>label:first-of-type img{margin-left:0}[data-tooltip]{position:relative}[data-tooltip]:after,[data-tooltip]:before{position:absolute;z-index:10;opacity:0;border-width:0;height:0;padding:0;overflow:hidden;transition:opacity .6s ease, height 0s ease .6s;top:calc(100% - 6px);left:0;margin-top:12px}[data-tooltip]:after{margin-left:0;font-size:.8em;background:#111;content:attr(data-tooltip);white-space:nowrap}[data-tooltip]:before{content:'';width:0;height:0;border-width:0;border-style:solid;border-color:transparent transparent #111;margin-top:0;left:10px}[data-tooltip]:hover:after,[data-tooltip]:focus:after,[data-tooltip]:hover:before,[data-tooltip]:focus:before{opacity:1;border-width:6px;height:auto}[data-tooltip]:hover:after,[data-tooltip]:focus:after{padding:.45em .9em}.tooltip-top:after,.tooltip-top:before{top:auto;bottom:calc(100% - 6px);left:0;margin-bottom:12px}.tooltip-top:before{border-color:#111 transparent transparent;margin-bottom:0;left:10px}.tooltip-right:after,.tooltip-right:before{left:100%;margin-left:6px;margin-top:0;top:0}.tooltip-right:before{border-color:transparent #111 transparent transparent;margin-left:-6px;left:100%;top:7px}.tooltip-left:after,.tooltip-left:before{right:100%;margin-right:6px;left:auto;margin-top:0;top:0}.tooltip-left:before{border-color:transparent transparent transparent #111;margin-right:-6px;right:100%;top:7px} diff --git a/DBUtils/Docs/pool.gif b/pool.gif similarity index 100% rename from DBUtils/Docs/pool.gif rename to pool.gif diff --git a/setup.py b/setup.py deleted file mode 100755 index 37b3215..0000000 --- a/setup.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Setup Script for DBUtils""" - -__version__ = '1.1.1b1' - -from sys import version_info - -py_version = version_info[:2] -if not (2, 3) <= py_version < (3, 0): - raise ImportError('Python %d.%d is not supported by DBUtils.' % py_version) - -import warnings -warnings.filterwarnings('ignore', 'Unknown distribution option') - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - try: - from distutils.dist import DistributionMetadata - except ImportError: - pass - else: - try: - DistributionMetadata.classifiers - except AttributeError: - DistributionMetadata.classifiers = None - try: - DistributionMetadata.download_url - except AttributeError: - DistributionMetadata.download_url = None - try: - DistributionMetadata.package_data - except AttributeError: - DistributionMetadata.package_data = None - try: - DistributionMetadata.zip_safe - except AttributeError: - DistributionMetadata.zip_safe = None - -setup( - name='DBUtils', - version=__version__, - description='Database connections for multi-threaded environments.', - long_description='''\ -DBUtils is a suite of tools providing solid, persistent and pooled connections -to a database that can be used in all kinds of multi-threaded environments -like Webware for Python or other web application servers. The suite supports -DB-API 2 compliant database interfaces and the classic PyGreSQL interface. -''', - classifiers=['Development Status :: 4 - Beta', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.3', - 'Programming Language :: Python :: 2.4', - 'Programming Language :: Python :: 2.5', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Topic :: Database', - 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', - 'Topic :: Software Development :: Libraries :: Python Modules' - ], - author='Christoph Zwerschke', - author_email='cito@online.de', - url='https://github.com/Cito/DBUtils', - platforms=['any'], - license='MIT License', - packages=['DBUtils', 'DBUtils.Examples', 'DBUtils.Tests'], - package_data={'DBUtils': ['Docs/*']}, - zip_safe=0 -) diff --git a/setversion.py b/setversion.py deleted file mode 100755 index a2a2a18..0000000 --- a/setversion.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python - -"""Set version. - -This script sets the DBUtils version number information -consistently in all files of the distribution. - -""" - -# Version format is (Major, Minor, Sub, Alpha/Beta/etc) -# The Sub is optional, and if 0 is not returned. -# Examples: (0, 8, 1, 'b1'), (0, 8, 2) or (0, 9, 0, 'rc1') -# releaseDate format should be 'MM/DD/YY'. - -# Update this to change the current version and release date: -# version = ('X', 'Y', 0) -version = (1, 1, 1, 'b1') -# releaseDate = '@@/@@/@@' -releaseDate = '01/31/17' - -# Verbose output (output unchanged files also): -verbose = False - -from glob import glob -import os, sys, re - -path = os.path.dirname(os.path.abspath(sys.argv[0])) -sys.path.append(path) -os.chdir(path) -print "Setversion", path - - -def versionString(version): - """Create version string. - - For a sequence containing version information such as (2, 0, 0, 'pre'), - this returns a printable string such as '2.0pre'. - The micro version number is only excluded from the string if it is zero. - - """ - ver = map(str, version) - numbers, rest = ver[:ver[2] == '0' and 2 or 3], ver[3:] - return '.'.join(numbers) + '-'.join(rest) - -versionString = versionString(version) - -if versionString == 'X.Y': - print "Please set the version." - sys.exit(1) -if releaseDate == '@@/@@/@@': - print "Please set the release date." - sys.exit(1) - - -class Replacer: - """Class to handle substitutions in a file.""" - - def __init__(self, *args): - self._subs = list(args) - - def add(self, search, replace): - self._subs.append((re.compile(search,re.M), replace)) - - def replaceInStr(self, data): - for search, replace in self._subs: - data = re.sub(search, replace, data) - return data - - def replaceInFile(self, filename): - data = open(filename).read() - newdata = self.replaceInStr(data) - if data == newdata: - if verbose: - print 'Unchanged ' + filename - else: - print 'Updating ' + filename - open(filename, 'w').write(newdata) - - def replaceGlob(self, pattern): - for file in glob(pattern): - if os.path.exists(file): - self.replaceInFile(file) - - -pyReplace = Replacer() -pyReplace.add(r"(__version__\s*=\s*)'.*'", r"\g<1>%s" % repr(versionString)) - -propReplace = Replacer() -propReplace.add(r"(version\s*=\s*).*", r"\g<1>%s" % repr(version)) -propReplace.add(r"(releaseDate\s*=\s*).*", r"\g<1>%s" % repr(releaseDate)) - -htmlReplace = Replacer() -htmlReplace.add(r"[^<]*", - r" %s " % versionString) -htmlReplace.add(r"[^<]*", - r" %s " % releaseDate) - -rstReplace = Replacer() -rstReplace.add(r"^:(.+)?: (X|\d+)\.(Y|\d+)(\.\d+)?$", r":\1: %s" % versionString) -rstReplace.add(r"^:(.+)?: (@|\d){2}/(@|\d){2}/(@|\d){2}$", r":\1: %s" % releaseDate) - -# Replace in Python files: -pyReplace.replaceGlob('*.py') -pyReplace.replaceGlob('DBUtils/*.py') -pyReplace.replaceGlob('DBUtils/*/*.py') - -# Replace in Properties files: -propReplace.replaceGlob('DBUtils/Properties.py') - -# Replace in existing HTML: -htmlReplace.replaceGlob('DBUtils/Docs/*.html') - -# Replace in reStructuredText files: -rstReplace.replaceGlob('DBUtils/Docs/*.txt') From 6d51d66f15fd502b7543098ea40a1e325a94e2e8 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 4 Feb 2017 15:05:50 +0100 Subject: [PATCH 02/23] Keep source and generator only on master branch --- UsersGuide.de.rst | 592 ---------------------------------------------- UsersGuide.rst | 548 ------------------------------------------ buildhtml.py | 35 --- 3 files changed, 1175 deletions(-) delete mode 100644 UsersGuide.de.rst delete mode 100644 UsersGuide.rst delete mode 100755 buildhtml.py diff --git a/UsersGuide.de.rst b/UsersGuide.de.rst deleted file mode 100644 index 3f3aa55..0000000 --- a/UsersGuide.de.rst +++ /dev/null @@ -1,592 +0,0 @@ -Benutzeranleitung für DBUtils -+++++++++++++++++++++++++++++ - -:Version: 1.1.1b1 -:Released: 01/31/17 -:Translations: English_ | German - -.. _English: UsersGuide.html - -.. contents:: Inhalt - - -Zusammenfassung -=============== - -DBUtils_ ist eine Sammlung von Python-Modulen, mit deren Hilfe man in Python_ -geschriebene Multithread-Anwendungen auf sichere und effiziente Weise an -Datenbanken anbinden kann. DBUtils wurde mit Blick auf `Webware for Python`_ -als Anwendung und PyGreSQL_ als PostgreSQL_-Datenbankadapter entwickelt, -kann aber für beliebige Python-Anwendungen und beliebige auf `DB-API 2`_ -beruhende Python-Datenbankadapter verwendet werden. - - -Module -====== - -DBUtils ist als Python-Package realisiert worden, das aus zwei verschiedenen -Gruppen von Modulen besteht: Einer Gruppe zur Verwendung mit beliebigen -DB-API-2-Datenbankadaptern, und einer Gruppe zur Verwendung mit dem klassischen PyGreSQL-Datenbankadapter-Modul. - -+-------------------+----------------------------------------------+ -| Allgemeine Variante für beliebige DB-API-2-Adapter | -+===================+==============================================+ -| SteadyDB.py | Gehärtete DB-API-2-Datenbankverbindungen | -+-------------------+----------------------------------------------+ -| PooledDB.py | Pooling für DB-API-2-Datenbankverbindungen | -+-------------------+----------------------------------------------+ -| PersistentDB.py | Persistente DB-API-2-Datenbankverbindungen | -+-------------------+----------------------------------------------+ -| SimplePooledDB.py | Einfaches Pooling für DB-API 2 | -+-------------------+----------------------------------------------+ - -+-------------------+----------------------------------------------+ -| Variante speziell für den klassischen PyGreSQL-Adapter | -+===================+==============================================+ -| SteadyPg.py | Gehärtete klassische PyGreSQL-Verbindungen | -+-------------------+----------------------------------------------+ -| PooledPg.py | Pooling für klassische PyGreSQL-Verbindungen | -+-------------------+----------------------------------------------+ -| PersistentPg.py | Persistente klassische PyGreSQL-Verbindungen | -+-------------------+----------------------------------------------+ -| SimplePooledPg.py | Einfaches Pooling für klassisches PyGreSQL | -+-------------------+----------------------------------------------+ - -Die Abhängigkeiten der Module in der Variante für beliebige DB-API-2-Adapter -sind im folgenden Diagramm dargestellt: - -.. image:: dbdep.gif - -Die Abhängigkeiten der Module in der Variante für den klassischen -PyGreSQL-Adapter sehen ähnlich aus: - -.. image:: pgdep.gif - - -Download -======== - -Die aktuelle Version von DBUtils kann vom Python Package Index -heruntergeladen werden:: - - https://pypi.python.org/pypi/DBUtils - -Das Source-Code-Repository befindet sich hier auf GitHub:: - - https://github.com/Cito/DBUtils - - -Installation -============ - -Installation als eigenständiges Paket -------------------------------------- -Wenn Sie DBUtils für andere Anwendungen als Webware for Python verwenden -möchten, empfiehlt es sich, das Paket auf die übliche Weise zu installieren:: - - python setup.py install - -Sie können auch `pip`_ für Download und Installation verwenden:: - - pip install DBUtils - -.. _pip: https://pip.pypa.io/ - -Installation als Unterpaket (Plug-In) von Webware for Python ------------------------------------------------------------- -Wenn Sie DBUtils nur als Ergänzung für das Web-Framework Webware for Python -verwenden wollen, sollten Sie DBUtils als Webware-Plug-In installieren:: - - python setup.py install --install-lib=/pfad/zu/Webware - -Ersetzen Sie ``/pfad/zu/Webware`` hierbei durch den Pfad zum Wurzelverzeichnis -der Installation von Webware for Python. Sie müssen auch das Installationsskript -von Webware for Python laufen lassen, wenn dies noch nicht geschehen ist, oder -wenn Sie DBUtils in die Webware-Dokumentation integrieren wollen:: - - cd /pfad/zu/Webware - python install.py - - -Anforderungen -============= - -DBUtils arbeitet mit Python_ 2.3 oder einer neueren Version von Python 2. -Die Module in der Variante für klassisches PyGreSQL benötigen PyGreSQL_ -Version 3.4 oder höher, während die Module in der allgemeinen Variante -für DB-API 2 mit jedem beliebigen Python-Datenbankadapter-Modul -zusammenarbeiten, das auf `DB-API 2`_ basiert. - - -Funktionalität -============== - -Dieser Abschnitt verwendet nur die Bezeichnungen der DB-API-2-Variante, aber -Entsprechendes gilt auch für die PyGreSQL-Variante. - - -SimplePooledDB --------------- -``DBUtils.SimplePooledDB`` ist eine sehr elementare Referenz-Implementierung -eines Pools von Datenbankverbindungen. Hiermit ist ein Vorratsspeicher an -Datenbankverbindungen gemeint, aus dem sich die Python-Anwendung bedienen kann. -Diese Implementierung ist weit weniger ausgefeilt als das eigentliche -``PooledDB``-Modul und stellt insbesondere keine Ausfallsicherung zur Verfügung. -``DBUtils.SimplePooledDB`` ist im Wesentlichen identisch mit dem zu Webware for -Python gehörenden Modul ``MiscUtils.DBPool``. Es ist eher zur Verdeutlichung -des Konzepts gedacht, als zum Einsatz im produktiven Betrieb. - -SteadyDB --------- -``DBUtils.SteadyDB`` ist ein Modul, das "gehärtete" Datenbankverbindungen -bereitstellt, denen gewöhnlichen Verbindungen eines DB-API-2-Datenbankadapters -zugrunde liegen. Eine "gehärtete" Verbindung wird bei Zugriff automatisch, -ohne dass die Anwendung dies bemerkt, wieder geöffnet, wenn sie geschlossen -wurde, die Datenbankverbindung unterbrochen wurde, oder wenn sie öfter als -ein optionales Limit genutzt wurde. - -Ein typisches Beispiel wo dies benötig wird, ist, wenn die Datenbank neu -gestartet wurde, während Ihre Anwendung immer noch läuft und Verbindungen -zur Datenbank offen hat, oder wenn Ihre Anwendung auf eine entfernte Datenbank -über ein Netzwerk zugreift, das durch eine Firewall geschützt ist, und die -Firewall neu gestartet wurde und dabei ihren Verbindungsstatus verloren hat. - -Normalerweise benutzen Sie das ``SteadyDB``-Modul nicht direkt; es wird aber -von den beiden nächsten Modulen benötigt, ``PersistentDB`` und ``PooledDB``. - -PersistentDB ------------- -``DBUtils.PersistentDB`` stellt gehärtete, thread-affine, persistente -Datenbankverbindungen zur Verfügung, unter Benutzung eines beliebigen -DB-API-2-Datenbankadapters. Mit "thread-affin" und "persistent" ist -hierbei gemeint, dass die einzelnen Datenbankverbindungen den jeweiligen -Threads fest zugeordnet bleiben und während der Laufzeit des Threads nicht -geschlossen werden. - -Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie -``PersistentDB``-Datenbankverbindungen einsetzen: - -.. image:: persist.gif - -Immer wenn ein Thread eine Datenbankverbindung zum ersten Mal öffnet, wird -eine neue Datenbankverbindung geöffnet, die von da an immer wieder für genau -diesen Thread verwendet wird. Wenn der Thread die Datenbankverbindung schließt, -wird sie trotzdem weiter offen gehalten, damit beim nächsten Mal, wenn der -gleiche Thread wieder eine Datenbankverbindung anfordert, diese gleiche bereits -geöffnete Datenbankverbindung wieder verwendet werden kann. Die Verbindung wird -automatisch geschlossen, wenn der Thread beendet wird. - -Kurz gesagt versucht ``PersistentDB`` Datenbankverbindungen wiederzuverwerten, -um die Gesamteffizienz der Datenbankzugriffe Ihrer Multithread-Anwendungen zu -steigern, aber es wird dabei sichergestellt, dass verschiedene Threads niemals -die gleiche Verbindung benutzen. - -Daher arbeitet ``PersistentDB`` sogar dann problemlos, wenn der zugrunde -liegende DB-API-2-Datenbankadapter nicht thread-sicher auf der Verbindungsebene -ist, oder wenn parallele Threads Parameter der Datenbank-Sitzung verändern -oder Transaktionen mit mehreren SQL-Befehlen durchführen. - -PooledDB --------- -``DBUtils.PooledDB`` stellt, unter Benutzung eines beliebigen -DB-API-2-Datenbankadapters, einen Pool von gehärteten, thread-sicheren -Datenbankverbindungen zur Verfügung, die automatisch, ohne dass die Anwendung -dies bemerkt, wiederverwendet werden. - -Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie -``PooledDB``-Datenbankverbindungen einsetzen: - -.. image:: pool.gif - -Wie im Diagramm angedeutet, kann ``PooledDB`` geöffnete Datenbankverbindungen -den verschiedenen Threads beliebig zuteilen. Dies geschieht standardmäßig, wenn -Sie den Verbindungspool mit einem positiven Wert für ``maxshared`` einrichten -und der zugrunde liegende DB-API-2-Datenbankadapter auf der Verbindungsebene -thread-sicher ist, aber sie können auch dedizierte Datenbankverbindungen -anfordern, die nicht von anderen Threads verwendet werden sollen. Neben dem -Pool gemeinsam genutzter Datenbankverbindungen ("shared pool") können Sie auch -einen Pool von mindestens ``mincached`` und höchstens ``maxcached`` inaktiven -Verbindungen auf Vorrat einrichten ("idle pool"), aus dem immer dann geschöpft -wird, wenn ein Thread eine dedizierte Datenbankverbindung anfordert, oder wenn -der Pool gemeinsam genutzter Datenbankverbindungen noch nicht voll ist. -Wenn ein Thread eine Datenbankverbindung schließt, die auch von keinem anderen -Thread mehr benutzt wird, wird sie an den Vorratsspeicher inaktiver -Datenbankverbindungen zurückgegeben, damit sie wiederverwertet werden kann. - -Wenn der zugrunde liegende DB-API-Datenbankadapter nicht thread-sicher ist, -werden Thread-Locks verwendet, um sicherzustellen, dass die -``PooledDB``-Verbindungen dennoch thread-sicher sind. Sie brauchen sich also -hierum keine Sorgen zu machen, aber Sie sollten darauf achten, dedizierte -Datenbankverbindungen zu verwenden, sobald Sie Parameter der Datenbanksitzung -verändern oder Transaktionen mit mehreren SQL-Befehlen ausführen. - -Die Qual der Wahl ------------------ -Sowohl ``PersistentDB`` als auch ``PooledDB`` dienen dem gleichen Zweck, -nämlich die Effizienz des Datenbankzugriffs durch Wiederverwendung von -Datenbankverbindungen zu steigern, und dabei gleichzeitig die Stabilität -zu gewährleisten, selbst wenn die Datenbankverbindung unterbrochen wird. - -Welches der beiden Module sollte also verwendet werden? Nach den obigen -Erklärungen ist es klar, dass ``PersistentDB`` dann sinnvoller ist, wenn -Ihre Anwendung eine gleich bleibende Anzahl Threads verwendet, die häufig -auf die Datenbank zugreifen. In diesem Fall werden Sie ungefähr die gleiche -Anzahl geöffneter Datenbankverbindungen erhalten. Wenn jedoch Ihre Anwendung -häufig Threads beendet und neu startet, dann ist ``PooledDB`` die bessere -Lösung, die auch mehr Möglichkeiten zur Feineinstellung zur Verbesserung -der Effizienz erlaubt, insbesondere bei Verwendung eines thread-sicheren -DB-API-2-Datenbankadapters. - -Da die Schnittstellen beider Module sehr ähnlich sind, können Sie recht einfach -von einem Modul zum anderen wechseln und austesten, welches geeigneter ist. - - -Benutzung -========= - -Die Benutzung aller Module ist zwar recht ähnlich, aber es gibt vor allem bei -der Initialisierung auch einige Unterschiede, sowohl zwischen den "Pooled"- -und den "Persistent"-Varianten, als auch zwischen den DB-API-2- und den -PyGreSQL-Varianten. - -Wir werden hier nur auf das ``PersistentDB``-Modul und das etwas kompliziertere -``PooledDB``-Modul eingehen. Einzelheiten zu den anderen Modulen finden Sie -in deren Docstrings. Unter Verwendung der Python-Interpreter-Konsole können Sie -sich die Dokumentation des ``PooledDB``-Moduls wie folgt anzeigen lassen (dies -funktioniert entsprechend auch mit den anderen Modulen):: - - help(PooledDB) - -PersistentDB ------------- -Wenn Sie das ``PersistentDB``-Modul einsetzen möchten, müssen Sie zuerst einen -Generator für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten, -indem Sie eine Instanz der Klasse ``PersistentDB`` erzeugen, wobei Sie folgende -Parameter angeben müssen: - -* ``creator``: entweder eine Funktion, die neue DB-API-2-Verbindungen - erzeugt, oder ein DB-API-2-Datenbankadapter-Modul - -* ``maxusage``: Obergrenze dafür, wie oft eine einzelne Verbindung - wiederverwendet werden darf (der Standardwert ``0`` oder ``None`` - bedeutet unbegrenzte Wiederverwendung) - - Sobald diese Obergrenze erreicht wird, wird die Verbindung zurückgesetzt. - -* ``setsession``: eine optionale Liste von SQL-Befehlen zur Initialisierung - der Datenbanksitzung, z.B. ``["set datestyle to german", ...]`` - -* ``failures``: eine optionale Exception-Klasse oder ein Tupel von Exceptions - bei denen die Ausfallsicherung zum Tragen kommen soll, falls die Vorgabe - (OperationalError, InternalError) nicht geeignet sein sollte - -* ``ping``: mit diesem Parameter kann eingestellt werden, wann Verbindungen - mit der ``ping()``-Methode geprüft werden, falls eine solche vorhanden ist - (``0`` = ``None`` = nie, ``1`` = Standardwert = immer wenn neu angefragt, - ``2`` = vor Erzeugen eines Cursors, ``4`` = vor dem Ausführen von Abfragen, - ``7`` = immer, und alle Bitkombinationen dieser Werte) - -* ``closeable``: wenn dies auf ``True`` gesetzt wird, dann wird das Schließen - von Verbindungen erlaubt, normalerweise wird es jedoch ignoriert - -* ``threadlocal``: eine optionale Klasse zur Speicherung thread-lokaler Daten, - die anstelle unserer Python-Implementierung benutzt wird (threading.local - ist schneller, kann aber nicht in allen Fällen verwendet werden) - -* Die als ``creator`` angegebene Funktion oder die Funktion ``connect`` - des DB-API-2-Datenbankadapter-Moduls erhalten alle weiteren Parameter, - wie ``host``, ``database``, ``user``, ``password`` usw. Sie können einige - oder alle dieser Parameter in Ihrer eigenen ``creator``-Funktion setzen, was - ausgefeilte Mechanismen zur Ausfallsicherung und Lastverteilung ermöglicht. - -Wenn Sie beispielsweise ``pgdb`` als DB-API-2-Datenbankadapter verwenden, und -möchten, dass jede Verbindung Ihrer lokalen Datenbank ``meinedb`` 1000 mal -wiederverwendet werden soll, sieht die Initialisierung so aus:: - - import pgdb # importiere das verwendete DB-API-2-Modul - from DBUtils.PersistentDB import PersistentDB - persist = PersistentDB(pgdb, 1000, database='meinedb') - -Nachdem Sie den Generator mit diesen Parametern eingerichtet haben, können -Sie derartige Datenbankverbindungen von da an wie folgt anfordern:: - - db = persist.connection() - -Sie können diese Verbindungen verwenden, als wären sie gewöhnliche -DB-API-2-Datenbankverbindungen. Genauer genommen erhalten Sie die -"gehärtete" ``SteadyDB``-Version der zugrunde liegenden DB-API-2-Verbindung. - -Wenn Sie eine solche persistente Verbindung mit ``db.close()`` schließen, -wird dies stillschweigend ignoriert, denn sie würde beim nächsten Zugriff -sowieso wieder geöffnet, und das wäre nicht im Sinne persistenter Verbindungen. -Stattdessen wird die Verbindung automatisch dann geschlossen, wenn der Thread -endet. Sie können dieses Verhalten ändern, indem Sie den Parameter namens -``closeable`` setzen. - -Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode -``begin()`` eingeleiten werden müssen. Hierdurch wird sichergestellt, dass -das transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion -ausgesetzt wird, und dass die Verbindung zurückgerollt wird, before sie vom -gleichen Thread erneut benutzt wird. - -Das Holen einer Verbindung kann etwas beschleunigt werden, indem man den -Parameter ``threadlocal`` auf ``threading.local`` setzt; dies könnte aber in -einigen Umgebungen nicht funktionieren (es ist zum Beispiel bekannt, dass -``mod_wsgi`` hier Probleme bereitet, da es Daten, die mit ``threading.local`` -gespeichert wurden, zwischen Requests löscht). - -PooledDB --------- -Wenn Sie das ``PooledDB``-Modul einsetzen möchten, müssen Sie zuerst einen -Pool für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten, -indem Sie eine Instanz der Klasse ``PooledDB`` erzeugen, wobei Sie folgende -Parameter angeben müssen: - -* ``creator``: entweder eine Funktion, die neue DB-API-2-Verbindungen - erzeugt, oder ein DB-API-2-Datenbankadapter-Modul - -* ``mincached`` : die anfängliche Anzahl inaktiver Verbindungen, die auf - Vorrat gehalten werden sollen (der Standardwert ``0`` bedeutet, dass beim - Start keine Verbindungen geöffnet werden) - -* ``maxcached``: Obergrenze für die Anzahl inaktiver Verbindungen, die auf - Vorrat gehalten werden sollen (der Standardwert ``0`` oder ``None`` bedeutet - unbegrenzte Größe des Vorratsspeichers) - -* ``maxshared``: Obergrenze für die Anzahl gemeinsam genutzer Verbindungen - (der Standardwert ``0`` oder ``None`` bedeutet, dass alle Verbindungen - dediziert sind) - - Wenn diese Obergrenze erreicht wird, werden Verbindungen wiederverwendet, - wenn diese als wiederverwendbar angefordert werden. - -* ``maxconnections``: Obergrenze für die Anzahl an Datenbankverbindungen, - die insgesamt überhaupt erlaubt werden sollen (der Standardwert ``0`` - oder ``None`` bedeutet unbegrenzte Anzahl von Datenbankverbindungen) - -* ``blocking``: bestimmt das Verhalten bei Überschreitung dieser Obergrenze - - Wenn dies auf ``True`` gesetzt wird, dann wird so lange gewartet, bis die - Anzahl an Datenbankverbindungen wieder abnimmt, normalerweise wird jedoch - sofort eine Fehlermeldung ausgegeben. - -* ``maxusage``: Obergrenze dafür, wie oft eine einzelne Verbindung - wiederverwendet werden darf (der Standardwert ``0`` oder ``None`` - bedeutet unbegrenzte Wiederverwendung) - - Sobald diese Obergrenze erreicht wird, wird die Verbindung automatisch - zurückgesetzt (geschlossen und wieder neu geöffnet). - -* ``setsession``: eine optionale Liste von SQL-Befehlen zur Initialisierung - der Datenbanksitzung, z.B. ``["set datestyle to german", ...]`` - -* ``reset``: wie Verbindungen zurückgesetzt werden sollen, bevor sie wieder - in den Verbindungspool zurückgegeben werden (``False`` oder ``None`` - um mit ``begin()`` gestartete Transaktionen zurückzurollen, der Standardwert - ``True`` rollt sicherheitshalber mögliche Transaktionen immer zurück) - -* ``failures``: eine optionale Exception-Klasse oder ein Tupel von Exceptions - bei denen die Ausfallsicherung zum Tragen kommen soll, falls die Vorgabe - (OperationalError, InternalError) nicht geeignet sein sollte - -* ``ping``: mit diesem Parameter kann eingestellt werden, wann Verbindungen - mit der ``ping()``-Methode geprüft werden, falls eine solche vorhanden ist - (``0`` = ``None`` = nie, ``1`` = Standardwert = immer wenn neu angefragt, - ``2`` = vor Erzeugen eines Cursors, ``4`` = vor dem Ausführen von Abfragen, - ``7`` = immer, und alle Bitkombinationen dieser Werte) - -* Die als ``creator`` angegebene Funktion oder die Funktion ``connect`` - des DB-API-2-Datenbankadapter-Moduls erhalten alle weiteren Parameter, - wie ``host``, ``database``, ``user``, ``password`` usw. Sie können einige - oder alle dieser Parameter in Ihrer eigenen ``creator``-Funktion setzen, was - ausgefeilte Mechanismen zur Ausfallsicherung und Lastverteilung ermöglicht. - -Wenn Sie beispielsweise ``pgdb`` als DB-API-2-Datenbankadapter benutzen, -und einen Pool von mindestens fünf Datenbankverbindungen zu Ihrer Datenbank -``meinedb`` verwenden möchten, dann sieht die Initialisierung so aus:: - - import pgdb # importiere das verwendete DB-API-2-Modul - from DBUtils.PooledDB import PooledDB - pool = PooledDB(pgdb, 5, database='meinedb') - -Nachdem Sie den Pool für Datenbankverbindungen so eingerichtet haben, können -Sie Verbindungen daraus wie folgt anfordern:: - - db = pool.connection() - -Sie können diese Verbindungen verwenden, als wären sie gewöhnliche -DB-API-2-Datenbankverbindungen. Genauer genommen erhalten Sie die -"gehärtete" ``SteadyDB``-Version der zugrunde liegenden DB-API-2-Verbindung. - -Bitte beachten Sie, dass die Verbindung von anderen Threads mitgenutzt werden -kann, wenn Sie den Parameter ``maxshared`` auf einen Wert größer als Null -gesetzt haben, und der zugrunde liegende DB-API-2-Datenbankadapter dies erlaubt. -Eine dedizierte Datenbankverbindung, die garantiert nicht von anderen Threads -mitgenutzt wird, fordern Sie wie folgt an:: - - db = pool.connection(shareable=False) - -Stattdessen können Sie eine dedizierte Verbindung auch wie folgt erhalten:: - - db = pool.dedicated_connection() - -Wenn Sie die Datenbankverbindung nicht mehr benötigen, sollten Sie diese sofort -wieder mit ``db.close()`` an den Pool zurückgeben. Sie können auf die gleiche -Weise eine neue Verbindung erhalten. - -*Warnung:* In einer Multithread-Umgebung benutzen Sie niemals:: - - pool.connection().cursor().execute(...) - -Dies würde die Datenbankverbindung zu früh zur Wiederverwendung zurückgeben, -was fatale Folgen haben könnte, wenn die Verbindungen nicht thread-sicher sind. -Stellen Sie sicher, dass die Verbindungsobjekte so lange vorhanden sind, wie -sie gebraucht werden, etwa so:: - - db = pool.connection() - cur = db.cursor() - cur.execute(...) - res = cur.fetchone() - cur.close() # oder del cur - db.close() # oder del db - -Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode -``begin()`` eingeleiten werden müssen. Hierdurch wird sichergestellt, -dass die Verbindung nicht mehr mit anderen Threads geteilt wird, dass das -transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion -ausgesetzt wird, und dass die Verbindung zurückgerollt wird, bevor sie -wieder an den Verbindungspool zurückgegeben wird. - -Benutzung in Webware for Python -------------------------------- -Wenn Sie DBUtils verwenden, um von Servlets des Web-Frameworks `Webware -for Python`_ auf eine Datenbank zuzugreifen, dann müssen Sie sicherstellen, -dass die Generatoren zur Erzeugung von Datenbankverbindungen nur einmal -eingerichtet werden, wenn die Anwendung startet, und nicht jedes Mal, wenn -eine Servlet-Instanz erzeugt wird. Den hierfür nötigen Code können Sie -bei der Basis-Servlet-Klasse einfügen, dort wo das Modul oder die Klasse -initialisiert wird, oder Sie können die Funktion ``contextInitialize()`` -im ``__init__.py``-Skript Ihres Anwendungskontextes verwenden. - -Das zusammen mit DButils ausgelieferte Verzeichnis ``Examples`` enthält -einen Beispielkontext für Webware for Python, der eine kleine Demo-Datenbank -verwendet, um Teilnehmer an einer Seminarreihe zu verwalten (die Idee für -dieses Beispiel wurde dem Artikel "`The Python DB-API`_" von Andrew Kuchling -entnommen). - -Der Beispielkontext kann konfiguriert werden, indem entweder eine Konfig-Datei -``Configs/Database.config`` angelegt wird, oder indem die Standard-Parameter -direkt im Beispielservlet ``Examples/DBUtilsExample.py`` geändert werden. -Auf diese Weise können Sie einen passenden Datenbanknutzer und sein Passwort -festlegen, sowie den zugrunde liegenden Datenbankadapter auswählen (das -klassische PyGreSQL-Modul oder irgendein DB-API-2-Modul). Wenn der Parameter -``maxcached`` vorhanden ist, verwendet das Beispielservlet die -``Pooled``-Variante, andernfalls die ``Persistent``-Variante. - - -Anmerkungen -=========== -Wenn Sie einen der bekannten "Object-Relational Mapper" SQLObject_ oder -SQLAlchemy_ verwenden, dann benötigen Sie DBUtils nicht, denn diese haben -ihre eigenen Mechanismen zum Pooling von Datenbankverbindungen eingebaut. -Tatsächlich hat SQLObject 2 (SQL-API) das Pooling in eine separate Schicht -ausgelagert, in der Code von DBUtils verwendet wird. - -Wenn Sie eine Lösung verwenden wie den Apache-Webserver mit mod_python_ -oder mod_wsgi_, dann sollten Sie bedenken, dass Ihr Python-Code normalerweise -im Kontext der Kindprozesse des Webservers läuft. Wenn Sie also das -``PooledDB``-Modul einsetzen, und mehrere dieser Kindprozesse laufen, dann -werden Sie ebensoviele Pools mit Datenbankverbindungen erhalten. Wenn diese -Prozesse viele Threads laufen lassen, dann mag dies eine sinnvoller Ansatz -sein, wenn aber diese Prozesse nicht mehr als einen Worker-Thread starten, -wie im Fall des Multi-Processing Moduls "prefork" für den Apache-Webserver, -dann sollten Sie auf eine Middleware für das Connection-Pooling zurückgreifen, -die Multi-Processing unterstützt, wie zum Beispiel pgpool_ oder pgbouncer_ -für die PostgreSQL-Datenbank. - - -Zukunft -======= -Einige Ideen für zukünftige Verbesserungen: - -* Alternativ zur Obergrenze in der Anzahl der Nutzung einer Datenbankverbindung - könnte eine maximale Lebensdauer für die Verbindung implementiert werden. -* Es könnten Module ``MonitorDB`` und ``MonitorPg`` hinzugefügt werden, die - in einem separaten Thread ständig den "idle pool" und eventuell auch den - "shared pool" bzw. die persistenten Verbindungen überwachen. Wenn eine - unterbrochene Datenbankverbindung entdeckt wird, wird diese automatisch durch - den Monitor-Thread wiederhergestellt. Dies ist in einem Szenario sinnvoll, - bei dem die Datenbank einer Website jede Nacht neu gestartet wird. Ohne - den Monitor-Thread würden die Benutzer morgens eine kleine Verzögerung - bemerken, weil erst dann die unterbrochenen Datenbankverbindungen entdeckt - würden und sich der Pool langsam wieder neu aufbaut. Mit dem Monitor-Thread - würde dies schon während der Nacht passieren, kurz nach der Unterbrechung. - Der Monitor-Thread könnte auch so konfiguriert werden, dass er überhaupt - täglich den Verbindungspool erneuert, kurz bevor die Benutzer erscheinen. -* Optional sollten Benutzung, schlechte Verbindungen und Überschreitung von - Obergrenzen in Logs gespeichert werden können. - - -Fehlermeldungen und Feedback -============================ -Bitte Senden Sie Fehlermeldungen, Patches und Feedback direkt an den -Autor (unter Verwendung der unten angegebenen E-Mail-Adresse). - -Probleme, die Webware betreffen, können auch in der `Webware for Python -mailing list`_ diskutiert werden. - - -Links -===== -Einige Links zu verwandter und alternativer Software: - -* DBUtils_ -* Python_ -* `Webware for Python`_ Framework -* Python `DB-API 2`_ -* PostgreSQL_ Datenbank -* PyGreSQL_ Python-Adapter for PostgreSQL -* pgpool_ Middleware für Connection-Pooling mit PostgreSQL -* pgbouncer_ Middleware für Connection-Pooling mit PostgreSQL -* SQLObject_ Objekt-relationaler Mapper -* SQLAlchemy_ Objekt-relationaler Mapper - -.. _DBUtils: https://github.com/Cito/DBUtils -.. _Python: https://www.python.org -.. _Webware for Python: https://cito.github.io/w4py/ -.. _Webware for Python mailing list: https://lists.sourceforge.net/lists/listinfo/webware-discuss -.. _DB-API 2: https://www.python.org/dev/peps/pep-0249/ -.. _The Python DB-API: http://www.linuxjournal.com/article/2605 -.. _PostgresQL: https://www.postgresql.org/ -.. _PyGreSQL: http://www.pygresql.org/ -.. _SQLObject: http://www.sqlobject.org/ -.. _SQLAlchemy: http://www.sqlalchemy.org -.. _Apache: http://httpd.apache.org/ -.. _mod_python: http://modpython.org/ -.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi -.. _pgpool: http://www.pgpool.net/ -.. _pgbouncer: https://pgbouncer.github.io/ - - -Autoren -======= - -:Autor: Christoph Zwerschke - -:Beiträge: DBUtils benutzt Code, Anmerkungen und Vorschläge von - Ian Bicking, Chuck Esterbrook (Webware for Python), Dan Green (DBTools), - Jay Love, Michael Palmer, Tom Schwaller, Geoffrey Talvola, - Warren Smith (DbConnectionPool), Ezio Vernacotola, Jehiah Czebotar, - Matthew Harriger, Gregory Piñero und Josef van Eenbergen. - - -Copyright und Lizenz -==================== - -Copyright © 2005-2017 Christoph Zwerschke. -Alle Rechte vorbehalten. - -DBUtils ist freie und quelloffene Software, -lizenziert unter der `MIT-Lizenz`__. - -__ https://opensource.org/licenses/MIT diff --git a/UsersGuide.rst b/UsersGuide.rst deleted file mode 100644 index f259bca..0000000 --- a/UsersGuide.rst +++ /dev/null @@ -1,548 +0,0 @@ -DBUtils User's Guide -++++++++++++++++++++ - -:Version: 1.1.1b1 -:Released: 01/31/17 -:Translations: English | German_ - -.. _German: UsersGuide.de.html - -.. contents:: Contents - - -Synopsis -======== - -DBUtils_ is a suite of Python modules allowing to connect in a safe and -efficient way between a threaded Python_ application and a database. DBUtils -has been written in view of `Webware for Python`_ as the application and -PyGreSQL_ as the adapter to a PostgreSQL_ database, but it can be used -for any other Python application and `DB-API 2`_ conformant database adapter. - - -Modules -======= - -The DBUtils suite is realized as a Python package containing -two subsets of modules, one for use with arbitrary DB-API 2 modules, -the other one for use with the classic PyGreSQL module. - -+-------------------+------------------------------------------+ -| Universal DB-API 2 variant | -+===================+==========================================+ -| SteadyDB.py | Hardened DB-API 2 connections | -+-------------------+------------------------------------------+ -| PooledDB.py | Pooling for DB-API 2 connections | -+-------------------+------------------------------------------+ -| PersistentDB.py | Persistent DB-API 2 connections | -+-------------------+------------------------------------------+ -| SimplePooledDB.py | Simple pooling for DB-API 2 | -+-------------------+------------------------------------------+ - -+-------------------+------------------------------------------+ -| Classic PyGreSQL variant | -+===================+==========================================+ -| SteadyPg.py | Hardened classic PyGreSQL connections | -+-------------------+------------------------------------------+ -| PooledPg.py | Pooling for classic PyGreSQL connections | -+-------------------+------------------------------------------+ -| PersistentPg.py | Persistent classic PyGreSQL connections | -+-------------------+------------------------------------------+ -| SimplePooledPg.py | Simple pooling for classic PyGreSQL | -+-------------------+------------------------------------------+ - -The dependencies of the modules in the universal DB-API 2 variant -are as indicated in the following diagram: - -.. image:: dbdep.gif - -The dependencies of the modules in the classic PyGreSQL variant -are similar: - -.. image:: pgdep.gif - - -Download -======== - -You can download the actual version of DBUtils from -the Python Package Index at:: - - https://pypi.python.org/pypi/DBUtils - -The source code repository can be found here on GitHub:: - - https://github.com/Cito/DBUtils - - -Installation -============ - -Installation as a standalone (top-level) package ------------------------------------------------- -If you intend to use DBUtils from other applications than Webware for Python, -it is recommended to install the package in the usual way:: - - python setup.py install - -You can also use `pip`_ for download and installation:: - - pip install DBUtils - -.. _pip: https://pip.pypa.io/ - -Installation as a Webware for Python subpackage (plug-in) ---------------------------------------------------------- -If you want to use DBUtils as a supplement for the Webware for Python -framework only, you should install it as a Webware plug-in:: - - python setup.py install --install-lib=/path/to/Webware - -Replace ``/path/to/Webware`` with the path to the root directory of -your Webware for Python installation. You will also need to run the -Webware installer if this has not been done already or if you want to -integrate the DBUtils documentation into the Webware documentation:: - - cd path/to/Webware - python install.py - - -Requirements -============ - -DBUtils runs with Python_ 2.3 or newer Python 2 versions. The modules in -the classic PyGreSQL variant need PyGreSQL_ version 3.4 or above, while the -modules in the universal DB-API 2 variant run with any Python `DB-API 2`_ -compliant database interface module. - - -Functionality -============= - -This section will refer to the names in the DB-API 2 variant only, -but the same applies to the classic PyGreSQL variant. - -SimplePooledDB --------------- -``DBUtils.SimplePooledDB`` is a very basic reference implementation of -a pooled database connection. It is much less sophisticated than the -regular ``PooledDB`` module and is particularly lacking the failover -functionality. ``DBUtils.SimplePooledDB`` is essentially the same as -the ``MiscUtils.DBPool`` module that is part of Webware for Python. -You should consider it a demonstration of concept rather than something -that should go into production. - -SteadyDB --------- -``DBUtils.SteadyDB`` is a module implementing "hardened" connections -to a database, based on ordinary connections made by any DB-API 2 -database module. A "hardened" connection will transparently reopen upon -access when it has been closed or the database connection has been lost -or when it is used more often than an optional usage limit. - -A typical example where this is needed is when the database has been -restarted while your application is still running and has open connections -to the database, or when your application accesses a remote database in -a network that is separated by a firewall and the firewall has been -restarted and lost its state. - -Usually, you will not use the ``SteadyDB`` module directly; it merely serves -as a basis for the next two modules, ``PersistentDB`` and ``PooledDB``. - -PersistentDB ------------- -``DBUtils.PersistentDB`` implements steady, thread-affine, persistent -connections to a database, using any DB-API 2 database module. - -The following diagram shows the connection layers involved when you -are using ``PersistentDB`` connections: - -.. image:: persist.gif - -Whenever a thread opens a database connection for the first time, a new -connection to the database will be opened that will be used from now on -for this specific thread. When the thread closes the database connection, -it will still be kept open so that the next time when a connection is -requested by the same thread, this already opened connection can be used. -The connection will be closed automatically when the thread dies. - -In short: ``PersistentDB`` tries to recycle database connections to -increase the overall database access performance of your threaded application, -but it makes sure that connections are never shared between threads. - -Therefore, ``PersistentDB`` will work perfectly even if the underlying -DB-API module is not thread-safe at the connection level, and it will -avoid problems when other threads change the database session or perform -transactions spreading over more than one SQL command. - -PooledDB --------- -``DBUtils.PooledDB`` implements a pool of steady, thread-safe cached -connections to a database which are transparently reused, using any -DB-API 2 database module. - -The following diagram shows the connection layers involved when you -are using ``PooledDB`` connections: - -.. image:: pool.gif - -As the diagram indicates, ``PooledDB`` can share opened database connections -between different threads. This will happen by default if you set up the -connection pool with a positive value of ``maxshared`` and the underlying -DB-API 2 is thread-safe at the connection level, but you can also request -dedicated database connections that will not be shared between threads. -Besides the pool of shared connections, you can also set up a pool of -at least ``mincached`` and at the most ``maxcached`` idle connections that -will be used whenever a thread is requesting a dedicated database connection -or the pool of shared connections is not yet full. When a thread closes a -connection that is not shared any more, it is returned back to the pool of -idle connections so that it can be recycled again. - -If the underlying DB-API module is not thread-safe, thread locks will be -used to ensure that the ``PooledDB`` connections are thread-safe. So you -don't need to worry about that, but you should be careful to use dedicated -connections whenever you change the database session or perform transactions -spreading over more than one SQL command. - -Which one to use? ------------------ -Both ``PersistentDB`` and ``PooledDB`` serve the same purpose to improve -the database access performance by recycling database connections, while -preserving stability even if database connection will be disrupted. - -So which of these two modules should you use? From the above explanations -it is clear that ``PersistentDB`` will make more sense if your application -keeps a constant number of threads which frequently use the database. In -this case, you will always have the same amount of open database connections. -However, if your application frequently starts and ends threads, then it -will be better to use ``PooledDB``. The latter will also allow more -fine-tuning, particularly if you are using a thread-safe DB-API 2 module. - -Since the interface of both modules is similar, you can easily switch from -one to the other and check which one will suit better. - - -Usage -===== - -The usage of all the modules is similar, but there are also some differences -in the initialization between the "Pooled" and "Persistent" variants and also -between the universal DB-API 2 and the classic PyGreSQL variants. - -We will cover here only the ``PersistentDB`` module and the more complex -``PooledDB`` module. For the details of the other modules, have a look -at their module docstrings. Using the Python interpreter console, you can -display the documentation of the ``PooledDB`` module as follows (this -works analogously for the other modules):: - - help(PooledDB) - -PersistentDB ------------- -In order to make use of the ``PersistentDB`` module, you first need to set -up a generator for your kind of database connections by creating an instance -of ``PersistentDB``, passing the following parameters: - -* ``creator``: either an arbitrary function returning new DB-API 2 - connection objects or a DB-API 2 compliant database module - -* ``maxusage``: the maximum number of reuses of a single connection - (the default of ``0`` or ``None`` means unlimited reuse) - - Whenever the limit is reached, the connection will be reset. - -* ``setsession``: an optional list of SQL commands that may serve to - prepare the session, e.g. ``["set datestyle to german", ...]`` - -* ``failures``: an optional exception class or a tuple of exception classes - for which the connection failover mechanism shall be applied, - if the default (OperationalError, InternalError) is not adequate - -* ``ping``: an optional flag controlling when connections are checked - with the ``ping()`` method if such a method is available - (``0`` = ``None`` = never, ``1`` = default = whenever it is requested, - ``2`` = when a cursor is created, ``4`` = when a query is executed, - ``7`` = always, and all other bit combinations of these values) - -* ``closeable``: if this is set to true, then closing connections will - be allowed, but by default this will be silently ignored - -* ``threadlocal``: an optional class for representing thread-local data - that will be used instead of our Python implementation - (threading.local is faster, but cannot be used in all cases) - -* The creator function or the connect function of the DB-API 2 compliant - database module specified as the creator will receive any additional - parameters such as the host, database, user, password etc. You may - choose some or all of these parameters in your own creator function, - allowing for sophisticated failover and load-balancing mechanisms. - -For instance, if you are using ``pgdb`` as your DB-API 2 database module and -want every connection to your local database ``mydb`` to be reused 1000 times:: - - import pgdb # import used DB-API 2 module - from DBUtils.PersistentDB import PersistentDB - persist = PersistentDB(pgdb, 1000, database='mydb') - -Once you have set up the generator with these parameters, you can request -database connections of that kind:: - - db = persist.connection() - -You can use these connections just as if they were ordinary DB-API 2 -connections. Actually what you get is the hardened ``SteadyDB`` version of -the underlying DB-API 2 connection. - -Closing a persistent connection with ``db.close()`` will be silently -ignored since it would be reopened at the next usage anyway and -contrary to the intent of having persistent connections. Instead, -the connection will be automatically closed when the thread dies. -You can change this behavior be setting the ``closeable`` parameter. - -Note that you need to explicitly start transactions by calling the -``begin()`` method. This ensures that the transparent reopening will be -suspended until the end of the transaction, and that the connection -will be rolled back before being reused by the same thread. - -By setting the ``threadlocal`` parameter to ``threading.local``, getting -connections may become a bit faster, but this may not work in all -environments (for instance, ``mod_wsgi`` is known to cause problems -since it clears the ``threading.local`` data between requests). - -PooledDB --------- -In order to make use of the ``PooledDB`` module, you first need to set up the -database connection pool by creating an instance of ``PooledDB``, passing the -following parameters: - -* ``creator``: either an arbitrary function returning new DB-API 2 - connection objects or a DB-API 2 compliant database module - -* ``mincached`` : the initial number of idle connections in the pool - (the default of ``0`` means no connections are made at startup) - -* ``maxcached``: the maximum number of idle connections in the pool - (the default value of ``0`` or ``None`` means unlimited pool size) - -* ``maxshared``: maximum number of shared connections allowed - (the default value of ``0`` or ``None`` means all connections are dedicated) - - When this maximum number is reached, connections are shared if they - have been requested as shareable. - -* ``maxconnections``: maximum number of connections generally allowed - (the default value of ``0`` or ``None`` means any number of connections) - -* ``blocking``: determines behavior when exceeding the maximum - - If this is set to true, block and wait until the number of - connections decreases, but by default an error will be reported. - -* ``maxusage``: maximum number of reuses of a single connection - (the default of ``0`` or ``None`` means unlimited reuse) - - When this maximum usage number of the connection is reached, - the connection is automatically reset (closed and reopened). - -* ``setsession``: an optional list of SQL commands that may serve to - prepare the session, e.g. ``["set datestyle to german", ...]`` - -* ``reset``: how connections should be reset when returned to the pool - (``False`` or ``None`` to rollback transcations started with ``begin()``, - the default value ``True`` always issues a rollback for safety's sake) - -* ``failures``: an optional exception class or a tuple of exception classes - for which the connection failover mechanism shall be applied, - if the default (OperationalError, InternalError) is not adequate - -* ``ping``: an optional flag controlling when connections are checked - with the ``ping()`` method if such a method is available - (``0`` = ``None`` = never, ``1`` = default = whenever fetched from the pool, - ``2`` = when a cursor is created, ``4`` = when a query is executed, - ``7`` = always, and all other bit combinations of these values) - -* The creator function or the connect function of the DB-API 2 compliant - database module specified as the creator will receive any additional - parameters such as the host, database, user, password etc. You may - choose some or all of these parameters in your own creator function, - allowing for sophisticated failover and load-balancing mechanisms. - -For instance, if you are using ``pgdb`` as your DB-API 2 database module and -want a pool of at least five connections to your local database ``mydb``:: - - import pgdb # import used DB-API 2 module - from DBUtils.PooledDB import PooledDB - pool = PooledDB(pgdb, 5, database='mydb') - -Once you have set up the connection pool you can request database connections -from that pool:: - - db = pool.connection() - -You can use these connections just as if they were ordinary DB-API 2 -connections. Actually what you get is the hardened ``SteadyDB`` version of -the underlying DB-API 2 connection. - -Please note that the connection may be shared with other threads by default -if you set a non-zero ``maxshared`` parameter and the DB-API 2 module allows -this. If you want to have a dedicated connection, use:: - - db = pool.connection(shareable=False) - -Instead of this, you can also get a dedicated connection as follows:: - - db = pool.dedicated_connection() - -If you don't need it any more, you should immediately return it to the -pool with ``db.close()``. You can get another connection in the same way. - -*Warning:* In a threaded environment, never do the following:: - - pool.connection().cursor().execute(...) - -This would release the connection too early for reuse which may be fatal -if the connections are not thread-safe. Make sure that the connection -object stays alive as long as you are using it, like that:: - - db = pool.connection() - cur = db.cursor() - cur.execute(...) - res = cur.fetchone() - cur.close() # or del cur - db.close() # or del db - -Note that you need to explicitly start transactions by calling the -``begin()`` method. This ensures that the connection will not be shared -with other threads, that the transparent reopening will be suspended -until the end of the transaction, and that the connection will be rolled -back before being given back to the connection pool. - -Usage in Webware for Python ---------------------------- -If you are using DBUtils in order to access a database from `Webware -for Python`_ servlets, you need to make sure that you set up your -database connection generators only once when the application starts, -and not every time a servlet instance is created. For this purpose, -you can add the necessary code to the module or class initialization -code of your base servlet class, or you can use the ``contextInitialize()`` -function in the ``__init__.py`` script of your application context. - -The directory ``Examples`` that is part of the DButils distribution -contains an example context for Webware for Python that uses a small -demo database designed to track the attendees for a series of seminars -(the idea for this example has been taken from the article -"`The Python DB-API`_" by Andrew Kuchling). - -The example context can be configured by either creating a config file -``Configs/Database.config`` or by directly changing the default parameters -in the example servlet ``Examples/DBUtilsExample.py``. This way you can -set an appropriate database user and password, and you can choose the -underlying database module (PyGreSQL classic or any DB-API 2 module). -If the setting ``maxcached`` is present, then the example servlet will use -the "Pooled" variant, otherwise it will use the "Persistent" variant. - - -Notes -===== -If you are using one of the popular object-relational mappers SQLObject_ -or SQLAlchemy_, you won't need DBUtils, since they come with their own -connection pools. SQLObject 2 (SQL-API) is actually borrowing some code -from DBUtils to split the pooling out into a separate layer. - -Also note that when you are using a solution like the Apache webserver -with mod_python_ or mod_wsgi_, then your Python code will be usually run -in the context of the webserver's child processes. So if you are using -the ``PooledDB`` module, and several of these child processes are running, -you will have as much database connection pools. If these processes are -running many threads, this may still be a reasonable approach, but if these -processes don't spawn more than one worker thread, as in the case of Apache's -"prefork" multi-processing module, this approach does not make sense. -If you're running such a configuration, you should resort to a middleware -for connection pooling that supports multi-processing, such as pgpool_ -or pgbouncer_ for the PostgreSQL database. - - -Future -====== -Some ideas for future improvements: - -* Alternatively to the maximum number of uses of a connection, - implement a maximum time to live for connections. -* Create modules ``MonitorDB`` and ``MonitorPg`` that will run in a separate - thread, monitoring the pool of the idle connections and maybe also the - shared connections respectively the thread-affine connections. If a - disrupted connection is detected, then it will be reestablished automatically - by the monitoring thread. This will be useful in a scenario where a database - powering a website is restarted during the night. Without the monitoring - thread, the users would experience a slight delay in the next morning, - because only then, the disrupted database connections will be detected and - the pool will be rebuilt. With the monitoring thread, this will already - happen during the night, shortly after the disruption. - The monitoring thread could also be configured to generally recreate - the connection pool every day shortly before the users arrive. -* Optionally log usage, bad connections and exceeding of limits. - - -Bug reports and feedback -======================== -Please send bug reports, patches and feedback directly to the author -(using the email address given below). - -If there are Webware related problems, these can also be discussed in -the `Webware for Python mailing list`_. - - -Links -===== -Some links to related and alternative software: - -* DBUtils_ -* Python_ -* `Webware for Python`_ framework -* Python `DB-API 2`_ -* PostgreSQL_ database -* PyGreSQL_ Python adapter for PostgreSQL -* pgpool_ middleware for PostgreSQL connection pooling -* pgbouncer_ lightweight PostgreSQL connection pooling -* SQLObject_ object-relational mapper -* SQLAlchemy_ object-relational mapper - -.. _DBUtils: https://github.com/Cito/DBUtils -.. _Python: https://www.python.org -.. _Webware for Python: https://cito.github.io/w4py/ -.. _Webware for Python mailing list: https://lists.sourceforge.net/lists/listinfo/webware-discuss -.. _DB-API 2: https://www.python.org/dev/peps/pep-0249/ -.. _The Python DB-API: http://www.linuxjournal.com/article/2605 -.. _PostgresQL: https://www.postgresql.org/ -.. _PyGreSQL: http://www.pygresql.org/ -.. _SQLObject: http://www.sqlobject.org/ -.. _SQLAlchemy: http://www.sqlalchemy.org -.. _Apache: http://httpd.apache.org/ -.. _mod_python: http://modpython.org/ -.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi -.. _pgpool: http://www.pgpool.net/ -.. _pgbouncer: https://pgbouncer.github.io/ - - -Credits -======= - -:Author: Christoph Zwerschke - -:Contributions: DBUtils uses code, input and suggestions made by - Ian Bicking, Chuck Esterbrook (Webware for Python), Dan Green (DBTools), - Jay Love, Michael Palmer, Tom Schwaller, Geoffrey Talvola, - Warren Smith (DbConnectionPool), Ezio Vernacotola, Jehiah Czebotar, - Matthew Harriger, Gregory Piñero and Josef van Eenbergen. - - -Copyright and License -===================== - -Copyright © 2005-2017 by Christoph Zwerschke. -All Rights Reserved. - -DBUtils is free and open source software, -licensed under the `MIT license`__. - -__ https://opensource.org/licenses/MIT diff --git a/buildhtml.py b/buildhtml.py deleted file mode 100755 index 838487a..0000000 --- a/buildhtml.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python - -"""Build HMTL from reST files.""" - -from glob import glob -from os.path import splitext, join -from docutils.core import publish_file - -print "Creating the documentation..." - -for rst_file in glob(join('DBUtils', 'Docs', '*.rst')): - name = splitext(rst_file)[0] - lang = splitext(name)[1] - if lang.startswith('.'): - lang = lang[1:] - if lang == 'zh': - lang = 'zh_cn' - else: - lang = 'en' - html_file = name + '.html' - print name, lang - - source = open(rst_file, 'r') - destination=open(html_file, 'w') - publish_file(writer_name='html5', - source=source, destination=destination, - settings_overrides = dict( - stylesheet_path='Doc.css', - embed_stylesheet=False, - toc_backlinks=False, - language_code=lang - ) - ) - -print "Done." From 35a0dcb60acfe240768a422cdf850b742236180c Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 4 Feb 2017 20:56:07 +0100 Subject: [PATCH 03/23] New release DBUtils 1.1.1 --- RelNotes-1.1.1.html | 31 +++++++++++++++++++++++++++++++ UsersGuide.de.html | 4 ++-- UsersGuide.html | 4 ++-- index.html | 7 ++++--- 4 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 RelNotes-1.1.1.html diff --git a/RelNotes-1.1.1.html b/RelNotes-1.1.1.html new file mode 100644 index 0000000..849f409 --- /dev/null +++ b/RelNotes-1.1.1.html @@ -0,0 +1,31 @@ + + + +DBUtils 1.1.1 Release Notes + + + +

DBUtils 1.1.1 Release Notes

+ +

DBUtils 1.1.1 was released on 02/04/17.

+ +

This bugfix release is the eight public release of DBUtils.

+ +

It is intended to be used with Python versions 2.3 to 2.7

+ +

Improvements:

+
    +
  • Reopen SteadyDB connections when commit or rollback fails +(suggested by Ben Hoyt).
  • +
+ +

Bugfixes:

+
    +
  • Fixed a problem when running under Jython (reported by Vitaly Kruglikov).
  • +
+ + + + \ No newline at end of file diff --git a/UsersGuide.de.html b/UsersGuide.de.html index cd33b76..562f33b 100644 --- a/UsersGuide.de.html +++ b/UsersGuide.de.html @@ -12,9 +12,9 @@

Benutzeranleitung für DBUtils

Version
-
1.1.1b1
+
1.1.1
Released
-

01/31/17

+

02/04/17

Translations

English | German

diff --git a/UsersGuide.html b/UsersGuide.html index c335166..c3f48ef 100644 --- a/UsersGuide.html +++ b/UsersGuide.html @@ -12,9 +12,9 @@

DBUtils User's Guide

Version
-
1.1.1b1
+
1.1.1
Released
-

01/31/17

+

02/04/17

Translations

English | German

diff --git a/index.html b/index.html index 0d7116a..056d09c 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - + @@ -43,7 +43,8 @@

Release Notes:

1.0 | - 1.1 + 1.1 | + 1.1.1
@@ -53,7 +54,7 @@

Release Notes:

Downloads

Current/Recommended Version:
- Download DBUtils 1.1 + Download DBUtils 1.1.1
Older Versions:
From c3af3f27eefdfef741d8c0e5a71722394f661add Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 4 Feb 2017 21:07:39 +0100 Subject: [PATCH 04/23] Update Picnic version --- picnic.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/picnic.css b/picnic.css index 74a7b58..2972e4d 100644 --- a/picnic.css +++ b/picnic.css @@ -1,2 +1,2 @@ -/* Version 6.1.1 */ -/*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:0;padding:0}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}*{box-sizing:inherit}html,body{font-family:Arial, Helvetica, sans-serif;box-sizing:border-box;height:100%}body{color:#111;font-size:1.1em;line-height:1.5;background:#fff}h1,h2,h3,h4,h5,h6{margin:0;padding:.6em 0}li{margin:0 0 .3em}a{color:#0074d9;text-decoration:none;box-shadow:none;transition:all 0.3s}code{padding:.3em .6em;font-size:.8em;background:#f5f5f5}pre{text-align:left;padding:.3em .6em;background:#f5f5f5;border-radius:.2em}pre code{padding:0}blockquote{padding:0 0 0 1em;margin:0 0 0 .1em;box-shadow:inset 5px 0 rgba(17,17,17,0.3)}label{cursor:pointer}[class^="icon-"]:before,[class*=" icon-"]:before{margin:0 .6em 0 0}i[class^="icon-"]:before,i[class*=" icon-"]:before{margin:0}.label,[data-tooltip]:after,button,.button,[type=submit],.dropimage{display:inline-block;text-align:center;margin:0;padding:.3em .9em;vertical-align:middle;background:#0074d9;color:#fff;border:0;border-radius:.2em;width:auto;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.success.label,.success[data-tooltip]:after,button.success,.success.button,.success[type=submit],.success.dropimage{background:#2ecc40}.warning.label,.warning[data-tooltip]:after,button.warning,.warning.button,.warning[type=submit],.warning.dropimage{background:#ff851b}.error.label,.error[data-tooltip]:after,button.error,.error.button,.error[type=submit],.error.dropimage{background:#ff4136}.pseudo.label,.pseudo[data-tooltip]:after,button.pseudo,.pseudo.button,.pseudo[type=submit],.pseudo.dropimage{background:transparent;color:#111}.label,[data-tooltip]:after{font-size:.6em;padding:.4em .6em;margin-left:1em;line-height:1}button,.button,[type=submit],.dropimage{margin:.3em 0;cursor:pointer;transition:all 0.3s;height:auto;box-shadow:0 0 transparent inset}button:hover,.button:hover,[type=submit]:hover,.dropimage:hover,button:focus,.button:focus,[type=submit]:focus,.dropimage:focus{box-shadow:inset 0 0 0 99em rgba(255,255,255,0.2);border:0}button.pseudo:hover,.pseudo.button:hover,.pseudo[type=submit]:hover,.pseudo.dropimage:hover,button.pseudo:focus,.pseudo.button:focus,.pseudo[type=submit]:focus,.pseudo.dropimage:focus{box-shadow:inset 0 0 0 99em rgba(17,17,17,0.1)}button.active,.active.button,.active[type=submit],.active.dropimage,button:active,.button:active,[type=submit]:active,.dropimage:active,button.pseudo:active,.pseudo.button:active,.pseudo[type=submit]:active,.pseudo.dropimage:active{box-shadow:inset 0 0 0 99em rgba(17,17,17,0.2)}button[disabled],[disabled].button,[disabled][type=submit],[disabled].dropimage{cursor:default;box-shadow:none;background:#bbb}:checked+.toggle,:checked+.toggle:hover{box-shadow:inset 0 0 0 99em rgba(17,17,17,0.2)}[type]+.toggle{padding:.3em .9em;margin-right:0}[type]+.toggle:after,[type]+.toggle:before{display:none}input,textarea,.select select{line-height:1.5;margin:0;height:2.1em;padding:.3em .6em;border:1px solid #ccc;background:#fff;border-radius:.2em;transition:all 0.3s;width:100%}input:focus,textarea:focus,.select select:focus{border:1px solid #0074d9;outline:0}textarea{height:auto}[type=file],[type=color]{cursor:pointer}[type=file]{height:auto}select{background:#fff url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyIiBoZWlnaHQ9IjMiPjxwYXRoIGQ9Im0gMCwxIDEsMiAxLC0yIHoiLz48L3N2Zz4=) no-repeat scroll 95% center/10px 15px;background-position:calc(100% - 15px) center;border:1px solid #ccc;border-radius:.2em;cursor:pointer;width:100%;height:2.1em;box-sizing:border-box;padding:.3em .45em;transition:all .3s;-moz-appearance:none;-webkit-appearance:none;appearance:none}select::-ms-expand{display:none}select:focus,select:active{border:1px solid #0074d9;transition:outline 0s}select:-moz-focusring{color:transparent;text-shadow:0 0 0 #111}select option{font-size:inherit;padding:.3em .45em}[type=radio],[type=checkbox]{opacity:0;width:0;position:absolute;display:inline-block}[type=radio]+.checkable:hover:before,[type=checkbox]+.checkable:hover:before,[type=radio]:focus+.checkable:before,[type=checkbox]:focus+.checkable:before{border:1px solid #0074d9}[type=radio]+.checkable,[type=checkbox]+.checkable{position:relative;cursor:pointer;padding-left:1.5em;margin-right:.6em}[type=radio]+.checkable:before,[type=checkbox]+.checkable:before,[type=radio]+.checkable:after,[type=checkbox]+.checkable:after{content:'';position:absolute;display:inline-block;left:0;top:50%;transform:translateY(-50%);font-size:1em;line-height:1em;color:transparent;font-family:sans;text-align:center;box-sizing:border-box;width:1em;height:1em;border-radius:50%;transition:all 0.3s}[type=radio]+.checkable:before,[type=checkbox]+.checkable:before{border:1px solid #aaa}[type=radio]:checked+.checkable:after,[type=checkbox]:checked+.checkable:after{background:#555;transform:scale(0.5) translateY(-100%)}[type=checkbox]+.checkable:before{border-radius:.2em}[type=checkbox]+.checkable:after{content:"✔";background:none;transform:scale(2) translateY(-25%);visibility:hidden;opacity:0}[type=checkbox]:checked+.checkable:after{color:#111;background:none;transform:translateY(-50%);transition:all 0.3s;visibility:visible;opacity:1}table{text-align:left}td,th{padding:.3em 2.4em .3em .6em}th{text-align:left;font-weight:900;color:#fff;background-color:#0074d9}.success th{background-color:#2ecc40}.warning th{background-color:#ff851b}.error th{background-color:#ff4136}.dull th{background-color:#aaa}tr:nth-child(even){background:rgba(0,0,0,0.05)}.flex{display:-ms-flexbox;display:flex;margin-left:-0.6em;width:calc(100% + .6em);flex-wrap:wrap;transition:all .3s ease}.flex>*{box-sizing:border-box;flex:1 1 auto;padding-left:.6em;padding-bottom:.6em}.flex[class*="one"]>*,.flex[class*="two"]>*,.flex[class*="three"]>*,.flex[class*="four"]>*,.flex[class*="five"]>*,.flex[class*="six"]>*,.flex[class*="seven"]>*,.flex[class*="eight"]>*,.flex[class*="nine"]>*,.flex[class*="ten"]>*,.flex[class*="eleven"]>*,.flex[class*="twelve"]>*{flex-grow:0}.flex.grow>*{flex-grow:1}.center{justify-content:center}.one>*{width:100%}.two>*{width:50%}.three>*{width:33.33333%}.four>*{width:25%}.five>*{width:20%}.six>*{width:16.66666%}.seven>*{width:14.28571%}.eight>*{width:12.5%}.nine>*{width:11.11111%}.ten>*{width:10%}.eleven>*{width:9.09091%}.twelve>*{width:8.33333%}@media all and (min-width: 500px){.one-500>*{width:100%}.two-500>*{width:50%}.three-500>*{width:33.33333%}.four-500>*{width:25%}.five-500>*{width:20%}.six-500>*{width:16.66666%}.seven-500>*{width:14.28571%}.eight-500>*{width:12.5%}.nine-500>*{width:11.11111%}.ten-500>*{width:10%}.eleven-500>*{width:9.09091%}.twelve-500>*{width:8.33333%}}@media all and (min-width: 600px){.one-600>*{width:100%}.two-600>*{width:50%}.three-600>*{width:33.33333%}.four-600>*{width:25%}.five-600>*{width:20%}.six-600>*{width:16.66666%}.seven-600>*{width:14.28571%}.eight-600>*{width:12.5%}.nine-600>*{width:11.11111%}.ten-600>*{width:10%}.eleven-600>*{width:9.09091%}.twelve-600>*{width:8.33333%}}@media all and (min-width: 700px){.one-700>*{width:100%}.two-700>*{width:50%}.three-700>*{width:33.33333%}.four-700>*{width:25%}.five-700>*{width:20%}.six-700>*{width:16.66666%}.seven-700>*{width:14.28571%}.eight-700>*{width:12.5%}.nine-700>*{width:11.11111%}.ten-700>*{width:10%}.eleven-700>*{width:9.09091%}.twelve-700>*{width:8.33333%}}@media all and (min-width: 800px){.one-800>*{width:100%}.two-800>*{width:50%}.three-800>*{width:33.33333%}.four-800>*{width:25%}.five-800>*{width:20%}.six-800>*{width:16.66666%}.seven-800>*{width:14.28571%}.eight-800>*{width:12.5%}.nine-800>*{width:11.11111%}.ten-800>*{width:10%}.eleven-800>*{width:9.09091%}.twelve-800>*{width:8.33333%}}@media all and (min-width: 900px){.one-900>*{width:100%}.two-900>*{width:50%}.three-900>*{width:33.33333%}.four-900>*{width:25%}.five-900>*{width:20%}.six-900>*{width:16.66666%}.seven-900>*{width:14.28571%}.eight-900>*{width:12.5%}.nine-900>*{width:11.11111%}.ten-900>*{width:10%}.eleven-900>*{width:9.09091%}.twelve-900>*{width:8.33333%}}@media all and (min-width: 1000px){.one-1000>*{width:100%}.two-1000>*{width:50%}.three-1000>*{width:33.33333%}.four-1000>*{width:25%}.five-1000>*{width:20%}.six-1000>*{width:16.66666%}.seven-1000>*{width:14.28571%}.eight-1000>*{width:12.5%}.nine-1000>*{width:11.11111%}.ten-1000>*{width:10%}.eleven-1000>*{width:9.09091%}.twelve-1000>*{width:8.33333%}}@media all and (min-width: 1100px){.one-1100>*{width:100%}.two-1100>*{width:50%}.three-1100>*{width:33.33333%}.four-1100>*{width:25%}.five-1100>*{width:20%}.six-1100>*{width:16.66666%}.seven-1100>*{width:14.28571%}.eight-1100>*{width:12.5%}.nine-1100>*{width:11.11111%}.ten-1100>*{width:10%}.eleven-1100>*{width:9.09091%}.twelve-1100>*{width:8.33333%}}@media all and (min-width: 1200px){.one-1200>*{width:100%}.two-1200>*{width:50%}.three-1200>*{width:33.33333%}.four-1200>*{width:25%}.five-1200>*{width:20%}.six-1200>*{width:16.66666%}.seven-1200>*{width:14.28571%}.eight-1200>*{width:12.5%}.nine-1200>*{width:11.11111%}.ten-1200>*{width:10%}.eleven-1200>*{width:9.09091%}.twelve-1200>*{width:8.33333%}}@media all and (min-width: 1300px){.one-1300>*{width:100%}.two-1300>*{width:50%}.three-1300>*{width:33.33333%}.four-1300>*{width:25%}.five-1300>*{width:20%}.six-1300>*{width:16.66666%}.seven-1300>*{width:14.28571%}.eight-1300>*{width:12.5%}.nine-1300>*{width:11.11111%}.ten-1300>*{width:10%}.eleven-1300>*{width:9.09091%}.twelve-1300>*{width:8.33333%}}@media all and (min-width: 1400px){.one-1400>*{width:100%}.two-1400>*{width:50%}.three-1400>*{width:33.33333%}.four-1400>*{width:25%}.five-1400>*{width:20%}.six-1400>*{width:16.66666%}.seven-1400>*{width:14.28571%}.eight-1400>*{width:12.5%}.nine-1400>*{width:11.11111%}.ten-1400>*{width:10%}.eleven-1400>*{width:9.09091%}.twelve-1400>*{width:8.33333%}}@media all and (min-width: 1500px){.one-1500>*{width:100%}.two-1500>*{width:50%}.three-1500>*{width:33.33333%}.four-1500>*{width:25%}.five-1500>*{width:20%}.six-1500>*{width:16.66666%}.seven-1500>*{width:14.28571%}.eight-1500>*{width:12.5%}.nine-1500>*{width:11.11111%}.ten-1500>*{width:10%}.eleven-1500>*{width:9.09091%}.twelve-1500>*{width:8.33333%}}@media all and (min-width: 1600px){.one-1600>*{width:100%}.two-1600>*{width:50%}.three-1600>*{width:33.33333%}.four-1600>*{width:25%}.five-1600>*{width:20%}.six-1600>*{width:16.66666%}.seven-1600>*{width:14.28571%}.eight-1600>*{width:12.5%}.nine-1600>*{width:11.11111%}.ten-1600>*{width:10%}.eleven-1600>*{width:9.09091%}.twelve-1600>*{width:8.33333%}}@media all and (min-width: 1700px){.one-1700>*{width:100%}.two-1700>*{width:50%}.three-1700>*{width:33.33333%}.four-1700>*{width:25%}.five-1700>*{width:20%}.six-1700>*{width:16.66666%}.seven-1700>*{width:14.28571%}.eight-1700>*{width:12.5%}.nine-1700>*{width:11.11111%}.ten-1700>*{width:10%}.eleven-1700>*{width:9.09091%}.twelve-1700>*{width:8.33333%}}@media all and (min-width: 1800px){.one-1800>*{width:100%}.two-1800>*{width:50%}.three-1800>*{width:33.33333%}.four-1800>*{width:25%}.five-1800>*{width:20%}.six-1800>*{width:16.66666%}.seven-1800>*{width:14.28571%}.eight-1800>*{width:12.5%}.nine-1800>*{width:11.11111%}.ten-1800>*{width:10%}.eleven-1800>*{width:9.09091%}.twelve-1800>*{width:8.33333%}}@media all and (min-width: 1900px){.one-1900>*{width:100%}.two-1900>*{width:50%}.three-1900>*{width:33.33333%}.four-1900>*{width:25%}.five-1900>*{width:20%}.six-1900>*{width:16.66666%}.seven-1900>*{width:14.28571%}.eight-1900>*{width:12.5%}.nine-1900>*{width:11.11111%}.ten-1900>*{width:10%}.eleven-1900>*{width:9.09091%}.twelve-1900>*{width:8.33333%}}@media all and (min-width: 2000px){.one-2000>*{width:100%}.two-2000>*{width:50%}.three-2000>*{width:33.33333%}.four-2000>*{width:25%}.five-2000>*{width:20%}.six-2000>*{width:16.66666%}.seven-2000>*{width:14.28571%}.eight-2000>*{width:12.5%}.nine-2000>*{width:11.11111%}.ten-2000>*{width:10%}.eleven-2000>*{width:9.09091%}.twelve-2000>*{width:8.33333%}}.full{width:100%}.half{width:50%}.third{width:33.33333%}.two-third{width:66.66666%}.fourth{width:25%}.three-fourth{width:75%}.fifth{width:20%}.two-fifth{width:40%}.three-fifth{width:60%}.four-fifth{width:80%}.sixth{width:16.66666%}.none{display:none}@media all and (min-width: 500px){.full-500{width:100%;display:block}.half-500{width:50%;display:block}.third-500{width:33.33333%;display:block}.two-third-500{width:66.66666%;display:block}.fourth-500{width:25%;display:block}.three-fourth-500{width:75%;display:block}.fifth-500{width:20%;display:block}.two-fifth-500{width:40%;display:block}.three-fifth-500{width:60%;display:block}.four-fifth-500{width:80%;display:block}.sixth-500{width:16.66666%;display:block}.none-500{display:none}}@media all and (min-width: 600px){.full-600{width:100%;display:block}.half-600{width:50%;display:block}.third-600{width:33.33333%;display:block}.two-third-600{width:66.66666%;display:block}.fourth-600{width:25%;display:block}.three-fourth-600{width:75%;display:block}.fifth-600{width:20%;display:block}.two-fifth-600{width:40%;display:block}.three-fifth-600{width:60%;display:block}.four-fifth-600{width:80%;display:block}.sixth-600{width:16.66666%;display:block}.none-600{display:none}}@media all and (min-width: 700px){.full-700{width:100%;display:block}.half-700{width:50%;display:block}.third-700{width:33.33333%;display:block}.two-third-700{width:66.66666%;display:block}.fourth-700{width:25%;display:block}.three-fourth-700{width:75%;display:block}.fifth-700{width:20%;display:block}.two-fifth-700{width:40%;display:block}.three-fifth-700{width:60%;display:block}.four-fifth-700{width:80%;display:block}.sixth-700{width:16.66666%;display:block}.none-700{display:none}}@media all and (min-width: 800px){.full-800{width:100%;display:block}.half-800{width:50%;display:block}.third-800{width:33.33333%;display:block}.two-third-800{width:66.66666%;display:block}.fourth-800{width:25%;display:block}.three-fourth-800{width:75%;display:block}.fifth-800{width:20%;display:block}.two-fifth-800{width:40%;display:block}.three-fifth-800{width:60%;display:block}.four-fifth-800{width:80%;display:block}.sixth-800{width:16.66666%;display:block}.none-800{display:none}}@media all and (min-width: 900px){.full-900{width:100%;display:block}.half-900{width:50%;display:block}.third-900{width:33.33333%;display:block}.two-third-900{width:66.66666%;display:block}.fourth-900{width:25%;display:block}.three-fourth-900{width:75%;display:block}.fifth-900{width:20%;display:block}.two-fifth-900{width:40%;display:block}.three-fifth-900{width:60%;display:block}.four-fifth-900{width:80%;display:block}.sixth-900{width:16.66666%;display:block}.none-900{display:none}}@media all and (min-width: 1000px){.full-1000{width:100%;display:block}.half-1000{width:50%;display:block}.third-1000{width:33.33333%;display:block}.two-third-1000{width:66.66666%;display:block}.fourth-1000{width:25%;display:block}.three-fourth-1000{width:75%;display:block}.fifth-1000{width:20%;display:block}.two-fifth-1000{width:40%;display:block}.three-fifth-1000{width:60%;display:block}.four-fifth-1000{width:80%;display:block}.sixth-1000{width:16.66666%;display:block}.none-1000{display:none}}@media all and (min-width: 1100px){.full-1100{width:100%;display:block}.half-1100{width:50%;display:block}.third-1100{width:33.33333%;display:block}.two-third-1100{width:66.66666%;display:block}.fourth-1100{width:25%;display:block}.three-fourth-1100{width:75%;display:block}.fifth-1100{width:20%;display:block}.two-fifth-1100{width:40%;display:block}.three-fifth-1100{width:60%;display:block}.four-fifth-1100{width:80%;display:block}.sixth-1100{width:16.66666%;display:block}.none-1100{display:none}}@media all and (min-width: 1200px){.full-1200{width:100%;display:block}.half-1200{width:50%;display:block}.third-1200{width:33.33333%;display:block}.two-third-1200{width:66.66666%;display:block}.fourth-1200{width:25%;display:block}.three-fourth-1200{width:75%;display:block}.fifth-1200{width:20%;display:block}.two-fifth-1200{width:40%;display:block}.three-fifth-1200{width:60%;display:block}.four-fifth-1200{width:80%;display:block}.sixth-1200{width:16.66666%;display:block}.none-1200{display:none}}@media all and (min-width: 1300px){.full-1300{width:100%;display:block}.half-1300{width:50%;display:block}.third-1300{width:33.33333%;display:block}.two-third-1300{width:66.66666%;display:block}.fourth-1300{width:25%;display:block}.three-fourth-1300{width:75%;display:block}.fifth-1300{width:20%;display:block}.two-fifth-1300{width:40%;display:block}.three-fifth-1300{width:60%;display:block}.four-fifth-1300{width:80%;display:block}.sixth-1300{width:16.66666%;display:block}.none-1300{display:none}}@media all and (min-width: 1400px){.full-1400{width:100%;display:block}.half-1400{width:50%;display:block}.third-1400{width:33.33333%;display:block}.two-third-1400{width:66.66666%;display:block}.fourth-1400{width:25%;display:block}.three-fourth-1400{width:75%;display:block}.fifth-1400{width:20%;display:block}.two-fifth-1400{width:40%;display:block}.three-fifth-1400{width:60%;display:block}.four-fifth-1400{width:80%;display:block}.sixth-1400{width:16.66666%;display:block}.none-1400{display:none}}@media all and (min-width: 1500px){.full-1500{width:100%;display:block}.half-1500{width:50%;display:block}.third-1500{width:33.33333%;display:block}.two-third-1500{width:66.66666%;display:block}.fourth-1500{width:25%;display:block}.three-fourth-1500{width:75%;display:block}.fifth-1500{width:20%;display:block}.two-fifth-1500{width:40%;display:block}.three-fifth-1500{width:60%;display:block}.four-fifth-1500{width:80%;display:block}.sixth-1500{width:16.66666%;display:block}.none-1500{display:none}}@media all and (min-width: 1600px){.full-1600{width:100%;display:block}.half-1600{width:50%;display:block}.third-1600{width:33.33333%;display:block}.two-third-1600{width:66.66666%;display:block}.fourth-1600{width:25%;display:block}.three-fourth-1600{width:75%;display:block}.fifth-1600{width:20%;display:block}.two-fifth-1600{width:40%;display:block}.three-fifth-1600{width:60%;display:block}.four-fifth-1600{width:80%;display:block}.sixth-1600{width:16.66666%;display:block}.none-1600{display:none}}@media all and (min-width: 1700px){.full-1700{width:100%;display:block}.half-1700{width:50%;display:block}.third-1700{width:33.33333%;display:block}.two-third-1700{width:66.66666%;display:block}.fourth-1700{width:25%;display:block}.three-fourth-1700{width:75%;display:block}.fifth-1700{width:20%;display:block}.two-fifth-1700{width:40%;display:block}.three-fifth-1700{width:60%;display:block}.four-fifth-1700{width:80%;display:block}.sixth-1700{width:16.66666%;display:block}.none-1700{display:none}}@media all and (min-width: 1800px){.full-1800{width:100%;display:block}.half-1800{width:50%;display:block}.third-1800{width:33.33333%;display:block}.two-third-1800{width:66.66666%;display:block}.fourth-1800{width:25%;display:block}.three-fourth-1800{width:75%;display:block}.fifth-1800{width:20%;display:block}.two-fifth-1800{width:40%;display:block}.three-fifth-1800{width:60%;display:block}.four-fifth-1800{width:80%;display:block}.sixth-1800{width:16.66666%;display:block}.none-1800{display:none}}@media all and (min-width: 1900px){.full-1900{width:100%;display:block}.half-1900{width:50%;display:block}.third-1900{width:33.33333%;display:block}.two-third-1900{width:66.66666%;display:block}.fourth-1900{width:25%;display:block}.three-fourth-1900{width:75%;display:block}.fifth-1900{width:20%;display:block}.two-fifth-1900{width:40%;display:block}.three-fifth-1900{width:60%;display:block}.four-fifth-1900{width:80%;display:block}.sixth-1900{width:16.66666%;display:block}.none-1900{display:none}}@media all and (min-width: 2000px){.full-2000{width:100%;display:block}.half-2000{width:50%;display:block}.third-2000{width:33.33333%;display:block}.two-third-2000{width:66.66666%;display:block}.fourth-2000{width:25%;display:block}.three-fourth-2000{width:75%;display:block}.fifth-2000{width:20%;display:block}.two-fifth-2000{width:40%;display:block}.three-fifth-2000{width:60%;display:block}.four-fifth-2000{width:80%;display:block}.sixth-2000{width:16.66666%;display:block}.none-2000{display:none}}.off-half{margin-left:50%}.off-third{margin-left:33.33333%}.off-two-third{margin-left:66.66666%}.off-fourth{margin-left:25%}.off-three-fourth{margin-left:75%}.off-fifth{margin-left:20%}.off-two-fifth{margin-left:40%}.off-three-fifth{margin-left:60%}.off-four-fifth{margin-left:80%}.off-sixth{margin-left:16.66666%}.off-none{margin-left:0}@media all and (min-width: 500px){.off-full-500{margin-left:100%}.off-half-500{margin-left:50%}.off-third-500{margin-left:33.33333%}.off-two-third-500{margin-left:66.66666%}.off-fourth-500{margin-left:25%}.off-three-fourth-500{margin-left:75%}.off-fifth-500{margin-left:20%}.off-two-fifth-500{margin-left:40%}.off-three-fifth-500{margin-left:60%}.off-four-fifth-500{margin-left:80%}.off-sixth-500{margin-left:16.66666%}.off-none-500{margin-left:0}}@media all and (min-width: 600px){.off-full-600{margin-left:100%}.off-half-600{margin-left:50%}.off-third-600{margin-left:33.33333%}.off-two-third-600{margin-left:66.66666%}.off-fourth-600{margin-left:25%}.off-three-fourth-600{margin-left:75%}.off-fifth-600{margin-left:20%}.off-two-fifth-600{margin-left:40%}.off-three-fifth-600{margin-left:60%}.off-four-fifth-600{margin-left:80%}.off-sixth-600{margin-left:16.66666%}.off-none-600{margin-left:0}}@media all and (min-width: 700px){.off-full-700{margin-left:100%}.off-half-700{margin-left:50%}.off-third-700{margin-left:33.33333%}.off-two-third-700{margin-left:66.66666%}.off-fourth-700{margin-left:25%}.off-three-fourth-700{margin-left:75%}.off-fifth-700{margin-left:20%}.off-two-fifth-700{margin-left:40%}.off-three-fifth-700{margin-left:60%}.off-four-fifth-700{margin-left:80%}.off-sixth-700{margin-left:16.66666%}.off-none-700{margin-left:0}}@media all and (min-width: 800px){.off-full-800{margin-left:100%}.off-half-800{margin-left:50%}.off-third-800{margin-left:33.33333%}.off-two-third-800{margin-left:66.66666%}.off-fourth-800{margin-left:25%}.off-three-fourth-800{margin-left:75%}.off-fifth-800{margin-left:20%}.off-two-fifth-800{margin-left:40%}.off-three-fifth-800{margin-left:60%}.off-four-fifth-800{margin-left:80%}.off-sixth-800{margin-left:16.66666%}.off-none-800{margin-left:0}}@media all and (min-width: 900px){.off-full-900{margin-left:100%}.off-half-900{margin-left:50%}.off-third-900{margin-left:33.33333%}.off-two-third-900{margin-left:66.66666%}.off-fourth-900{margin-left:25%}.off-three-fourth-900{margin-left:75%}.off-fifth-900{margin-left:20%}.off-two-fifth-900{margin-left:40%}.off-three-fifth-900{margin-left:60%}.off-four-fifth-900{margin-left:80%}.off-sixth-900{margin-left:16.66666%}.off-none-900{margin-left:0}}@media all and (min-width: 1000px){.off-full-1000{margin-left:100%}.off-half-1000{margin-left:50%}.off-third-1000{margin-left:33.33333%}.off-two-third-1000{margin-left:66.66666%}.off-fourth-1000{margin-left:25%}.off-three-fourth-1000{margin-left:75%}.off-fifth-1000{margin-left:20%}.off-two-fifth-1000{margin-left:40%}.off-three-fifth-1000{margin-left:60%}.off-four-fifth-1000{margin-left:80%}.off-sixth-1000{margin-left:16.66666%}.off-none-1000{margin-left:0}}@media all and (min-width: 1100px){.off-full-1100{margin-left:100%}.off-half-1100{margin-left:50%}.off-third-1100{margin-left:33.33333%}.off-two-third-1100{margin-left:66.66666%}.off-fourth-1100{margin-left:25%}.off-three-fourth-1100{margin-left:75%}.off-fifth-1100{margin-left:20%}.off-two-fifth-1100{margin-left:40%}.off-three-fifth-1100{margin-left:60%}.off-four-fifth-1100{margin-left:80%}.off-sixth-1100{margin-left:16.66666%}.off-none-1100{margin-left:0}}@media all and (min-width: 1200px){.off-full-1200{margin-left:100%}.off-half-1200{margin-left:50%}.off-third-1200{margin-left:33.33333%}.off-two-third-1200{margin-left:66.66666%}.off-fourth-1200{margin-left:25%}.off-three-fourth-1200{margin-left:75%}.off-fifth-1200{margin-left:20%}.off-two-fifth-1200{margin-left:40%}.off-three-fifth-1200{margin-left:60%}.off-four-fifth-1200{margin-left:80%}.off-sixth-1200{margin-left:16.66666%}.off-none-1200{margin-left:0}}@media all and (min-width: 1300px){.off-full-1300{margin-left:100%}.off-half-1300{margin-left:50%}.off-third-1300{margin-left:33.33333%}.off-two-third-1300{margin-left:66.66666%}.off-fourth-1300{margin-left:25%}.off-three-fourth-1300{margin-left:75%}.off-fifth-1300{margin-left:20%}.off-two-fifth-1300{margin-left:40%}.off-three-fifth-1300{margin-left:60%}.off-four-fifth-1300{margin-left:80%}.off-sixth-1300{margin-left:16.66666%}.off-none-1300{margin-left:0}}@media all and (min-width: 1400px){.off-full-1400{margin-left:100%}.off-half-1400{margin-left:50%}.off-third-1400{margin-left:33.33333%}.off-two-third-1400{margin-left:66.66666%}.off-fourth-1400{margin-left:25%}.off-three-fourth-1400{margin-left:75%}.off-fifth-1400{margin-left:20%}.off-two-fifth-1400{margin-left:40%}.off-three-fifth-1400{margin-left:60%}.off-four-fifth-1400{margin-left:80%}.off-sixth-1400{margin-left:16.66666%}.off-none-1400{margin-left:0}}@media all and (min-width: 1500px){.off-full-1500{margin-left:100%}.off-half-1500{margin-left:50%}.off-third-1500{margin-left:33.33333%}.off-two-third-1500{margin-left:66.66666%}.off-fourth-1500{margin-left:25%}.off-three-fourth-1500{margin-left:75%}.off-fifth-1500{margin-left:20%}.off-two-fifth-1500{margin-left:40%}.off-three-fifth-1500{margin-left:60%}.off-four-fifth-1500{margin-left:80%}.off-sixth-1500{margin-left:16.66666%}.off-none-1500{margin-left:0}}@media all and (min-width: 1600px){.off-full-1600{margin-left:100%}.off-half-1600{margin-left:50%}.off-third-1600{margin-left:33.33333%}.off-two-third-1600{margin-left:66.66666%}.off-fourth-1600{margin-left:25%}.off-three-fourth-1600{margin-left:75%}.off-fifth-1600{margin-left:20%}.off-two-fifth-1600{margin-left:40%}.off-three-fifth-1600{margin-left:60%}.off-four-fifth-1600{margin-left:80%}.off-sixth-1600{margin-left:16.66666%}.off-none-1600{margin-left:0}}@media all and (min-width: 1700px){.off-full-1700{margin-left:100%}.off-half-1700{margin-left:50%}.off-third-1700{margin-left:33.33333%}.off-two-third-1700{margin-left:66.66666%}.off-fourth-1700{margin-left:25%}.off-three-fourth-1700{margin-left:75%}.off-fifth-1700{margin-left:20%}.off-two-fifth-1700{margin-left:40%}.off-three-fifth-1700{margin-left:60%}.off-four-fifth-1700{margin-left:80%}.off-sixth-1700{margin-left:16.66666%}.off-none-1700{margin-left:0}}@media all and (min-width: 1800px){.off-full-1800{margin-left:100%}.off-half-1800{margin-left:50%}.off-third-1800{margin-left:33.33333%}.off-two-third-1800{margin-left:66.66666%}.off-fourth-1800{margin-left:25%}.off-three-fourth-1800{margin-left:75%}.off-fifth-1800{margin-left:20%}.off-two-fifth-1800{margin-left:40%}.off-three-fifth-1800{margin-left:60%}.off-four-fifth-1800{margin-left:80%}.off-sixth-1800{margin-left:16.66666%}.off-none-1800{margin-left:0}}@media all and (min-width: 1900px){.off-full-1900{margin-left:100%}.off-half-1900{margin-left:50%}.off-third-1900{margin-left:33.33333%}.off-two-third-1900{margin-left:66.66666%}.off-fourth-1900{margin-left:25%}.off-three-fourth-1900{margin-left:75%}.off-fifth-1900{margin-left:20%}.off-two-fifth-1900{margin-left:40%}.off-three-fifth-1900{margin-left:60%}.off-four-fifth-1900{margin-left:80%}.off-sixth-1900{margin-left:16.66666%}.off-none-1900{margin-left:0}}@media all and (min-width: 2000px){.off-full-2000{margin-left:100%}.off-half-2000{margin-left:50%}.off-third-2000{margin-left:33.33333%}.off-two-third-2000{margin-left:66.66666%}.off-fourth-2000{margin-left:25%}.off-three-fourth-2000{margin-left:75%}.off-fifth-2000{margin-left:20%}.off-two-fifth-2000{margin-left:40%}.off-three-fifth-2000{margin-left:60%}.off-four-fifth-2000{margin-left:80%}.off-sixth-2000{margin-left:16.66666%}.off-none-2000{margin-left:0}}nav{position:fixed;top:0;left:0;right:0;height:3em;padding:0;background:#fff;box-shadow:0 0 0.2em rgba(17,17,17,0.2);z-index:10;transition:all .3s;transform-style:preserve-3d}nav .brand,nav .menu,nav .burger{float:right;position:relative;top:50%;transform:translateY(-50%)}nav .brand{font-weight:700;float:left;padding:0 .6em;max-width:50%;white-space:nowrap;color:#111}nav .brand *{vertical-align:middle}nav .logo{height:2em;margin-right:.3em}nav .select::after{height:calc(100% - 1px);padding:0;line-height:2.4em}nav .menu>*{margin-right:.6em}.burger{display:none}@media all and (max-width: 60em){nav .burger{display:inline-block;cursor:pointer;bottom:-1000em;margin:0}nav .menu,nav .show:checked ~ .burger{position:fixed;min-height:100%;width:0;overflow:hidden;top:0;right:0;bottom:-1000em;margin:0;background:#fff;transition:all .3s ease;transform:none}nav .show:checked ~ .burger{color:transparent;width:100%;border-radius:0;background:rgba(0,0,0,0.2);transition:all .3s ease}nav .show:checked ~ .menu{width:70%;overflow:auto;transition:all .3s ease}nav .menu>*{display:block;margin:.3em;text-align:left}nav .menu>a{padding:.3em .9em}}.stack,.stack .toggle{margin-top:0;margin-bottom:0;display:block;width:100%;text-align:left;border-radius:0}.stack:first-child,.stack:first-child .toggle{border-top-left-radius:.2em;border-top-right-radius:.2em}.stack:last-child,.stack:last-child .toggle{border-bottom-left-radius:.2em;border-bottom-right-radius:.2em}input.stack,textarea.stack,select.stack{transition:border-bottom 0 ease 0;border-bottom-width:0}input.stack:last-child,textarea.stack:last-child,select.stack:last-child{border-bottom-width:1px}input.stack:focus+input,input.stack:focus+textarea,input.stack:focus+select,textarea.stack:focus+input,textarea.stack:focus+textarea,textarea.stack:focus+select,select.stack:focus+input,select.stack:focus+textarea,select.stack:focus+select{border-top-color:#0074d9}.card,.modal .overlay ~ *{position:relative;box-shadow:0;border-radius:.2em;border:1px solid #ccc;overflow:hidden;text-align:left;background:#fff;margin-bottom:.6em;padding:0;transition:all .3s ease}.hidden.card,.modal .overlay ~ .hidden,:checked+.card,.modal .overlay ~ :checked+*,.modal .overlay:checked+*{font-size:0;padding:0;margin:0;border:0}.card>*,.modal .overlay ~ *>*{max-width:100%;display:block}.card>*:last-child,.modal .overlay ~ *>*:last-child{margin-bottom:0}.card header,.modal .overlay ~ * header,.card section,.modal .overlay ~ * section,.card>p,.modal .overlay ~ *>p{padding:.6em .8em}.card section,.modal .overlay ~ * section{padding:.6em .8em 0}.card hr,.modal .overlay ~ * hr{border:none;height:1px;background-color:#eee}.card header,.modal .overlay ~ * header{font-weight:bold;position:relative;border-bottom:1px solid #eee}.card header h1,.modal .overlay ~ * header h1,.card header h2,.modal .overlay ~ * header h2,.card header h3,.modal .overlay ~ * header h3,.card header h4,.modal .overlay ~ * header h4,.card header h5,.modal .overlay ~ * header h5,.card header h6,.modal .overlay ~ * header h6{padding:0;margin:0 2em 0 0;line-height:1;display:inline-block;vertical-align:text-bottom}.card header:last-child,.modal .overlay ~ * header:last-child{border-bottom:0}.card footer,.modal .overlay ~ * footer{padding:.8em}.card p,.modal .overlay ~ * p{margin:.3em 0}.card p:first-child,.modal .overlay ~ * p:first-child{margin-top:0}.card p:last-child,.modal .overlay ~ * p:last-child{margin-bottom:0}.card>p,.modal .overlay ~ *>p{margin:0;padding-right:2.5em}.card .close,.modal .overlay ~ * .close{position:absolute;top:.4em;right:.3em;font-size:1.2em;padding:0 .5em;cursor:pointer;width:auto}.card .close:hover,.modal .overlay ~ * .close:hover{color:#ff4136}.card h1+.close,.modal .overlay ~ * h1+.close{margin:.2em}.card h2+.close,.modal .overlay ~ * h2+.close{margin:.1em}.card .dangerous,.modal .overlay ~ * .dangerous{background:#ff4136;float:right}.modal{text-align:center}.modal>input{display:none}.modal>input ~ *{opacity:0;max-height:0;overflow:hidden}.modal .overlay{top:0;left:0;bottom:0;right:0;position:fixed;margin:0;border-radius:0;background:rgba(17,17,17,0.6);transition:all 0.3s;z-index:99}.modal .overlay:before,.modal .overlay:after{display:none}.modal .overlay ~ *{border:0;position:fixed;top:50%;left:50%;transform:translateX(-50%) translateY(-50%) scale(0.2, 0.2);z-index:100;transition:all 0.3s}.modal>input:checked ~ *{display:block;opacity:1;max-height:10000px;transition:all 0.3s}.modal>input:checked ~ .overlay ~ *{max-height:90%;overflow:auto;-webkit-transform:translateX(-50%) translateY(-50%) scale(1, 1);transform:translateX(-50%) translateY(-50%) scale(1, 1)}@media (max-width: 60em){.modal .overlay ~ *{min-width:90%}}.dropimage{position:relative;display:block;padding:0;padding-bottom:56.25%;overflow:hidden;cursor:pointer;border:0;background-color:#ddd;background-size:cover;background-position:center center;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDAiIGhlaWdodD0iNjQwIiB2ZXJzaW9uPSIxLjEiPjxnIHN0eWxlPSJmaWxsOiMzMzMiPjxwYXRoIGQ9Ik0gMTg3IDIzMCBDIDE3NSAyMzAgMTY1IDI0MCAxNjUgMjUyIEwgMTY1IDMwMCBMIDE2NSA0MDggQyAxNjUgNDIwIDE3NSA0MzAgMTg3IDQzMCBMIDQ2MyA0MzAgQyA0NzUgNDMwIDQ4NSA0MjAgNDg1IDQwOCBMIDQ4NSAzMDAgTCA0ODUgMjUyIEMgNDg1IDI0MCA0NzUgMjMwIDQ2MyAyMzAgTCAxODcgMjMwIHogTSAzNjAgMjU2IEEgNzAgNzIgMCAwIDEgNDMwIDMyOCBBIDcwIDcyIDAgMCAxIDM2MCA0MDAgQSA3MCA3MiAwIDAgMSAyOTAgMzI4IEEgNzAgNzIgMCAwIDEgMzYwIDI1NiB6Ii8+PGNpcmNsZSBjeD0iMzYwIiBjeT0iMzMwIiByPSI0MSIvPjxwYXRoIGQ9Im0yMDUgMjI1IDUtMTAgMjAgMCA1IDEwLTMwIDAiLz48cGF0aCBkPSJNMjg1IDIwMEwyNzAgMjI1IDM3NiAyMjUgMzYxIDIwMCAyODUgMjAwek0zMTAgMjA1TDMzNyAyMDUgMzM3IDIxOCAzMTAgMjE4IDMxMCAyMDV6Ii8+PHBhdGggZD0ibTQwNSAyMjUgNS0xMCAyMCAwIDUgMTAtMzAgMCIvPjwvZz48L3N2Zz4=)}.dropimage input{left:0;width:100%;height:100%;border:0;margin:0;padding:0;opacity:0;position:absolute}.tabs{position:relative;overflow:hidden}.tabs>label img{float:left;margin-left:.6em}.tabs>.row{width:calc(100% + 2 * .6em);display:table;table-layout:fixed;position:relative;padding-left:0;transition:all .3s;border-spacing:0;margin:0}.tabs>.row:before,.tabs>.row:after{display:none}.tabs>.row>*,.tabs>.row img{display:table-cell;vertical-align:top;margin:0;width:100%}.tabs>input{display:none}.tabs>input+*{width:100%}.tabs>input+label{width:auto}.two.tabs>.row{width:200%;left:-100%}.two.tabs>input:nth-of-type(1):checked ~ .row{margin-left:100%}.two.tabs>label img{width:48%;margin:4% 0 4% 4%}.three.tabs>.row{width:300%;left:-200%}.three.tabs>input:nth-of-type(1):checked ~ .row{margin-left:200%}.three.tabs>input:nth-of-type(2):checked ~ .row{margin-left:100%}.three.tabs>label img{width:30%;margin:5% 0 5% 5%}.four.tabs>.row{width:400%;left:-300%}.four.tabs>input:nth-of-type(1):checked ~ .row{margin-left:300%}.four.tabs>input:nth-of-type(2):checked ~ .row{margin-left:200%}.four.tabs>input:nth-of-type(3):checked ~ .row{margin-left:100%}.four.tabs>label img{width:22%;margin:4% 0 4% 4%}.tabs>label:first-of-type img{margin-left:0}[data-tooltip]{position:relative}[data-tooltip]:after,[data-tooltip]:before{position:absolute;z-index:10;opacity:0;border-width:0;height:0;padding:0;overflow:hidden;transition:opacity .6s ease, height 0s ease .6s;top:calc(100% - 6px);left:0;margin-top:12px}[data-tooltip]:after{margin-left:0;font-size:.8em;background:#111;content:attr(data-tooltip);white-space:nowrap}[data-tooltip]:before{content:'';width:0;height:0;border-width:0;border-style:solid;border-color:transparent transparent #111;margin-top:0;left:10px}[data-tooltip]:hover:after,[data-tooltip]:focus:after,[data-tooltip]:hover:before,[data-tooltip]:focus:before{opacity:1;border-width:6px;height:auto}[data-tooltip]:hover:after,[data-tooltip]:focus:after{padding:.45em .9em}.tooltip-top:after,.tooltip-top:before{top:auto;bottom:calc(100% - 6px);left:0;margin-bottom:12px}.tooltip-top:before{border-color:#111 transparent transparent;margin-bottom:0;left:10px}.tooltip-right:after,.tooltip-right:before{left:100%;margin-left:6px;margin-top:0;top:0}.tooltip-right:before{border-color:transparent #111 transparent transparent;margin-left:-6px;left:100%;top:7px}.tooltip-left:after,.tooltip-left:before{right:100%;margin-right:6px;left:auto;margin-top:0;top:0}.tooltip-left:before{border-color:transparent transparent transparent #111;margin-right:-6px;right:100%;top:7px} +/* Picnic CSS v6.3.2 http://picnicss.com/ */ +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:0;padding:0}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}*{box-sizing:inherit}html,body{font-family:Arial, Helvetica, sans-serif;box-sizing:border-box;height:100%}body{color:#111;font-size:1.1em;line-height:1.5;background:#fff}main{display:block}h1,h2,h3,h4,h5,h6{margin:0;padding:.6em 0}li{margin:0 0 .3em}a{color:#0074d9;text-decoration:none;box-shadow:none;transition:all 0.3s}code{padding:.3em .6em;font-size:.8em;background:#f5f5f5}pre{text-align:left;padding:.3em .6em;background:#f5f5f5;border-radius:.2em}pre code{padding:0}blockquote{padding:0 0 0 1em;margin:0 0 0 .1em;box-shadow:inset 5px 0 rgba(17,17,17,0.3)}label{cursor:pointer}[class^="icon-"]:before,[class*=" icon-"]:before{margin:0 .6em 0 0}i[class^="icon-"]:before,i[class*=" icon-"]:before{margin:0}.label,[data-tooltip]:after,button,.button,[type=submit],.dropimage{display:inline-block;text-align:center;margin:0;padding:.3em .9em;vertical-align:middle;background:#0074d9;color:#fff;border:0;border-radius:.2em;width:auto;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.success.label,.success[data-tooltip]:after,button.success,.success.button,.success[type=submit],.success.dropimage{background:#2ecc40}.warning.label,.warning[data-tooltip]:after,button.warning,.warning.button,.warning[type=submit],.warning.dropimage{background:#ff851b}.error.label,.error[data-tooltip]:after,button.error,.error.button,.error[type=submit],.error.dropimage{background:#ff4136}.pseudo.label,.pseudo[data-tooltip]:after,button.pseudo,.pseudo.button,.pseudo[type=submit],.pseudo.dropimage{background:transparent;color:#111}.label,[data-tooltip]:after{font-size:.6em;padding:.4em .6em;margin-left:1em;line-height:1}button,.button,[type=submit],.dropimage{margin:.3em 0;cursor:pointer;transition:all 0.3s;border-radius:.2em;height:auto;box-shadow:0 0 transparent inset}button:hover,.button:hover,[type=submit]:hover,.dropimage:hover,button:focus,.button:focus,[type=submit]:focus,.dropimage:focus{box-shadow:inset 0 0 0 99em rgba(255,255,255,0.2);border:0}button.pseudo:hover,.pseudo.button:hover,.pseudo[type=submit]:hover,.pseudo.dropimage:hover,button.pseudo:focus,.pseudo.button:focus,.pseudo[type=submit]:focus,.pseudo.dropimage:focus{box-shadow:inset 0 0 0 99em rgba(17,17,17,0.1)}button.active,.active.button,.active[type=submit],.active.dropimage,button:active,.button:active,[type=submit]:active,.dropimage:active,button.pseudo:active,.pseudo.button:active,.pseudo[type=submit]:active,.pseudo.dropimage:active{box-shadow:inset 0 0 0 99em rgba(17,17,17,0.2)}button[disabled],[disabled].button,[disabled][type=submit],[disabled].dropimage{cursor:default;box-shadow:none;background:#bbb}:checked+.toggle,:checked+.toggle:hover{box-shadow:inset 0 0 0 99em rgba(17,17,17,0.2)}[type]+.toggle{padding:.3em .9em;margin-right:0}[type]+.toggle:after,[type]+.toggle:before{display:none}input,textarea,.select select{line-height:1.5;margin:0;height:2.1em;padding:.3em .6em;border:1px solid #ccc;background-color:#fff;border-radius:.2em;transition:all 0.3s;width:100%}input:focus,textarea:focus,.select select:focus{border:1px solid #0074d9;outline:0}textarea{height:auto}[type=file],[type=color]{cursor:pointer}[type=file]{height:auto}select{background:#fff url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyIiBoZWlnaHQ9IjMiPjxwYXRoIGQ9Im0gMCwxIDEsMiAxLC0yIHoiLz48L3N2Zz4=) no-repeat scroll 95% center/10px 15px;background-position:calc(100% - 15px) center;border:1px solid #ccc;border-radius:.2em;cursor:pointer;width:100%;height:2.1em;box-sizing:border-box;padding:.3em .45em;transition:all .3s;-moz-appearance:none;-webkit-appearance:none;appearance:none}select::-ms-expand{display:none}select:focus,select:active{border:1px solid #0074d9;transition:outline 0s}select:-moz-focusring{color:transparent;text-shadow:0 0 0 #111}select option{font-size:inherit;padding:.3em .45em}[type=radio],[type=checkbox]{opacity:0;width:0;position:absolute;display:inline-block}[type=radio]+.checkable:hover:before,[type=checkbox]+.checkable:hover:before,[type=radio]:focus+.checkable:before,[type=checkbox]:focus+.checkable:before{border:1px solid #0074d9}[type=radio]+.checkable,[type=checkbox]+.checkable{position:relative;cursor:pointer;padding-left:1.5em;margin-right:.6em}[type=radio]+.checkable:before,[type=checkbox]+.checkable:before,[type=radio]+.checkable:after,[type=checkbox]+.checkable:after{content:'';position:absolute;display:inline-block;left:0;top:50%;transform:translateY(-50%);font-size:1em;line-height:1em;color:transparent;font-family:sans;text-align:center;box-sizing:border-box;width:1em;height:1em;border-radius:50%;transition:all 0.3s}[type=radio]+.checkable:before,[type=checkbox]+.checkable:before{border:1px solid #aaa}[type=radio]:checked+.checkable:after,[type=checkbox]:checked+.checkable:after{background:#555;transform:scale(0.5) translateY(-100%)}[type=checkbox]+.checkable:before{border-radius:.2em}[type=checkbox]+.checkable:after{content:"✔";background:none;transform:scale(2) translateY(-25%);visibility:hidden;opacity:0}[type=checkbox]:checked+.checkable:after{color:#111;background:none;transform:translateY(-50%);transition:all 0.3s;visibility:visible;opacity:1}table{text-align:left}td,th{padding:.3em 2.4em .3em .6em}th{text-align:left;font-weight:900;color:#fff;background-color:#0074d9}.success th{background-color:#2ecc40}.warning th{background-color:#ff851b}.error th{background-color:#ff4136}.dull th{background-color:#aaa}tr:nth-child(even){background:rgba(0,0,0,0.05)}.flex{display:-ms-flexbox;display:flex;margin-left:-0.6em;width:calc(100% + .6em);flex-wrap:wrap;transition:all .3s ease}.flex>*{box-sizing:border-box;flex:1 1 auto;padding-left:.6em;padding-bottom:.6em}.flex[class*="one"]>*,.flex[class*="two"]>*,.flex[class*="three"]>*,.flex[class*="four"]>*,.flex[class*="five"]>*,.flex[class*="six"]>*,.flex[class*="seven"]>*,.flex[class*="eight"]>*,.flex[class*="nine"]>*,.flex[class*="ten"]>*,.flex[class*="eleven"]>*,.flex[class*="twelve"]>*{flex-grow:0}.flex.grow>*{flex-grow:1}.center{justify-content:center}.one>*{width:100%}.two>*{width:50%}.three>*{width:33.33333%}.four>*{width:25%}.five>*{width:20%}.six>*{width:16.66666%}.seven>*{width:14.28571%}.eight>*{width:12.5%}.nine>*{width:11.11111%}.ten>*{width:10%}.eleven>*{width:9.09091%}.twelve>*{width:8.33333%}@media all and (min-width: 500px){.one-500>*{width:100%}.two-500>*{width:50%}.three-500>*{width:33.33333%}.four-500>*{width:25%}.five-500>*{width:20%}.six-500>*{width:16.66666%}.seven-500>*{width:14.28571%}.eight-500>*{width:12.5%}.nine-500>*{width:11.11111%}.ten-500>*{width:10%}.eleven-500>*{width:9.09091%}.twelve-500>*{width:8.33333%}}@media all and (min-width: 600px){.one-600>*{width:100%}.two-600>*{width:50%}.three-600>*{width:33.33333%}.four-600>*{width:25%}.five-600>*{width:20%}.six-600>*{width:16.66666%}.seven-600>*{width:14.28571%}.eight-600>*{width:12.5%}.nine-600>*{width:11.11111%}.ten-600>*{width:10%}.eleven-600>*{width:9.09091%}.twelve-600>*{width:8.33333%}}@media all and (min-width: 700px){.one-700>*{width:100%}.two-700>*{width:50%}.three-700>*{width:33.33333%}.four-700>*{width:25%}.five-700>*{width:20%}.six-700>*{width:16.66666%}.seven-700>*{width:14.28571%}.eight-700>*{width:12.5%}.nine-700>*{width:11.11111%}.ten-700>*{width:10%}.eleven-700>*{width:9.09091%}.twelve-700>*{width:8.33333%}}@media all and (min-width: 800px){.one-800>*{width:100%}.two-800>*{width:50%}.three-800>*{width:33.33333%}.four-800>*{width:25%}.five-800>*{width:20%}.six-800>*{width:16.66666%}.seven-800>*{width:14.28571%}.eight-800>*{width:12.5%}.nine-800>*{width:11.11111%}.ten-800>*{width:10%}.eleven-800>*{width:9.09091%}.twelve-800>*{width:8.33333%}}@media all and (min-width: 900px){.one-900>*{width:100%}.two-900>*{width:50%}.three-900>*{width:33.33333%}.four-900>*{width:25%}.five-900>*{width:20%}.six-900>*{width:16.66666%}.seven-900>*{width:14.28571%}.eight-900>*{width:12.5%}.nine-900>*{width:11.11111%}.ten-900>*{width:10%}.eleven-900>*{width:9.09091%}.twelve-900>*{width:8.33333%}}@media all and (min-width: 1000px){.one-1000>*{width:100%}.two-1000>*{width:50%}.three-1000>*{width:33.33333%}.four-1000>*{width:25%}.five-1000>*{width:20%}.six-1000>*{width:16.66666%}.seven-1000>*{width:14.28571%}.eight-1000>*{width:12.5%}.nine-1000>*{width:11.11111%}.ten-1000>*{width:10%}.eleven-1000>*{width:9.09091%}.twelve-1000>*{width:8.33333%}}@media all and (min-width: 1100px){.one-1100>*{width:100%}.two-1100>*{width:50%}.three-1100>*{width:33.33333%}.four-1100>*{width:25%}.five-1100>*{width:20%}.six-1100>*{width:16.66666%}.seven-1100>*{width:14.28571%}.eight-1100>*{width:12.5%}.nine-1100>*{width:11.11111%}.ten-1100>*{width:10%}.eleven-1100>*{width:9.09091%}.twelve-1100>*{width:8.33333%}}@media all and (min-width: 1200px){.one-1200>*{width:100%}.two-1200>*{width:50%}.three-1200>*{width:33.33333%}.four-1200>*{width:25%}.five-1200>*{width:20%}.six-1200>*{width:16.66666%}.seven-1200>*{width:14.28571%}.eight-1200>*{width:12.5%}.nine-1200>*{width:11.11111%}.ten-1200>*{width:10%}.eleven-1200>*{width:9.09091%}.twelve-1200>*{width:8.33333%}}@media all and (min-width: 1300px){.one-1300>*{width:100%}.two-1300>*{width:50%}.three-1300>*{width:33.33333%}.four-1300>*{width:25%}.five-1300>*{width:20%}.six-1300>*{width:16.66666%}.seven-1300>*{width:14.28571%}.eight-1300>*{width:12.5%}.nine-1300>*{width:11.11111%}.ten-1300>*{width:10%}.eleven-1300>*{width:9.09091%}.twelve-1300>*{width:8.33333%}}@media all and (min-width: 1400px){.one-1400>*{width:100%}.two-1400>*{width:50%}.three-1400>*{width:33.33333%}.four-1400>*{width:25%}.five-1400>*{width:20%}.six-1400>*{width:16.66666%}.seven-1400>*{width:14.28571%}.eight-1400>*{width:12.5%}.nine-1400>*{width:11.11111%}.ten-1400>*{width:10%}.eleven-1400>*{width:9.09091%}.twelve-1400>*{width:8.33333%}}@media all and (min-width: 1500px){.one-1500>*{width:100%}.two-1500>*{width:50%}.three-1500>*{width:33.33333%}.four-1500>*{width:25%}.five-1500>*{width:20%}.six-1500>*{width:16.66666%}.seven-1500>*{width:14.28571%}.eight-1500>*{width:12.5%}.nine-1500>*{width:11.11111%}.ten-1500>*{width:10%}.eleven-1500>*{width:9.09091%}.twelve-1500>*{width:8.33333%}}@media all and (min-width: 1600px){.one-1600>*{width:100%}.two-1600>*{width:50%}.three-1600>*{width:33.33333%}.four-1600>*{width:25%}.five-1600>*{width:20%}.six-1600>*{width:16.66666%}.seven-1600>*{width:14.28571%}.eight-1600>*{width:12.5%}.nine-1600>*{width:11.11111%}.ten-1600>*{width:10%}.eleven-1600>*{width:9.09091%}.twelve-1600>*{width:8.33333%}}@media all and (min-width: 1700px){.one-1700>*{width:100%}.two-1700>*{width:50%}.three-1700>*{width:33.33333%}.four-1700>*{width:25%}.five-1700>*{width:20%}.six-1700>*{width:16.66666%}.seven-1700>*{width:14.28571%}.eight-1700>*{width:12.5%}.nine-1700>*{width:11.11111%}.ten-1700>*{width:10%}.eleven-1700>*{width:9.09091%}.twelve-1700>*{width:8.33333%}}@media all and (min-width: 1800px){.one-1800>*{width:100%}.two-1800>*{width:50%}.three-1800>*{width:33.33333%}.four-1800>*{width:25%}.five-1800>*{width:20%}.six-1800>*{width:16.66666%}.seven-1800>*{width:14.28571%}.eight-1800>*{width:12.5%}.nine-1800>*{width:11.11111%}.ten-1800>*{width:10%}.eleven-1800>*{width:9.09091%}.twelve-1800>*{width:8.33333%}}@media all and (min-width: 1900px){.one-1900>*{width:100%}.two-1900>*{width:50%}.three-1900>*{width:33.33333%}.four-1900>*{width:25%}.five-1900>*{width:20%}.six-1900>*{width:16.66666%}.seven-1900>*{width:14.28571%}.eight-1900>*{width:12.5%}.nine-1900>*{width:11.11111%}.ten-1900>*{width:10%}.eleven-1900>*{width:9.09091%}.twelve-1900>*{width:8.33333%}}@media all and (min-width: 2000px){.one-2000>*{width:100%}.two-2000>*{width:50%}.three-2000>*{width:33.33333%}.four-2000>*{width:25%}.five-2000>*{width:20%}.six-2000>*{width:16.66666%}.seven-2000>*{width:14.28571%}.eight-2000>*{width:12.5%}.nine-2000>*{width:11.11111%}.ten-2000>*{width:10%}.eleven-2000>*{width:9.09091%}.twelve-2000>*{width:8.33333%}}.full{width:100%}.half{width:50%}.third{width:33.33333%}.two-third{width:66.66666%}.fourth{width:25%}.three-fourth{width:75%}.fifth{width:20%}.two-fifth{width:40%}.three-fifth{width:60%}.four-fifth{width:80%}.sixth{width:16.66666%}.none{display:none}@media all and (min-width: 500px){.full-500{width:100%;display:block}.half-500{width:50%;display:block}.third-500{width:33.33333%;display:block}.two-third-500{width:66.66666%;display:block}.fourth-500{width:25%;display:block}.three-fourth-500{width:75%;display:block}.fifth-500{width:20%;display:block}.two-fifth-500{width:40%;display:block}.three-fifth-500{width:60%;display:block}.four-fifth-500{width:80%;display:block}.sixth-500{width:16.66666%;display:block}}@media all and (min-width: 600px){.full-600{width:100%;display:block}.half-600{width:50%;display:block}.third-600{width:33.33333%;display:block}.two-third-600{width:66.66666%;display:block}.fourth-600{width:25%;display:block}.three-fourth-600{width:75%;display:block}.fifth-600{width:20%;display:block}.two-fifth-600{width:40%;display:block}.three-fifth-600{width:60%;display:block}.four-fifth-600{width:80%;display:block}.sixth-600{width:16.66666%;display:block}}@media all and (min-width: 700px){.full-700{width:100%;display:block}.half-700{width:50%;display:block}.third-700{width:33.33333%;display:block}.two-third-700{width:66.66666%;display:block}.fourth-700{width:25%;display:block}.three-fourth-700{width:75%;display:block}.fifth-700{width:20%;display:block}.two-fifth-700{width:40%;display:block}.three-fifth-700{width:60%;display:block}.four-fifth-700{width:80%;display:block}.sixth-700{width:16.66666%;display:block}}@media all and (min-width: 800px){.full-800{width:100%;display:block}.half-800{width:50%;display:block}.third-800{width:33.33333%;display:block}.two-third-800{width:66.66666%;display:block}.fourth-800{width:25%;display:block}.three-fourth-800{width:75%;display:block}.fifth-800{width:20%;display:block}.two-fifth-800{width:40%;display:block}.three-fifth-800{width:60%;display:block}.four-fifth-800{width:80%;display:block}.sixth-800{width:16.66666%;display:block}}@media all and (min-width: 900px){.full-900{width:100%;display:block}.half-900{width:50%;display:block}.third-900{width:33.33333%;display:block}.two-third-900{width:66.66666%;display:block}.fourth-900{width:25%;display:block}.three-fourth-900{width:75%;display:block}.fifth-900{width:20%;display:block}.two-fifth-900{width:40%;display:block}.three-fifth-900{width:60%;display:block}.four-fifth-900{width:80%;display:block}.sixth-900{width:16.66666%;display:block}}@media all and (min-width: 1000px){.full-1000{width:100%;display:block}.half-1000{width:50%;display:block}.third-1000{width:33.33333%;display:block}.two-third-1000{width:66.66666%;display:block}.fourth-1000{width:25%;display:block}.three-fourth-1000{width:75%;display:block}.fifth-1000{width:20%;display:block}.two-fifth-1000{width:40%;display:block}.three-fifth-1000{width:60%;display:block}.four-fifth-1000{width:80%;display:block}.sixth-1000{width:16.66666%;display:block}}@media all and (min-width: 1100px){.full-1100{width:100%;display:block}.half-1100{width:50%;display:block}.third-1100{width:33.33333%;display:block}.two-third-1100{width:66.66666%;display:block}.fourth-1100{width:25%;display:block}.three-fourth-1100{width:75%;display:block}.fifth-1100{width:20%;display:block}.two-fifth-1100{width:40%;display:block}.three-fifth-1100{width:60%;display:block}.four-fifth-1100{width:80%;display:block}.sixth-1100{width:16.66666%;display:block}}@media all and (min-width: 1200px){.full-1200{width:100%;display:block}.half-1200{width:50%;display:block}.third-1200{width:33.33333%;display:block}.two-third-1200{width:66.66666%;display:block}.fourth-1200{width:25%;display:block}.three-fourth-1200{width:75%;display:block}.fifth-1200{width:20%;display:block}.two-fifth-1200{width:40%;display:block}.three-fifth-1200{width:60%;display:block}.four-fifth-1200{width:80%;display:block}.sixth-1200{width:16.66666%;display:block}}@media all and (min-width: 1300px){.full-1300{width:100%;display:block}.half-1300{width:50%;display:block}.third-1300{width:33.33333%;display:block}.two-third-1300{width:66.66666%;display:block}.fourth-1300{width:25%;display:block}.three-fourth-1300{width:75%;display:block}.fifth-1300{width:20%;display:block}.two-fifth-1300{width:40%;display:block}.three-fifth-1300{width:60%;display:block}.four-fifth-1300{width:80%;display:block}.sixth-1300{width:16.66666%;display:block}}@media all and (min-width: 1400px){.full-1400{width:100%;display:block}.half-1400{width:50%;display:block}.third-1400{width:33.33333%;display:block}.two-third-1400{width:66.66666%;display:block}.fourth-1400{width:25%;display:block}.three-fourth-1400{width:75%;display:block}.fifth-1400{width:20%;display:block}.two-fifth-1400{width:40%;display:block}.three-fifth-1400{width:60%;display:block}.four-fifth-1400{width:80%;display:block}.sixth-1400{width:16.66666%;display:block}}@media all and (min-width: 1500px){.full-1500{width:100%;display:block}.half-1500{width:50%;display:block}.third-1500{width:33.33333%;display:block}.two-third-1500{width:66.66666%;display:block}.fourth-1500{width:25%;display:block}.three-fourth-1500{width:75%;display:block}.fifth-1500{width:20%;display:block}.two-fifth-1500{width:40%;display:block}.three-fifth-1500{width:60%;display:block}.four-fifth-1500{width:80%;display:block}.sixth-1500{width:16.66666%;display:block}}@media all and (min-width: 1600px){.full-1600{width:100%;display:block}.half-1600{width:50%;display:block}.third-1600{width:33.33333%;display:block}.two-third-1600{width:66.66666%;display:block}.fourth-1600{width:25%;display:block}.three-fourth-1600{width:75%;display:block}.fifth-1600{width:20%;display:block}.two-fifth-1600{width:40%;display:block}.three-fifth-1600{width:60%;display:block}.four-fifth-1600{width:80%;display:block}.sixth-1600{width:16.66666%;display:block}}@media all and (min-width: 1700px){.full-1700{width:100%;display:block}.half-1700{width:50%;display:block}.third-1700{width:33.33333%;display:block}.two-third-1700{width:66.66666%;display:block}.fourth-1700{width:25%;display:block}.three-fourth-1700{width:75%;display:block}.fifth-1700{width:20%;display:block}.two-fifth-1700{width:40%;display:block}.three-fifth-1700{width:60%;display:block}.four-fifth-1700{width:80%;display:block}.sixth-1700{width:16.66666%;display:block}}@media all and (min-width: 1800px){.full-1800{width:100%;display:block}.half-1800{width:50%;display:block}.third-1800{width:33.33333%;display:block}.two-third-1800{width:66.66666%;display:block}.fourth-1800{width:25%;display:block}.three-fourth-1800{width:75%;display:block}.fifth-1800{width:20%;display:block}.two-fifth-1800{width:40%;display:block}.three-fifth-1800{width:60%;display:block}.four-fifth-1800{width:80%;display:block}.sixth-1800{width:16.66666%;display:block}}@media all and (min-width: 1900px){.full-1900{width:100%;display:block}.half-1900{width:50%;display:block}.third-1900{width:33.33333%;display:block}.two-third-1900{width:66.66666%;display:block}.fourth-1900{width:25%;display:block}.three-fourth-1900{width:75%;display:block}.fifth-1900{width:20%;display:block}.two-fifth-1900{width:40%;display:block}.three-fifth-1900{width:60%;display:block}.four-fifth-1900{width:80%;display:block}.sixth-1900{width:16.66666%;display:block}}@media all and (min-width: 2000px){.full-2000{width:100%;display:block}.half-2000{width:50%;display:block}.third-2000{width:33.33333%;display:block}.two-third-2000{width:66.66666%;display:block}.fourth-2000{width:25%;display:block}.three-fourth-2000{width:75%;display:block}.fifth-2000{width:20%;display:block}.two-fifth-2000{width:40%;display:block}.three-fifth-2000{width:60%;display:block}.four-fifth-2000{width:80%;display:block}.sixth-2000{width:16.66666%;display:block}}@media all and (min-width: 500px){.none-500{display:none}}@media all and (min-width: 600px){.none-600{display:none}}@media all and (min-width: 700px){.none-700{display:none}}@media all and (min-width: 800px){.none-800{display:none}}@media all and (min-width: 900px){.none-900{display:none}}@media all and (min-width: 1000px){.none-1000{display:none}}@media all and (min-width: 1100px){.none-1100{display:none}}@media all and (min-width: 1200px){.none-1200{display:none}}@media all and (min-width: 1300px){.none-1300{display:none}}@media all and (min-width: 1400px){.none-1400{display:none}}@media all and (min-width: 1500px){.none-1500{display:none}}@media all and (min-width: 1600px){.none-1600{display:none}}@media all and (min-width: 1700px){.none-1700{display:none}}@media all and (min-width: 1800px){.none-1800{display:none}}@media all and (min-width: 1900px){.none-1900{display:none}}@media all and (min-width: 2000px){.none-2000{display:none}}.off-none{margin-left:0}.off-half{margin-left:50%}.off-third{margin-left:33.33333%}.off-two-third{margin-left:66.66666%}.off-fourth{margin-left:25%}.off-three-fourth{margin-left:75%}.off-fifth{margin-left:20%}.off-two-fifth{margin-left:40%}.off-three-fifth{margin-left:60%}.off-four-fifth{margin-left:80%}.off-sixth{margin-left:16.66666%}@media all and (min-width: 500px){.off-none-500{margin-left:0}.off-half-500{margin-left:50%}.off-third-500{margin-left:33.33333%}.off-two-third-500{margin-left:66.66666%}.off-fourth-500{margin-left:25%}.off-three-fourth-500{margin-left:75%}.off-fifth-500{margin-left:20%}.off-two-fifth-500{margin-left:40%}.off-three-fifth-500{margin-left:60%}.off-four-fifth-500{margin-left:80%}.off-sixth-500{margin-left:16.66666%}}@media all and (min-width: 600px){.off-none-600{margin-left:0}.off-half-600{margin-left:50%}.off-third-600{margin-left:33.33333%}.off-two-third-600{margin-left:66.66666%}.off-fourth-600{margin-left:25%}.off-three-fourth-600{margin-left:75%}.off-fifth-600{margin-left:20%}.off-two-fifth-600{margin-left:40%}.off-three-fifth-600{margin-left:60%}.off-four-fifth-600{margin-left:80%}.off-sixth-600{margin-left:16.66666%}}@media all and (min-width: 700px){.off-none-700{margin-left:0}.off-half-700{margin-left:50%}.off-third-700{margin-left:33.33333%}.off-two-third-700{margin-left:66.66666%}.off-fourth-700{margin-left:25%}.off-three-fourth-700{margin-left:75%}.off-fifth-700{margin-left:20%}.off-two-fifth-700{margin-left:40%}.off-three-fifth-700{margin-left:60%}.off-four-fifth-700{margin-left:80%}.off-sixth-700{margin-left:16.66666%}}@media all and (min-width: 800px){.off-none-800{margin-left:0}.off-half-800{margin-left:50%}.off-third-800{margin-left:33.33333%}.off-two-third-800{margin-left:66.66666%}.off-fourth-800{margin-left:25%}.off-three-fourth-800{margin-left:75%}.off-fifth-800{margin-left:20%}.off-two-fifth-800{margin-left:40%}.off-three-fifth-800{margin-left:60%}.off-four-fifth-800{margin-left:80%}.off-sixth-800{margin-left:16.66666%}}@media all and (min-width: 900px){.off-none-900{margin-left:0}.off-half-900{margin-left:50%}.off-third-900{margin-left:33.33333%}.off-two-third-900{margin-left:66.66666%}.off-fourth-900{margin-left:25%}.off-three-fourth-900{margin-left:75%}.off-fifth-900{margin-left:20%}.off-two-fifth-900{margin-left:40%}.off-three-fifth-900{margin-left:60%}.off-four-fifth-900{margin-left:80%}.off-sixth-900{margin-left:16.66666%}}@media all and (min-width: 1000px){.off-none-1000{margin-left:0}.off-half-1000{margin-left:50%}.off-third-1000{margin-left:33.33333%}.off-two-third-1000{margin-left:66.66666%}.off-fourth-1000{margin-left:25%}.off-three-fourth-1000{margin-left:75%}.off-fifth-1000{margin-left:20%}.off-two-fifth-1000{margin-left:40%}.off-three-fifth-1000{margin-left:60%}.off-four-fifth-1000{margin-left:80%}.off-sixth-1000{margin-left:16.66666%}}@media all and (min-width: 1100px){.off-none-1100{margin-left:0}.off-half-1100{margin-left:50%}.off-third-1100{margin-left:33.33333%}.off-two-third-1100{margin-left:66.66666%}.off-fourth-1100{margin-left:25%}.off-three-fourth-1100{margin-left:75%}.off-fifth-1100{margin-left:20%}.off-two-fifth-1100{margin-left:40%}.off-three-fifth-1100{margin-left:60%}.off-four-fifth-1100{margin-left:80%}.off-sixth-1100{margin-left:16.66666%}}@media all and (min-width: 1200px){.off-none-1200{margin-left:0}.off-half-1200{margin-left:50%}.off-third-1200{margin-left:33.33333%}.off-two-third-1200{margin-left:66.66666%}.off-fourth-1200{margin-left:25%}.off-three-fourth-1200{margin-left:75%}.off-fifth-1200{margin-left:20%}.off-two-fifth-1200{margin-left:40%}.off-three-fifth-1200{margin-left:60%}.off-four-fifth-1200{margin-left:80%}.off-sixth-1200{margin-left:16.66666%}}@media all and (min-width: 1300px){.off-none-1300{margin-left:0}.off-half-1300{margin-left:50%}.off-third-1300{margin-left:33.33333%}.off-two-third-1300{margin-left:66.66666%}.off-fourth-1300{margin-left:25%}.off-three-fourth-1300{margin-left:75%}.off-fifth-1300{margin-left:20%}.off-two-fifth-1300{margin-left:40%}.off-three-fifth-1300{margin-left:60%}.off-four-fifth-1300{margin-left:80%}.off-sixth-1300{margin-left:16.66666%}}@media all and (min-width: 1400px){.off-none-1400{margin-left:0}.off-half-1400{margin-left:50%}.off-third-1400{margin-left:33.33333%}.off-two-third-1400{margin-left:66.66666%}.off-fourth-1400{margin-left:25%}.off-three-fourth-1400{margin-left:75%}.off-fifth-1400{margin-left:20%}.off-two-fifth-1400{margin-left:40%}.off-three-fifth-1400{margin-left:60%}.off-four-fifth-1400{margin-left:80%}.off-sixth-1400{margin-left:16.66666%}}@media all and (min-width: 1500px){.off-none-1500{margin-left:0}.off-half-1500{margin-left:50%}.off-third-1500{margin-left:33.33333%}.off-two-third-1500{margin-left:66.66666%}.off-fourth-1500{margin-left:25%}.off-three-fourth-1500{margin-left:75%}.off-fifth-1500{margin-left:20%}.off-two-fifth-1500{margin-left:40%}.off-three-fifth-1500{margin-left:60%}.off-four-fifth-1500{margin-left:80%}.off-sixth-1500{margin-left:16.66666%}}@media all and (min-width: 1600px){.off-none-1600{margin-left:0}.off-half-1600{margin-left:50%}.off-third-1600{margin-left:33.33333%}.off-two-third-1600{margin-left:66.66666%}.off-fourth-1600{margin-left:25%}.off-three-fourth-1600{margin-left:75%}.off-fifth-1600{margin-left:20%}.off-two-fifth-1600{margin-left:40%}.off-three-fifth-1600{margin-left:60%}.off-four-fifth-1600{margin-left:80%}.off-sixth-1600{margin-left:16.66666%}}@media all and (min-width: 1700px){.off-none-1700{margin-left:0}.off-half-1700{margin-left:50%}.off-third-1700{margin-left:33.33333%}.off-two-third-1700{margin-left:66.66666%}.off-fourth-1700{margin-left:25%}.off-three-fourth-1700{margin-left:75%}.off-fifth-1700{margin-left:20%}.off-two-fifth-1700{margin-left:40%}.off-three-fifth-1700{margin-left:60%}.off-four-fifth-1700{margin-left:80%}.off-sixth-1700{margin-left:16.66666%}}@media all and (min-width: 1800px){.off-none-1800{margin-left:0}.off-half-1800{margin-left:50%}.off-third-1800{margin-left:33.33333%}.off-two-third-1800{margin-left:66.66666%}.off-fourth-1800{margin-left:25%}.off-three-fourth-1800{margin-left:75%}.off-fifth-1800{margin-left:20%}.off-two-fifth-1800{margin-left:40%}.off-three-fifth-1800{margin-left:60%}.off-four-fifth-1800{margin-left:80%}.off-sixth-1800{margin-left:16.66666%}}@media all and (min-width: 1900px){.off-none-1900{margin-left:0}.off-half-1900{margin-left:50%}.off-third-1900{margin-left:33.33333%}.off-two-third-1900{margin-left:66.66666%}.off-fourth-1900{margin-left:25%}.off-three-fourth-1900{margin-left:75%}.off-fifth-1900{margin-left:20%}.off-two-fifth-1900{margin-left:40%}.off-three-fifth-1900{margin-left:60%}.off-four-fifth-1900{margin-left:80%}.off-sixth-1900{margin-left:16.66666%}}@media all and (min-width: 2000px){.off-none-2000{margin-left:0}.off-half-2000{margin-left:50%}.off-third-2000{margin-left:33.33333%}.off-two-third-2000{margin-left:66.66666%}.off-fourth-2000{margin-left:25%}.off-three-fourth-2000{margin-left:75%}.off-fifth-2000{margin-left:20%}.off-two-fifth-2000{margin-left:40%}.off-three-fifth-2000{margin-left:60%}.off-four-fifth-2000{margin-left:80%}.off-sixth-2000{margin-left:16.66666%}}nav{position:fixed;top:0;left:0;right:0;height:3em;padding:0 .6em;background:#fff;box-shadow:0 0 0.2em rgba(17,17,17,0.2);z-index:10000;transition:all .3s;transform-style:preserve-3d}nav .brand,nav .menu,nav .burger{float:right;position:relative;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}nav .brand{font-weight:700;float:left;padding:0 .6em;max-width:50%;white-space:nowrap;color:#111}nav .brand *{vertical-align:middle}nav .logo{height:2em;margin-right:.3em}nav .select::after{height:calc(100% - 1px);padding:0;line-height:2.4em}nav .menu>*{margin-right:.6em}nav .burger{display:none}@media all and (max-width: 60em){nav .burger{display:inline-block;cursor:pointer;bottom:-1000em;margin:0}nav .burger ~ .menu,nav .show:checked ~ .burger{position:fixed;min-height:100%;width:0;overflow:hidden;top:0;right:0;bottom:-1000em;margin:0;background:#fff;transition:all .3s ease;transform:none}nav .burger ~ .menu{z-index:11}nav .show:checked ~ .burger{color:transparent;width:100%;border-radius:0;background:rgba(0,0,0,0.2);transition:all .3s ease}nav .show:checked ~ .menu{width:70%;overflow:auto;transition:all .3s ease;height:100vh}nav .burger ~ .menu>*{display:block;margin:.3em;text-align:left;max-width:calc(100% - .6em)}nav .burger ~ .menu>a{padding:.3em .9em}}.stack,.stack .toggle{margin-top:0;margin-bottom:0;display:block;width:100%;text-align:left;border-radius:0}.stack:first-child,.stack:first-child .toggle{border-top-left-radius:.2em;border-top-right-radius:.2em}.stack:last-child,.stack:last-child .toggle{border-bottom-left-radius:.2em;border-bottom-right-radius:.2em}input.stack,textarea.stack,select.stack{transition:border-bottom 0 ease 0;border-bottom-width:0}input.stack:last-child,textarea.stack:last-child,select.stack:last-child{border-bottom-width:1px}input.stack:focus+input,input.stack:focus+textarea,input.stack:focus+select,textarea.stack:focus+input,textarea.stack:focus+textarea,textarea.stack:focus+select,select.stack:focus+input,select.stack:focus+textarea,select.stack:focus+select{border-top-color:#0074d9}.card,.modal .overlay ~ *{position:relative;box-shadow:0;border-radius:.2em;border:1px solid #ccc;overflow:hidden;text-align:left;background:#fff;margin-bottom:.6em;padding:0;transition:all .3s ease}.hidden.card,.modal .overlay ~ .hidden,:checked+.card,.modal .overlay ~ :checked+*,.modal .overlay:checked+*{font-size:0;padding:0;margin:0;border:0}.card>*,.modal .overlay ~ *>*{max-width:100%;display:block}.card>*:last-child,.modal .overlay ~ *>*:last-child{margin-bottom:0}.card header,.modal .overlay ~ * header,.card section,.modal .overlay ~ * section,.card>p,.modal .overlay ~ *>p{padding:.6em .8em}.card section,.modal .overlay ~ * section{padding:.6em .8em 0}.card hr,.modal .overlay ~ * hr{border:none;height:1px;background-color:#eee}.card header,.modal .overlay ~ * header{font-weight:bold;position:relative;border-bottom:1px solid #eee}.card header h1,.modal .overlay ~ * header h1,.card header h2,.modal .overlay ~ * header h2,.card header h3,.modal .overlay ~ * header h3,.card header h4,.modal .overlay ~ * header h4,.card header h5,.modal .overlay ~ * header h5,.card header h6,.modal .overlay ~ * header h6{padding:0;margin:0 2em 0 0;line-height:1;display:inline-block;vertical-align:text-bottom}.card header:last-child,.modal .overlay ~ * header:last-child{border-bottom:0}.card footer,.modal .overlay ~ * footer{padding:.8em}.card p,.modal .overlay ~ * p{margin:.3em 0}.card p:first-child,.modal .overlay ~ * p:first-child{margin-top:0}.card p:last-child,.modal .overlay ~ * p:last-child{margin-bottom:0}.card>p,.modal .overlay ~ *>p{margin:0;padding-right:2.5em}.card .close,.modal .overlay ~ * .close{position:absolute;top:.4em;right:.3em;font-size:1.2em;padding:0 .5em;cursor:pointer;width:auto}.card .close:hover,.modal .overlay ~ * .close:hover{color:#ff4136}.card h1+.close,.modal .overlay ~ * h1+.close{margin:.2em}.card h2+.close,.modal .overlay ~ * h2+.close{margin:.1em}.card .dangerous,.modal .overlay ~ * .dangerous{background:#ff4136;float:right}.modal{text-align:center}.modal>input{display:none}.modal>input ~ *{opacity:0;max-height:0;overflow:hidden}.modal .overlay{top:0;left:0;bottom:0;right:0;position:fixed;margin:0;border-radius:0;background:rgba(17,17,17,0.6);transition:all 0.3s;z-index:100000}.modal .overlay:before,.modal .overlay:after{display:none}.modal .overlay ~ *{border:0;position:fixed;top:50%;left:50%;transform:translateX(-50%) translateY(-50%) scale(0.2, 0.2);z-index:1000000;transition:all 0.3s}.modal>input:checked ~ *{display:block;opacity:1;max-height:10000px;transition:all 0.3s}.modal>input:checked ~ .overlay ~ *{max-height:90%;overflow:auto;-webkit-transform:translateX(-50%) translateY(-50%) scale(1, 1);transform:translateX(-50%) translateY(-50%) scale(1, 1)}@media (max-width: 60em){.modal .overlay ~ *{min-width:90%}}.dropimage{position:relative;display:block;padding:0;padding-bottom:56.25%;overflow:hidden;cursor:pointer;border:0;margin:.3em 0;border-radius:.2em;background-color:#ddd;background-size:cover;background-position:center center;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDAiIGhlaWdodD0iNjQwIiB2ZXJzaW9uPSIxLjEiPjxnIHN0eWxlPSJmaWxsOiMzMzMiPjxwYXRoIGQ9Ik0gMTg3IDIzMCBDIDE3NSAyMzAgMTY1IDI0MCAxNjUgMjUyIEwgMTY1IDMwMCBMIDE2NSA0MDggQyAxNjUgNDIwIDE3NSA0MzAgMTg3IDQzMCBMIDQ2MyA0MzAgQyA0NzUgNDMwIDQ4NSA0MjAgNDg1IDQwOCBMIDQ4NSAzMDAgTCA0ODUgMjUyIEMgNDg1IDI0MCA0NzUgMjMwIDQ2MyAyMzAgTCAxODcgMjMwIHogTSAzNjAgMjU2IEEgNzAgNzIgMCAwIDEgNDMwIDMyOCBBIDcwIDcyIDAgMCAxIDM2MCA0MDAgQSA3MCA3MiAwIDAgMSAyOTAgMzI4IEEgNzAgNzIgMCAwIDEgMzYwIDI1NiB6Ii8+PGNpcmNsZSBjeD0iMzYwIiBjeT0iMzMwIiByPSI0MSIvPjxwYXRoIGQ9Im0yMDUgMjI1IDUtMTAgMjAgMCA1IDEwLTMwIDAiLz48cGF0aCBkPSJNMjg1IDIwMEwyNzAgMjI1IDM3NiAyMjUgMzYxIDIwMCAyODUgMjAwek0zMTAgMjA1TDMzNyAyMDUgMzM3IDIxOCAzMTAgMjE4IDMxMCAyMDV6Ii8+PHBhdGggZD0ibTQwNSAyMjUgNS0xMCAyMCAwIDUgMTAtMzAgMCIvPjwvZz48L3N2Zz4=)}.dropimage input{left:0;width:100%;height:100%;border:0;margin:0;padding:0;opacity:0;cursor:pointer;position:absolute}.tabs{position:relative;overflow:hidden}.tabs>label img{float:left;margin-left:.6em}.tabs>.row{width:calc(100% + 2 * .6em);display:table;table-layout:fixed;position:relative;padding-left:0;transition:all .3s;border-spacing:0;margin:0}.tabs>.row:before,.tabs>.row:after{display:none}.tabs>.row>*,.tabs>.row img{display:table-cell;vertical-align:top;margin:0;width:100%}.tabs>input{display:none}.tabs>input+*{width:100%}.tabs>input+label{width:auto}.two.tabs>.row{width:200%;left:-100%}.two.tabs>input:nth-of-type(1):checked ~ .row{margin-left:100%}.two.tabs>label img{width:48%;margin:4% 0 4% 4%}.three.tabs>.row{width:300%;left:-200%}.three.tabs>input:nth-of-type(1):checked ~ .row{margin-left:200%}.three.tabs>input:nth-of-type(2):checked ~ .row{margin-left:100%}.three.tabs>label img{width:30%;margin:5% 0 5% 5%}.four.tabs>.row{width:400%;left:-300%}.four.tabs>input:nth-of-type(1):checked ~ .row{margin-left:300%}.four.tabs>input:nth-of-type(2):checked ~ .row{margin-left:200%}.four.tabs>input:nth-of-type(3):checked ~ .row{margin-left:100%}.four.tabs>label img{width:22%;margin:4% 0 4% 4%}.tabs>label:first-of-type img{margin-left:0}[data-tooltip]{position:relative}[data-tooltip]:after,[data-tooltip]:before{position:absolute;z-index:10;opacity:0;border-width:0;height:0;padding:0;overflow:hidden;transition:opacity .6s ease, height 0s ease .6s;top:calc(100% - 6px);left:0;margin-top:12px}[data-tooltip]:after{margin-left:0;font-size:.8em;background:#111;content:attr(data-tooltip);white-space:nowrap}[data-tooltip]:before{content:'';width:0;height:0;border-width:0;border-style:solid;border-color:transparent transparent #111;margin-top:0;left:10px}[data-tooltip]:hover:after,[data-tooltip]:focus:after,[data-tooltip]:hover:before,[data-tooltip]:focus:before{opacity:1;border-width:6px;height:auto}[data-tooltip]:hover:after,[data-tooltip]:focus:after{padding:.45em .9em}.tooltip-top:after,.tooltip-top:before{top:auto;bottom:calc(100% - 6px);left:0;margin-bottom:12px}.tooltip-top:before{border-color:#111 transparent transparent;margin-bottom:0;left:10px}.tooltip-right:after,.tooltip-right:before{left:100%;margin-left:6px;margin-top:0;top:0}.tooltip-right:before{border-color:transparent #111 transparent transparent;margin-left:-6px;left:100%;top:7px}.tooltip-left:after,.tooltip-left:before{right:100%;margin-right:6px;left:auto;margin-top:0;top:0}.tooltip-left:before{border-color:transparent transparent transparent #111;margin-right:-6px;right:100%;top:7px} From 6a164ee2d8681196e0eb34eac53f92ac773f8030 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Feb 2017 14:02:14 +0100 Subject: [PATCH 05/23] New release DBUtils 1.2 --- RelNotes-1.2.html | 25 +++++++++++++++++++++++++ UsersGuide.de.html | 13 ++++++------- UsersGuide.html | 12 ++++++------ index.html | 9 +++++++-- 4 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 RelNotes-1.2.html diff --git a/RelNotes-1.2.html b/RelNotes-1.2.html new file mode 100644 index 0000000..fe07758 --- /dev/null +++ b/RelNotes-1.2.html @@ -0,0 +1,25 @@ + + + +DBUtils 1.2 Release Notes + + + +

DBUtils 1.2 Release Notes

+ +

DBUtils 1.2 was released on 02/05/17.

+ +

This is the ninth public release of DBUtils.

+ +

It is intended to be used with Python versions 2.6 and newer.

+ +

Improvements:

+
    +
  • Python 3 is now supported.
  • +
+ + + + \ No newline at end of file diff --git a/UsersGuide.de.html b/UsersGuide.de.html index 562f33b..b944b4e 100644 --- a/UsersGuide.de.html +++ b/UsersGuide.de.html @@ -12,9 +12,9 @@

Benutzeranleitung für DBUtils

Version
-
1.1.1
+
1.2
Released
-

02/04/17

+

02/05/17

Translations

English | German

@@ -159,11 +159,10 @@

Installation als Unterpaket (Plug-In) von Webware for Python

Anforderungen

-

DBUtils arbeitet mit Python 2.3 oder einer neueren Version von Python 2. -Die Module in der Variante für klassisches PyGreSQL benötigen PyGreSQL -Version 3.4 oder höher, während die Module in der allgemeinen Variante -für DB-API 2 mit jedem beliebigen Python-Datenbankadapter-Modul -zusammenarbeiten, das auf DB-API 2 basiert.

+

DBUtils benötigt mindestens Python Version 2.6. Die Module in der Variante +für klassisches PyGreSQL benötigen PyGreSQL Version 3.4 oder höher, während +die Module in der allgemeinen Variante für DB-API 2 mit jedem beliebigen +Python-Datenbankadapter-Modul zusammenarbeiten, das auf DB-API 2 basiert.

Funktionalität

diff --git a/UsersGuide.html b/UsersGuide.html index c3f48ef..bf561b7 100644 --- a/UsersGuide.html +++ b/UsersGuide.html @@ -12,9 +12,9 @@

DBUtils User's Guide

Version
-
1.1.1
+
1.2
Released
-

02/04/17

+

02/05/17

Translations

English | German

@@ -158,10 +158,10 @@

Installation as a Webware for Python subpackage (plug-in)

Requirements

-

DBUtils runs with Python 2.3 or newer Python 2 versions. The modules in -the classic PyGreSQL variant need PyGreSQL version 3.4 or above, while the -modules in the universal DB-API 2 variant run with any Python DB-API 2 -compliant database interface module.

+

DBUtils requires at least Python version 2.6. The modules in the classic +PyGreSQL variant need PyGreSQL version 3.4 or above, while the modules +in the universal DB-API 2 variant run with any Python DB-API 2 compliant +database interface module.

Functionality

diff --git a/index.html b/index.html index 056d09c..84c2085 100644 --- a/index.html +++ b/index.html @@ -46,6 +46,9 @@

Release Notes:

1.1 | 1.1.1
+
+ 1.2 +
@@ -54,11 +57,13 @@

Release Notes:

Downloads

Current/Recommended Version:
- Download DBUtils 1.1.1 + Download DBUtils 1.2 +

(this version supports Python 2.6, 2.7 and 3.x)

Older Versions:
- Download DBUtils 1.0 + Download DBUtils 1.1.1 +

(this version supports Python 2.3 to 2.7)

From c142fa102ec57ded83c97e0435d738f345949d3c Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 3 Aug 2018 23:30:29 +0200 Subject: [PATCH 06/23] New release DBUtils 1.3 --- .gitignore | 9 ++++++++- README.md | 13 ++++++++----- RelNotes-1.3.html | 25 +++++++++++++++++++++++++ UsersGuide.de.html | 4 ++-- UsersGuide.html | 4 ++-- index.html | 7 ++++--- 6 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 RelNotes-1.3.html diff --git a/.gitignore b/.gitignore index 4977316..c18d018 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,13 @@ *.pyo *.swp -Thumbs.db +build +dist .idea +.tox +.pytest_cache + +MANIFEST + +Thumbs.db diff --git a/README.md b/README.md index 3737a3b..794ff9d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ -# DBUtils +DBUtils +======= -DBUtils is a suite of tools providing solid, persistent and pooled connections to a database that can be used in all kinds of multi-threaded environments like Webware for Python or other web application servers. The suite supports DB-API 2 compliant database interfaces and the classic PyGreSQL interface. +DBUtils is a suite of tools providing solid, persistent and pooled connections +to a database that can be used in all kinds of multi-threaded environments +like Webware for Python or other web application servers. The suite supports +DB-API 2 compliant database interfaces and the classic PyGreSQL interface. -The current version supports Python Version 2.3 to 2.7. - -See [Docs/UsersGuide.html](https://htmlpreview.github.io/?https://raw.githubusercontent.com/Cito/DBUtils/master/DBUtils/Docs/UsersGuide.html) for more explanation and usage information. +The current version of DBUtils supports Python versions 2.6, 2.7 and 3.4 - 3.7. +The DBUtils home page can be found here: https://cito.github.io/DBUtils/ diff --git a/RelNotes-1.3.html b/RelNotes-1.3.html new file mode 100644 index 0000000..07c1dd0 --- /dev/null +++ b/RelNotes-1.3.html @@ -0,0 +1,25 @@ + + + +DBUtils 1.3 Release Notes + + + +

DBUtils 1.3 Release Notes

+ +

DBUtils 1.3 was released on 03/08/18.

+ +

This is the tenth public release of DBUtils.

+ +

It is intended to be used with Python versions 2.6, 2.7 or 3.4 - 3.7.

+ +

Improvements:

+
    +
  • Supports context handlers for connections and cursors.
  • +
+ + + + \ No newline at end of file diff --git a/UsersGuide.de.html b/UsersGuide.de.html index b944b4e..cbb6fe5 100644 --- a/UsersGuide.de.html +++ b/UsersGuide.de.html @@ -12,9 +12,9 @@

Benutzeranleitung für DBUtils

Version
-
1.2
+
1.3
Released
-

02/05/17

+

08/03/18

Translations

English | German

diff --git a/UsersGuide.html b/UsersGuide.html index bf561b7..2c9d42b 100644 --- a/UsersGuide.html +++ b/UsersGuide.html @@ -12,9 +12,9 @@

DBUtils User's Guide

Version
-
1.2
+
1.3
Released
-

02/05/17

+

08/03/18

Translations

English | German

diff --git a/index.html b/index.html index 84c2085..0b8b506 100644 --- a/index.html +++ b/index.html @@ -47,7 +47,8 @@

Release Notes:

1.1.1
- 1.2 + 1.2 | + 1.3
@@ -57,8 +58,8 @@

Release Notes:

Downloads

Current/Recommended Version:
- Download DBUtils 1.2 -

(this version supports Python 2.6, 2.7 and 3.x)

+ Download DBUtils 1.3 +

(this version supports Python 2.6, 2.7 and 3.4 to 3.7)

Older Versions:
From 8cc8ff0da1fcc1c66ea827c9674a9b4f92a7dde0 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 3 Aug 2018 23:55:38 +0200 Subject: [PATCH 07/23] Update copyright --- LICENSE | 2 +- UsersGuide.de.html | 2 +- UsersGuide.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index c09beb5..9ecd470 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017 Christoph Zwerschke +Copyright (c) 2018 Christoph Zwerschke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/UsersGuide.de.html b/UsersGuide.de.html index cbb6fe5..0db6530 100644 --- a/UsersGuide.de.html +++ b/UsersGuide.de.html @@ -550,7 +550,7 @@

Autoren

Zusammenfassung

-

DBUtils ist eine Sammlung von Python-Modulen, mit deren Hilfe man in Python +

DBUtils ist eine Sammlung von Python-Modulen, mit deren Hilfe man in Python geschriebene Multithread-Anwendungen auf sichere und effiziente Weise an -Datenbanken anbinden kann. DBUtils wurde mit Blick auf Webware for Python +Datenbanken anbinden kann. DBUtils wurde mit Blick auf Webware for Python als Anwendung und PyGreSQL als PostgreSQL-Datenbankadapter entwickelt, kann aber für beliebige Python-Anwendungen und beliebige auf DB-API 2 beruhende Python-Datenbankadapter verwendet werden.

@@ -132,7 +132,7 @@

Download

heruntergeladen werden:

https://pypi.python.org/pypi/DBUtils

Das Source-Code-Repository befindet sich hier auf GitHub:

-
https://github.com/Cito/DBUtils
+
https://github.com/WebwareForPython/DBUtils

Installation

@@ -446,7 +446,7 @@

PooledDB

Benutzung in Webware for Python

-

Wenn Sie DBUtils verwenden, um von Servlets des Web-Frameworks Webware +

Wenn Sie DBUtils verwenden, um von Servlets des Web-Frameworks Webware for Python auf eine Datenbank zuzugreifen, dann müssen Sie sicherstellen, dass die Generatoren zur Erzeugung von Datenbankverbindungen nur einmal eingerichtet werden, wenn die Anwendung startet, und nicht jedes Mal, wenn @@ -521,9 +521,9 @@

Fehlermeldungen und Feedback

Links

Einige Links zu verwandter und alternativer Software:

Synopsis

-

DBUtils is a suite of Python modules allowing to connect in a safe and +

DBUtils is a suite of Python modules allowing to connect in a safe and efficient way between a threaded Python application and a database. DBUtils -has been written in view of Webware for Python as the application and +has been written in view of Webware for Python as the application and PyGreSQL as the adapter to a PostgreSQL database, but it can be used for any other Python application and DB-API 2 conformant database adapter.

@@ -131,7 +131,7 @@

Download

the Python Package Index at:

https://pypi.python.org/pypi/DBUtils

The source code repository can be found here on GitHub:

-
https://github.com/Cito/DBUtils
+
https://github.com/WebwareForPython/DBUtils

Installation

@@ -408,7 +408,7 @@

PooledDB

Usage in Webware for Python

-

If you are using DBUtils in order to access a database from Webware +

If you are using DBUtils in order to access a database from Webware for Python servlets, you need to make sure that you set up your database connection generators only once when the application starts, and not every time a servlet instance is created. For this purpose, @@ -479,9 +479,9 @@

Bug reports and feedback

Links

Some links to related and alternative software:

    -
  • DBUtils

  • +
  • DBUtils

  • Python

  • -
  • Webware for Python framework

  • +
  • Webware for Python framework

  • Python DB-API 2

  • PostgreSQL database

  • PyGreSQL Python adapter for PostgreSQL

  • From 44a15e2874bbd12bbd9433a7b4cd69c521ad07a1 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 26 Sep 2020 11:42:57 +0200 Subject: [PATCH 09/23] New release DBUtils 1.4 --- README.md | 2 +- RelNotes-1.4.html | 26 ++++++++++++++++++++++++++ UsersGuide.de.html | 16 ++++++++-------- UsersGuide.html | 15 +++++++-------- index.html | 9 ++++++--- 5 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 RelNotes-1.4.html diff --git a/README.md b/README.md index 481657f..9e33489 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,6 @@ to a database that can be used in all kinds of multi-threaded environments like Webware for Python or other web application servers. The suite supports DB-API 2 compliant database interfaces and the classic PyGreSQL interface. -The current version of DBUtils supports Python versions 2.7 and 3.5 - 3.8. +The current version of DBUtils supports Python versions 2.7 and 3.5 to 3.9. The DBUtils home page can be found here: https://webwareforpython.github.io/DBUtils/ diff --git a/RelNotes-1.4.html b/RelNotes-1.4.html new file mode 100644 index 0000000..0565fd0 --- /dev/null +++ b/RelNotes-1.4.html @@ -0,0 +1,26 @@ + + + +DBUtils 1.4 Release Notes + + + +

    DBUtils 1.4 Release Notes

    + +

    DBUtils 1.4 was released on 09/26/20.

    + +

    This version is intended to be used with Python versions 2.7 and 3.5 to 3.9.

    + +

    Improvements:

    +
      +
    • The SteadyDB and SteadyPg classes only reconnect +after the maxusage limit has been reached when the connection is +not currently inside a transaction.
    • +
    + + + + diff --git a/UsersGuide.de.html b/UsersGuide.de.html index 6edea58..948e12a 100644 --- a/UsersGuide.de.html +++ b/UsersGuide.de.html @@ -2,19 +2,18 @@ - + Benutzeranleitung für DBUtils

    Benutzeranleitung für DBUtils

    -
    Version
    -
    1.3
    +
    1.4
    Released
    -

    08/03/18

    +

    09/26/20

    Translations

    English | German

    @@ -159,10 +158,11 @@

    Installation als Unterpaket (Plug-In) von Webware for Python

    Anforderungen

    -

    DBUtils benötigt mindestens Python Version 2.6. Die Module in der Variante -für klassisches PyGreSQL benötigen PyGreSQL Version 3.4 oder höher, während -die Module in der allgemeinen Variante für DB-API 2 mit jedem beliebigen -Python-Datenbankadapter-Modul zusammenarbeiten, das auf DB-API 2 basiert.

    +

    DBUtils unterstützt die Python Versionen 2.7 und 3.5 bis 3.8.

    +

    Die Module in der Variante für klassisches PyGreSQL benötigen PyGreSQL +Version 4.0 oder höher, während die Module in der allgemeinen Variante +für DB-API 2 mit jedem beliebigen Python-Datenbankadapter-Modul zusammenarbeiten, +das auf DB-API 2 basiert.

    Funktionalität

    diff --git a/UsersGuide.html b/UsersGuide.html index cdc14fd..2ec476e 100644 --- a/UsersGuide.html +++ b/UsersGuide.html @@ -2,19 +2,18 @@ - + DBUtils User's Guide

    DBUtils User's Guide

    -
    Version
    -
    1.3
    +
    1.4
    Released
    -

    08/03/18

    +

    09/26/20

    Translations

    English | German

    @@ -158,10 +157,10 @@

    Installation as a Webware for Python subpackage (plug-in)

    Requirements

    -

    DBUtils requires at least Python version 2.6. The modules in the classic -PyGreSQL variant need PyGreSQL version 3.4 or above, while the modules -in the universal DB-API 2 variant run with any Python DB-API 2 compliant -database interface module.

    +

    DBUtils supports Python version 2.6 and Python versions 3.5 to 3.8.

    +

    The modules in the classic PyGreSQL variant need PyGreSQL version 4.0 +or above, while the modules in the universal DB-API 2 variant run with +any Python DB-API 2 compliant database interface module.

    Functionality

    diff --git a/index.html b/index.html index 0b8b506..bc5e638 100644 --- a/index.html +++ b/index.html @@ -48,7 +48,8 @@

    Release Notes:

    1.2 | - 1.3 + 1.3 | + 1.4
    @@ -58,11 +59,13 @@

    Release Notes:

    Downloads

    Current/Recommended Version:
    - Download DBUtils 1.3 -

    (this version supports Python 2.6, 2.7 and 3.4 to 3.7)

    + Download DBUtils 1.4 +

    (this version supports Python 2.7 and 3.5 to 3.9)

    Older Versions:
    + Download DBUtils 1.3 +

    (this version supports Python 2.6, 2.7 and 3.4 to 3.7)

    Download DBUtils 1.1.1

    (this version supports Python 2.3 to 2.7)

    From 6e0ac4fbbc79cc854e96a3f312ee8d227252328d Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 27 Sep 2020 00:03:35 +0200 Subject: [PATCH 10/23] Release version 2.0 of DBUtils --- LICENSE | 2 +- README.md | 11 +- RelNotes-0.8.1.html | 19 -- RelNotes-0.9.1.html | 29 --- RelNotes-0.9.2.html | 26 --- RelNotes-0.9.3.html | 34 ---- RelNotes-0.9.4.html | 26 --- RelNotes-1.0.html | 49 ----- RelNotes-1.1.1.html | 32 ---- RelNotes-1.1.html | 46 ----- RelNotes-1.2.html | 26 --- RelNotes-1.3.html | 26 --- RelNotes-1.4.html | 26 --- changelog.html | 186 ++++++++++++++++++ dbdep.gif | Bin 2653 -> 0 bytes DBUtils.css => dbutils.css | 0 dependencies_db.png | Bin 0 -> 4541 bytes dependencies_pg.png | Bin 0 -> 4305 bytes Doc.css => doc.css | 2 +- DocUtils.css => docutils.css | 0 index.html | 33 +--- UsersGuide.de.html => main.de.html | 294 +++++++++++++---------------- UsersGuide.html => main.html | 266 ++++++++++++-------------- persist.gif | Bin 7378 -> 0 bytes persistent.png | Bin 0 -> 7038 bytes pgdep.gif | Bin 2426 -> 0 bytes pool.gif | Bin 13454 -> 0 bytes pooled.png | Bin 0 -> 12053 bytes 28 files changed, 446 insertions(+), 687 deletions(-) delete mode 100644 RelNotes-0.8.1.html delete mode 100644 RelNotes-0.9.1.html delete mode 100644 RelNotes-0.9.2.html delete mode 100644 RelNotes-0.9.3.html delete mode 100644 RelNotes-0.9.4.html delete mode 100644 RelNotes-1.0.html delete mode 100644 RelNotes-1.1.1.html delete mode 100644 RelNotes-1.1.html delete mode 100644 RelNotes-1.2.html delete mode 100644 RelNotes-1.3.html delete mode 100644 RelNotes-1.4.html create mode 100644 changelog.html delete mode 100644 dbdep.gif rename DBUtils.css => dbutils.css (100%) create mode 100644 dependencies_db.png create mode 100644 dependencies_pg.png rename Doc.css => doc.css (99%) rename DocUtils.css => docutils.css (100%) rename UsersGuide.de.html => main.de.html (67%) rename UsersGuide.html => main.html (67%) delete mode 100644 persist.gif create mode 100644 persistent.png delete mode 100644 pgdep.gif delete mode 100644 pool.gif create mode 100644 pooled.png diff --git a/LICENSE b/LICENSE index 9ecd470..65e7e8b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 Christoph Zwerschke +Copyright (c) 2020 Christoph Zwerschke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9e33489..b35abf9 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ DBUtils ======= DBUtils is a suite of tools providing solid, persistent and pooled connections -to a database that can be used in all kinds of multi-threaded environments -like Webware for Python or other web application servers. The suite supports -DB-API 2 compliant database interfaces and the classic PyGreSQL interface. +to a database that can be used in all kinds of multi-threaded environments. -The current version of DBUtils supports Python versions 2.7 and 3.5 to 3.9. +The suite supports DB-API 2 compliant database interfaces +and the classic PyGreSQL interface. -The DBUtils home page can be found here: https://webwareforpython.github.io/DBUtils/ +The current version 2.0 of DBUtils supports Python versions 2.7 and 3.5 to 3.9. + +The DBUtils home page can be found at https://webwareforpython.github.io/DBUtils/ diff --git a/RelNotes-0.8.1.html b/RelNotes-0.8.1.html deleted file mode 100644 index fbb16f8..0000000 --- a/RelNotes-0.8.1.html +++ /dev/null @@ -1,19 +0,0 @@ - - - -DBUtils 0.8.1 Release Notes - - - -

    DBUtils 0.8.1 Release Notes

    - -

    DBUtils 0.8.1 was released on September 13, 2005.

    - -

    This is the first public release of DBUtils.

    - - - - diff --git a/RelNotes-0.9.1.html b/RelNotes-0.9.1.html deleted file mode 100644 index 55b96b4..0000000 --- a/RelNotes-0.9.1.html +++ /dev/null @@ -1,29 +0,0 @@ - - - -DBUtils 0.9.1 Release Notes - - - -

    DBUtils 0.9.1 Release Notes

    - -

    DBUtils 0.9.1 was released on May 8, 2006.

    - -

    This is the second public release of DBUtils.

    - -

    Changes:

    -
      -
    • Added _closeable attribute and made persistent connections -not closeable by default. This allows PersistentDB to be used -in the same way as you would use PooledDB.
    • -
    • Allowed arguments in the DB-API 2 cursor() method. -MySQLdb is using this to specify cursor classes. (Suggested by Michael Palmer.)
    • -
    • Improved the documentation and added a User's Guide.
    • -
    - - - - diff --git a/RelNotes-0.9.2.html b/RelNotes-0.9.2.html deleted file mode 100644 index b3e566d..0000000 --- a/RelNotes-0.9.2.html +++ /dev/null @@ -1,26 +0,0 @@ - - - -DBUtils 0.9.2 Release Notes - - - -

    DBUtils 0.9.2 Release Notes

    - -

    DBUtils 0.9.2 was released on September 22, 2006.

    - -

    This is the third public release of DBUtils.

    - -

    Changes:

    -
      -
    • Renamed SolidDB to SteadyDB to avoid confusion -with the solidDB storage engine.
    • -
    • Accordingly, renamed SolidPg to SteadyPg.
    • -
    - - - - diff --git a/RelNotes-0.9.3.html b/RelNotes-0.9.3.html deleted file mode 100644 index 72f4fb8..0000000 --- a/RelNotes-0.9.3.html +++ /dev/null @@ -1,34 +0,0 @@ - - - -DBUtils 0.9.3 Release Notes - - - -

    DBUtils 0.9.3 Release Notes

    - -

    DBUtils 0.9.3 was released on May 21, 2007.

    - -

    This is the fourth public release of DBUtils.

    - -

    Changes:

    -
      -
    • Support custom creator functions for database connections. -These can now be used as the first parameter instead of an DB-API module -(suggested by Ezio Vernacotola).
    • -
    • Added destructor for steady connections.
    • -
    • Use setuptools -if available.
    • -
    • Some code cleanup.
    • -
    • Some fixes in the documentation. -Added Chinese translation -of the User's Guide, -kindly contributed by gashero.
    • -
    - - - - diff --git a/RelNotes-0.9.4.html b/RelNotes-0.9.4.html deleted file mode 100644 index 674a6c8..0000000 --- a/RelNotes-0.9.4.html +++ /dev/null @@ -1,26 +0,0 @@ - - - -DBUtils 0.9.4 Release Notes - - - -

    DBUtils 0.9.4 Release Notes

    - -

    DBUtils 0.9.4 was released on July 7, 2007.

    - -

    This is the fifth public release of DBUtils.

    - -

    This release fixes a problem in the destructor code and has been -supplemented with a German User's Guide.

    - -

    Please note that the dbapi parameter has been renamed to -creator in the last release since you can now pass custom -creator functions for database connections instead of DB-API 2 modules.

    - - - - diff --git a/RelNotes-1.0.html b/RelNotes-1.0.html deleted file mode 100644 index 2606375..0000000 --- a/RelNotes-1.0.html +++ /dev/null @@ -1,49 +0,0 @@ - - - -DBUtils 1.0 Release Notes - - - -

    DBUtils 1.0 Release Notes

    - -

    DBUtils 1.0 was released on November 29, 2008.

    - -

    This is the sixth public release of DBUtils.

    - -

    Changes:

    -
      -
    • Added a failures parameter for configuring the exception classes for -which the failover mechanisms is applied (as suggested by Matthew Harriger).
    • -
    • Added a closeable parameter for configuring whether connections -can be closed (otherwise closing connections will be silently ignored).
    • -
    • It is now possible to override defaults via the creator.dbapi -and creator.threadsafety attributes.
    • -
    • Added alias method dedicated_connection for -connection(shareable=False).
    • -
    • Added a version attribute to all exported classes.
    • -
    • Where 0 has the meaning "unlimited", parameters can now be also -set to None instead.
    • -
    • It turned out that threading.local does not work properly with -mod_wsgi, so we use the Python implementation for thread-local data -even when a faster threading.local implementation is available. -A new parameter threadlocal allows you to pass an arbitrary class -such as threading.local if you know it works in your environment.
    • -
    - -

    Bugfixes and Improvements:

    -
      -
    • In some cases, when instance initialization failed or referenced objects -were already destroyed, finalizers could throw exceptions or create infinite -recursion (problem reported by Gregory Pinero and Jehiah Czebotar).
    • -
    • DBUtils now tries harder to find the underlying DB-API 2 module if only a -connection creator function is specified. This had not worked before with the -MySQLdb module (problem reported by Gregory Pinero).
    • -
    - - - - diff --git a/RelNotes-1.1.1.html b/RelNotes-1.1.1.html deleted file mode 100644 index b780067..0000000 --- a/RelNotes-1.1.1.html +++ /dev/null @@ -1,32 +0,0 @@ - - - -DBUtils 1.1.1 Release Notes - - - -

    DBUtils 1.1.1 Release Notes

    - -

    DBUtils 1.1.1 was released on 02/04/17.

    - -

    This bugfix release is the eight public release of DBUtils.

    - -

    It is intended to be used with Python versions 2.3 to 2.7

    - -

    Improvements:

    -
      -
    • Reopen SteadyDB connections when commit or rollback fails -(suggested by Ben Hoyt).
    • -
    - -

    Bugfixes:

    -
      -
    • Fixed a problem when running under Jython (reported by Vitaly Kruglikov).
    • -
    - - - - diff --git a/RelNotes-1.1.html b/RelNotes-1.1.html deleted file mode 100644 index 6cdb156..0000000 --- a/RelNotes-1.1.html +++ /dev/null @@ -1,46 +0,0 @@ - - - -DBUtils 1.1 Release Notes - - - -

    DBUtils 1.1 Release Notes

    - -

    DBUtils 1.1 was released on August 14, 2011.

    - -

    This is the seventh public release of DBUtils.

    - -

    Improvements:

    -
      -
    • The transparent reopening of connections is actually an undesired behavior -if it happens during database transactions. In these cases, the transaction -should fail and the error be reported back to the application instead of the -rest of the transaction being executed in a new connection and therefore in -a new transaction. Therefore DBUtils now allows suspending the transparent -reopening during transactions. All you need to do is indicate the beginning -of a transaction by calling the begin() method of the connection. -DBUtils makes sure that this method always exists, even if the database driver -does not support it.
    • -
    • If the database driver supports a ping() method, then DBUtils -can use it to check whether connections are alive instead of just trying -to use the connection and reestablishing it in case it was dead. Since these -checks are done at the expense of some performance, you have exact control -when these are executed via the new ping parameter.
    • -
    • PooledDB has got another new parameter reset for -controlling how connections are reset before being put back into the pool.
    • -
    - -

    Bugfixes:

    -
      -
    • Fixed propagation of error messages when the connection was lost.
    • -
    • Fixed an issue with the setoutputsize() cursor method.
    • -
    • Fixed some minor issues with the DBUtilsExample for Webware.
    • -
    - - - - diff --git a/RelNotes-1.2.html b/RelNotes-1.2.html deleted file mode 100644 index a90eae2..0000000 --- a/RelNotes-1.2.html +++ /dev/null @@ -1,26 +0,0 @@ - - - -DBUtils 1.2 Release Notes - - - -

    DBUtils 1.2 Release Notes

    - -

    DBUtils 1.2 was released on 02/05/17.

    - -

    This is the ninth public release of DBUtils.

    - -

    It is intended to be used with Python versions 2.6 and newer.

    - -

    Improvements:

    -
      -
    • Python 3 is now supported.
    • -
    - - - - diff --git a/RelNotes-1.3.html b/RelNotes-1.3.html deleted file mode 100644 index 05f8614..0000000 --- a/RelNotes-1.3.html +++ /dev/null @@ -1,26 +0,0 @@ - - - -DBUtils 1.3 Release Notes - - - -

    DBUtils 1.3 Release Notes

    - -

    DBUtils 1.3 was released on 03/08/18.

    - -

    This is the tenth public release of DBUtils.

    - -

    It is intended to be used with Python versions 2.6, 2.7 or 3.4 - 3.7.

    - -

    Improvements:

    -
      -
    • Supports context handlers for connections and cursors.
    • -
    - - - - diff --git a/RelNotes-1.4.html b/RelNotes-1.4.html deleted file mode 100644 index 0565fd0..0000000 --- a/RelNotes-1.4.html +++ /dev/null @@ -1,26 +0,0 @@ - - - -DBUtils 1.4 Release Notes - - - -

    DBUtils 1.4 Release Notes

    - -

    DBUtils 1.4 was released on 09/26/20.

    - -

    This version is intended to be used with Python versions 2.7 and 3.5 to 3.9.

    - -

    Improvements:

    -
      -
    • The SteadyDB and SteadyPg classes only reconnect -after the maxusage limit has been reached when the connection is -not currently inside a transaction.
    • -
    - - - - diff --git a/changelog.html b/changelog.html new file mode 100644 index 0000000..f85ecbb --- /dev/null +++ b/changelog.html @@ -0,0 +1,186 @@ + + + + + +Changelog for DBUtils + + + +
    +

    Changelog for DBUtils

    + +
    +

    2.0

    +

    DBUtils 2.0 was released on September 26, 2020.

    +

    It is intended to be used with Python versions 2.7 and 3.5 to 3.9.

    +

    Changes:

    +
      +
    • DBUtils does not act as a Webware plugin anymore, it is now just an ordinary +Python package (of course it could be used as such also before).

    • +
    • The Webware Examples folder has been removed.

    • +
    • Folders, packages and modules have been renamed to lower-case. +Particularly, you need to import dbutils instead of DBUtils now.

    • +
    • The internal naming conventions have also been changed to comply with PEP8.

    • +
    • The documentation has been adapted to reflect the changes in this version.

    • +
    • This changelog has been compiled from the former release notes.

    • +
    +
    +
    +

    1.4

    +

    DBUtils 1.4 was released on September 26, 2020.

    +

    It is intended to be used with Python versions 2.7 and 3.5 to 3.9.

    +

    Improvements:

    +
      +
    • The SteadyDB and SteadyPg classes only reconnect after the +maxusage limit has been reached when the connection is not currently +inside a transaction.

    • +
    +
    +
    +

    1.3

    +

    DBUtils 1.3 was released on March 3, 2018.

    +

    It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.

    +

    Improvements:

    +
      +
    • This version now supports context handlers for connections and cursors.

    • +
    +
    +
    +

    1.2

    +

    DBUtils 1.2 was released on February 5, 2017.

    +

    It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.

    +
    +
    +

    1.1.1

    +

    DBUtils 1.1.1 was released on February 4, 2017.

    +

    It is intended to be used with Python versions 2.3 to 2.7.

    +

    Improvements:

    +
      +
    • Reopen SteadyDB connections when commit or rollback fails +(suggested by Ben Hoyt).

    • +
    +

    Bugfixes:

    +
      +
    • Fixed a problem when running under Jython (reported by Vitaly Kruglikov).

    • +
    +
    +
    +

    1.1

    +

    DBUtils 1.1 was released on August 14, 2011.

    +

    Improvements:

    +
      +
    • The transparent reopening of connections is actually an undesired behavior +if it happens during database transactions. In these cases, the transaction +should fail and the error be reported back to the application instead of the +rest of the transaction being executed in a new connection and therefore in +a new transaction. Therefore DBUtils now allows suspending the transparent +reopening during transactions. All you need to do is indicate the beginning +of a transaction by calling the begin() method of the connection. +DBUtils makes sure that this method always exists, even if the database +driver does not support it.

    • +
    • If the database driver supports a ping() method, then DBUtils can use it +to check whether connections are alive instead of just trying to use the +connection and reestablishing it in case it was dead. Since these checks are +done at the expense of some performance, you have exact control when these +are executed via the new ping parameter.

    • +
    • PooledDB has got another new parameter reset for controlling how +connections are reset before being put back into the pool.

    • +
    +

    Bugfixes:

    +
      +
    • Fixed propagation of error messages when the connection was lost.

    • +
    • Fixed an issue with the setoutputsize() cursor method.

    • +
    • Fixed some minor issues with the DBUtilsExample for Webware.

    • +
    +
    +
    +

    1.0

    +

    DBUtils 1.0 was released on November 29, 2008.

    +

    It is intended to be used with Python versions 2.2 to 2.6.

    +

    Changes:

    +
      +
    • Added a failures parameter for configuring the exception classes for +which the failover mechanisms is applied (as suggested by Matthew Harriger).

    • +
    • Added a closeable parameter for configuring whether connections can be +closed (otherwise closing connections will be silently ignored).

    • +
    • It is now possible to override defaults via the creator.dbapi and +creator.threadsafety attributes.

    • +
    • Added an alias method dedicated_connection as a shorthand for +connection(shareable=False).

    • +
    • Added a version attribute to all exported classes.

    • +
    • Where the value 0 has the meaning "unlimited", parameters can now be also +set to the value None instead.

    • +
    • It turned out that threading.local does not work properly with +mod_wsgi, so we use the Python implementation for thread-local data +even when a faster threading.local implementation is available. +A new parameter threadlocal allows you to pass an arbitrary class +such as threading.local if you know it works in your environment.

    • +
    +

    Bugfixes and improvements:

    +
      +
    • In some cases, when instance initialization failed or referenced objects +were already destroyed, finalizers could throw exceptions or create infinite +recursion (problem reported by Gregory Pinero and Jehiah Czebotar).

    • +
    • DBUtils now tries harder to find the underlying DB-API 2 module if only a +connection creator function is specified. This had not worked before with +the MySQLdb module (problem reported by Gregory Pinero).

    • +
    +
    +
    +

    0.9.4

    +

    DBUtils 0.9.4 was released on July 7, 2007.

    +

    This release fixes a problem in the destructor code and has been supplemented +with a German User's Guide.

    +

    Again, please note that the dbapi parameter has been renamed to creator +in the last release, since you can now pass custom creator functions +for database connections instead of DB-API 2 modules.

    +
    +
    +

    0.9.3

    +

    DBUtils 0.9.3 was released on May 21, 2007.

    +

    Changes:

    +
      +
    • Support custom creator functions for database connections. +These can now be used as the first parameter instead of an DB-API module +(suggested by Ezio Vernacotola).

    • +
    • Added destructor for steady connections.

    • +
    • Use setuptools if available.

    • +
    • Some code cleanup.

    • +
    • Some fixes in the documentation. +Added Chinese translation of the User's Guide, kindly contributed by gashero.

    • +
    +
    +
    +

    0.9.2

    +

    DBUtils 0.9.2 was released on September 22, 2006.

    +

    It is intended to be used with Python versions 2.2 to 2.5.

    +

    Changes:

    +
      +
    • Renamed SolidDB to SteadyDB to avoid confusion with the "solidDB" +storage engine. Accordingly, renamed SolidPg to SteadyPg.

    • +
    +
    +
    +

    0.9.1

    +

    DBUtils 0.9.1 was released on May 8, 2006.

    +

    It is intended to be used with Python versions 2.2 to 2.4.

    +

    Changes:

    +
      +
    • Added _closeable attribute and made persistent connections not closeable +by default. This allows PersistentDB to be used in the same way as you +would use PooledDB.

    • +
    • Allowed arguments in the DB-API 2 cursor() method. MySQLdb is using this +to specify cursor classes. (Suggested by Michael Palmer.)

    • +
    • Improved the documentation and added a User's Guide.

    • +
    +
    +
    +

    0.8.1 - 2005-09-13

    +

    DBUtils 0.8.1 was released on September 13, 2005.

    +

    It is intended to be used with Python versions 2.0 to 2.4.

    +

    This is the first public release of DBUtils.

    +
    +
    + + diff --git a/dbdep.gif b/dbdep.gif deleted file mode 100644 index 8554b62b242c5f9e58916c99d1ea419c18701f18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2653 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 OE979~Y+Yp;koteBsk^NJ literal 0 HcmV?d00001 diff --git a/dependencies_pg.png b/dependencies_pg.png new file mode 100644 index 0000000000000000000000000000000000000000..380def9a0bba68c76ba73cd87e9d946f6c8147b6 GIT binary patch literal 4305 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 - + @@ -30,28 +30,11 @@

    Multi-threaded database connections

    Documentation

    -

    User's Guide

    -
    English | German
    - -

    Release Notes:

    -
    - 0.8.1 | - 0.9.1 | - 0.9.2 | - 0.9.3 | - 0.9.4 -
    -
    - 1.0 | - 1.1 | - 1.1.1 -
    -
    - 1.2 | - 1.3 | - 1.4 -
    -
    +

    User's Guide

    +
    English | German
    +
    +
    @@ -59,15 +42,13 @@

    Release Notes:

    Downloads

    Current/Recommended Version:
    - Download DBUtils 1.4 + Download DBUtils 2.0

    (this version supports Python 2.7 and 3.5 to 3.9)

    Older Versions:
    Download DBUtils 1.3

    (this version supports Python 2.6, 2.7 and 3.4 to 3.7)

    - Download DBUtils 1.1.1 -

    (this version supports Python 2.3 to 2.7)

diff --git a/UsersGuide.de.html b/main.de.html similarity index 67% rename from UsersGuide.de.html rename to main.de.html index 948e12a..0720d75 100644 --- a/UsersGuide.de.html +++ b/main.de.html @@ -2,128 +2,125 @@ - + Benutzeranleitung für DBUtils - +

Benutzeranleitung für DBUtils

Version
-
1.4
-
Released
-

09/26/20

-
+
2.0
Translations
-

English | German

+

English | German

Zusammenfassung

DBUtils ist eine Sammlung von Python-Modulen, mit deren Hilfe man in Python geschriebene Multithread-Anwendungen auf sichere und effiziente Weise an -Datenbanken anbinden kann. DBUtils wurde mit Blick auf Webware for Python -als Anwendung und PyGreSQL als PostgreSQL-Datenbankadapter entwickelt, -kann aber für beliebige Python-Anwendungen und beliebige auf DB-API 2 -beruhende Python-Datenbankadapter verwendet werden.

+Datenbanken anbinden kann.

+

DBUtils wurde ursprünglich speziell für Webware for Python als Anwendung +und PyGreSQL als PostgreSQL-Datenbankadapter entwickelt, +kann aber inzwischen für beliebige Python-Anwendungen und beliebige +auf DB-API 2 beruhende Python-Datenbankadapter verwendet werden.

Module

DBUtils ist als Python-Package realisiert worden, das aus zwei verschiedenen Gruppen von Modulen besteht: Einer Gruppe zur Verwendung mit beliebigen -DB-API-2-Datenbankadaptern, und einer Gruppe zur Verwendung mit dem klassischen PyGreSQL-Datenbankadapter-Modul.

+DB-API-2-Datenbankadaptern, und einer Gruppe zur Verwendung mit dem klassischen +PyGreSQL-Datenbankadapter-Modul.

--++ - + - + - + - +

Allgemeine Variante für beliebige DB-API-2-Adapter

SteadyDB.py

steady_db

Gehärtete DB-API-2-Datenbankverbindungen

PooledDB.py

pooled_db

Pooling für DB-API-2-Datenbankverbindungen

PersistentDB.py

persistent_db

Persistente DB-API-2-Datenbankverbindungen

SimplePooledDB.py

simple_pooled_db

Einfaches Pooling für DB-API 2

--++ - + - + - + - +

Variante speziell für den klassischen PyGreSQL-Adapter

SteadyPg.py

steady_pg

Gehärtete klassische PyGreSQL-Verbindungen

PooledPg.py

pooled_pg

Pooling für klassische PyGreSQL-Verbindungen

PersistentPg.py

persistent_pg

Persistente klassische PyGreSQL-Verbindungen

SimplePooledPg.py

simple_pooled_pg

Einfaches Pooling für klassisches PyGreSQL

Die Abhängigkeiten der Module in der Variante für beliebige DB-API-2-Adapter sind im folgenden Diagramm dargestellt:

-dbdep.gif +dependencies_db.png

Die Abhängigkeiten der Module in der Variante für den klassischen PyGreSQL-Adapter sehen ähnlich aus:

-pgdep.gif +depdependencies_pg.png

Download

@@ -135,31 +132,19 @@

Download

Installation

-
-

Installation als eigenständiges Paket

-

Wenn Sie DBUtils für andere Anwendungen als Webware for Python verwenden -möchten, empfiehlt es sich, das Paket auf die übliche Weise zu installieren:

+
+

Installation

+

Das Paket kann auf die übliche Weise installiert werden:

python setup.py install
-

Sie können auch pip für Download und Installation verwenden:

+

Noch einfacher ist, das Paket in einem Schritt mit pip automatisch +herunterzuladen und zu installieren:

pip install DBUtils
-
-

Installation als Unterpaket (Plug-In) von Webware for Python

-

Wenn Sie DBUtils nur als Ergänzung für das Web-Framework Webware for Python -verwenden wollen, sollten Sie DBUtils als Webware-Plug-In installieren:

-
python setup.py install --install-lib=/pfad/zu/Webware
-

Ersetzen Sie /pfad/zu/Webware hierbei durch den Pfad zum Wurzelverzeichnis -der Installation von Webware for Python. Sie müssen auch das Installationsskript -von Webware for Python laufen lassen, wenn dies noch nicht geschehen ist, oder -wenn Sie DBUtils in die Webware-Dokumentation integrieren wollen:

-
cd /pfad/zu/Webware
-python install.py
-

Anforderungen

-

DBUtils unterstützt die Python Versionen 2.7 und 3.5 bis 3.8.

-

Die Module in der Variante für klassisches PyGreSQL benötigen PyGreSQL +

DBUtils unterstützt die Python Versionen 2.7 und 3.5 bis 3.9.

+

Die Module in der Variante für klassisches PyGreSQL benötigen PyGreSQL Version 4.0 oder höher, während die Module in der allgemeinen Variante für DB-API 2 mit jedem beliebigen Python-Datenbankadapter-Modul zusammenarbeiten, das auf DB-API 2 basiert.

@@ -168,44 +153,49 @@

Anforderungen

Funktionalität

Dieser Abschnitt verwendet nur die Bezeichnungen der DB-API-2-Variante, aber Entsprechendes gilt auch für die PyGreSQL-Variante.

-
-

SimplePooledDB

-

DBUtils.SimplePooledDB ist eine sehr elementare Referenz-Implementierung -eines Pools von Datenbankverbindungen. Hiermit ist ein Vorratsspeicher an -Datenbankverbindungen gemeint, aus dem sich die Python-Anwendung bedienen kann. -Diese Implementierung ist weit weniger ausgefeilt als das eigentliche -PooledDB-Modul und stellt insbesondere keine Ausfallsicherung zur Verfügung. -DBUtils.SimplePooledDB ist im Wesentlichen identisch mit dem zu Webware for -Python gehörenden Modul MiscUtils.DBPool. Es ist eher zur Verdeutlichung -des Konzepts gedacht, als zum Einsatz im produktiven Betrieb.

+

DBUtils installiert sich als Paket dbutils, das alle hier beschriebenen +Module enthält. Jedes dieser Modul enthält im Wesentlichen eine Klasse, die +einen analogen Namen trägt und die jeweilige Funktionalität bereitstellt. +So enthält z.B. das Modul dbutils.pooled_db die Klasse PooledDB.

+
+

SimplePooledDB (simple_pooled_db)

+

Die Klasse SimplePooledDB in dbutils.simple_pooled_db ist eine sehr +elementare Referenz-Implementierung eines Pools von Datenbankverbindungen. +Hiermit ist ein Vorratsspeicher an Datenbankverbindungen gemeint, aus dem sich +die Python-Anwendung bedienen kann. Diese Implementierung ist weit weniger +ausgefeilt als das eigentliche pooled_db-Modul und stellt insbesondere +keine Ausfallsicherung zur Verfügung. dbutils.simple_pooled_db ist im +Wesentlichen identisch mit dem zu Webware for Python gehörenden Modul +MiscUtils.DBPool. Es ist eher zur Verdeutlichung des Konzepts gedacht, +als zum Einsatz im produktiven Betrieb.

-
-

SteadyDB

-

DBUtils.SteadyDB ist ein Modul, das "gehärtete" Datenbankverbindungen -bereitstellt, denen gewöhnlichen Verbindungen eines DB-API-2-Datenbankadapters -zugrunde liegen. Eine "gehärtete" Verbindung wird bei Zugriff automatisch, -ohne dass die Anwendung dies bemerkt, wieder geöffnet, wenn sie geschlossen -wurde, die Datenbankverbindung unterbrochen wurde, oder wenn sie öfter als -ein optionales Limit genutzt wurde.

+
+

SteadyDBConnection (steady_db)

+

Die Klasse SteadyDBConnection im Modul dbutils.steady_db stellt +"gehärtete" Datenbankverbindungen bereit, denen gewöhnlichen Verbindungen +eines DB-API-2-Datenbankadapters zugrunde liegen. Eine "gehärtete" Verbindung +wird bei Zugriff automatisch, ohne dass die Anwendung dies bemerkt, wieder +geöffnet, wenn sie geschlossen wurde, die Datenbankverbindung unterbrochen +wurde, oder wenn sie öfter als ein optionales Limit genutzt wurde.

Ein typisches Beispiel wo dies benötig wird, ist, wenn die Datenbank neu gestartet wurde, während Ihre Anwendung immer noch läuft und Verbindungen zur Datenbank offen hat, oder wenn Ihre Anwendung auf eine entfernte Datenbank über ein Netzwerk zugreift, das durch eine Firewall geschützt ist, und die Firewall neu gestartet wurde und dabei ihren Verbindungsstatus verloren hat.

-

Normalerweise benutzen Sie das SteadyDB-Modul nicht direkt; es wird aber -von den beiden nächsten Modulen benötigt, PersistentDB und PooledDB.

+

Normalerweise benutzen Sie das steady_db-Modul nicht direkt; es wird aber +von den beiden nächsten Modulen benötigt, persistent_db und pooled_db.

-
-

PersistentDB

-

DBUtils.PersistentDB stellt gehärtete, thread-affine, persistente -Datenbankverbindungen zur Verfügung, unter Benutzung eines beliebigen -DB-API-2-Datenbankadapters. Mit "thread-affin" und "persistent" ist -hierbei gemeint, dass die einzelnen Datenbankverbindungen den jeweiligen -Threads fest zugeordnet bleiben und während der Laufzeit des Threads nicht -geschlossen werden.

+
+

PersistentDB (persistent_db)

+

Die Klasse PersistentDB im Modul dbutils.persistent_db stellt +gehärtete, thread-affine, persistente Datenbankverbindungen zur Verfügung, +unter Benutzung eines beliebigen DB-API-2-Datenbankadapters. Mit "thread-affin" +und "persistent" ist hierbei gemeint, dass die einzelnen Datenbankverbindungen +den jeweiligen Threads fest zugeordnet bleiben und während der Laufzeit des +Threads nicht geschlossen werden.

Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie -PersistentDB-Datenbankverbindungen einsetzen:

-persist.gif +persistent_db-Datenbankverbindungen einsetzen:

+persistent.png

Immer wenn ein Thread eine Datenbankverbindung zum ersten Mal öffnet, wird eine neue Datenbankverbindung geöffnet, die von da an immer wieder für genau diesen Thread verwendet wird. Wenn der Thread die Datenbankverbindung schließt, @@ -213,25 +203,25 @@

PersistentDB

gleiche Thread wieder eine Datenbankverbindung anfordert, diese gleiche bereits geöffnete Datenbankverbindung wieder verwendet werden kann. Die Verbindung wird automatisch geschlossen, wenn der Thread beendet wird.

-

Kurz gesagt versucht PersistentDB Datenbankverbindungen wiederzuverwerten, +

Kurz gesagt versucht persistent_db Datenbankverbindungen wiederzuverwerten, um die Gesamteffizienz der Datenbankzugriffe Ihrer Multithread-Anwendungen zu steigern, aber es wird dabei sichergestellt, dass verschiedene Threads niemals die gleiche Verbindung benutzen.

-

Daher arbeitet PersistentDB sogar dann problemlos, wenn der zugrunde +

Daher arbeitet persistent_db sogar dann problemlos, wenn der zugrunde liegende DB-API-2-Datenbankadapter nicht thread-sicher auf der Verbindungsebene ist, oder wenn parallele Threads Parameter der Datenbank-Sitzung verändern oder Transaktionen mit mehreren SQL-Befehlen durchführen.

-
-

PooledDB

-

DBUtils.PooledDB stellt, unter Benutzung eines beliebigen -DB-API-2-Datenbankadapters, einen Pool von gehärteten, thread-sicheren -Datenbankverbindungen zur Verfügung, die automatisch, ohne dass die Anwendung -dies bemerkt, wiederverwendet werden.

+
+

PooledDB (pooled_db)

+

Die Klasse PooledDB im Modul dbutils.pooled_db stellt, unter Benutzung +eines beliebigen DB-API-2-Datenbankadapters, einen Pool von gehärteten, +thread-sicheren Datenbankverbindungen zur Verfügung, die automatisch, ohne dass +die Anwendung dies bemerkt, wiederverwendet werden.

Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie -PooledDB-Datenbankverbindungen einsetzen:

-pool.gif -

Wie im Diagramm angedeutet, kann PooledDB geöffnete Datenbankverbindungen +pooled_db-Datenbankverbindungen einsetzen:

+pooled.png +

Wie im Diagramm angedeutet, kann pooled_db geöffnete Datenbankverbindungen den verschiedenen Threads beliebig zuteilen. Dies geschieht standardmäßig, wenn Sie den Verbindungspool mit einem positiven Wert für maxshared einrichten und der zugrunde liegende DB-API-2-Datenbankadapter auf der Verbindungsebene @@ -247,23 +237,23 @@

PooledDB

Datenbankverbindungen zurückgegeben, damit sie wiederverwertet werden kann.

Wenn der zugrunde liegende DB-API-Datenbankadapter nicht thread-sicher ist, werden Thread-Locks verwendet, um sicherzustellen, dass die -PooledDB-Verbindungen dennoch thread-sicher sind. Sie brauchen sich also +pooled_db-Verbindungen dennoch thread-sicher sind. Sie brauchen sich also hierum keine Sorgen zu machen, aber Sie sollten darauf achten, dedizierte Datenbankverbindungen zu verwenden, sobald Sie Parameter der Datenbanksitzung verändern oder Transaktionen mit mehreren SQL-Befehlen ausführen.

Die Qual der Wahl

-

Sowohl PersistentDB als auch PooledDB dienen dem gleichen Zweck, +

Sowohl persistent_db als auch pooled_db dienen dem gleichen Zweck, nämlich die Effizienz des Datenbankzugriffs durch Wiederverwendung von Datenbankverbindungen zu steigern, und dabei gleichzeitig die Stabilität zu gewährleisten, selbst wenn die Datenbankverbindung unterbrochen wird.

Welches der beiden Module sollte also verwendet werden? Nach den obigen -Erklärungen ist es klar, dass PersistentDB dann sinnvoller ist, wenn +Erklärungen ist es klar, dass persistent_db dann sinnvoller ist, wenn Ihre Anwendung eine gleich bleibende Anzahl Threads verwendet, die häufig auf die Datenbank zugreifen. In diesem Fall werden Sie ungefähr die gleiche Anzahl geöffneter Datenbankverbindungen erhalten. Wenn jedoch Ihre Anwendung -häufig Threads beendet und neu startet, dann ist PooledDB die bessere +häufig Threads beendet und neu startet, dann ist pooled_db die bessere Lösung, die auch mehr Möglichkeiten zur Feineinstellung zur Verbesserung der Effizienz erlaubt, insbesondere bei Verwendung eines thread-sicheren DB-API-2-Datenbankadapters.

@@ -277,17 +267,17 @@

Benutzung

der Initialisierung auch einige Unterschiede, sowohl zwischen den "Pooled"- und den "Persistent"-Varianten, als auch zwischen den DB-API-2- und den PyGreSQL-Varianten.

-

Wir werden hier nur auf das PersistentDB-Modul und das etwas kompliziertere -PooledDB-Modul eingehen. Einzelheiten zu den anderen Modulen finden Sie +

Wir werden hier nur auf das persistent_db-Modul und das etwas kompliziertere +pooled_db-Modul eingehen. Einzelheiten zu den anderen Modulen finden Sie in deren Docstrings. Unter Verwendung der Python-Interpreter-Konsole können Sie -sich die Dokumentation des PooledDB-Moduls wie folgt anzeigen lassen (dies +sich die Dokumentation des pooled_db-Moduls wie folgt anzeigen lassen (dies funktioniert entsprechend auch mit den anderen Modulen):

-
help(PooledDB)
-
-

PersistentDB

-

Wenn Sie das PersistentDB-Modul einsetzen möchten, müssen Sie zuerst einen +

help(pooled_db)
+
+

PersistentDB (persistent_db)

+

Wenn Sie das persistent_db-Modul einsetzen möchten, müssen Sie zuerst einen Generator für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten, -indem Sie eine Instanz der Klasse PersistentDB erzeugen, wobei Sie folgende +indem Sie eine Instanz der Klasse persistent_db erzeugen, wobei Sie folgende Parameter angeben müssen:

  • creator: entweder eine Funktion, die neue DB-API-2-Verbindungen @@ -322,14 +312,14 @@

    PersistentDB

    möchten, dass jede Verbindung Ihrer lokalen Datenbank meinedb 1000 mal wiederverwendet werden soll, sieht die Initialisierung so aus:

    import pgdb  # importiere das verwendete DB-API-2-Modul
    -from DBUtils.PersistentDB import PersistentDB
    +from dbutils.persistent_db import PersistentDB
     persist = PersistentDB(pgdb, 1000, database='meinedb')

    Nachdem Sie den Generator mit diesen Parametern eingerichtet haben, können Sie derartige Datenbankverbindungen von da an wie folgt anfordern:

    db = persist.connection()

    Sie können diese Verbindungen verwenden, als wären sie gewöhnliche DB-API-2-Datenbankverbindungen. Genauer genommen erhalten Sie die -"gehärtete" SteadyDB-Version der zugrunde liegenden DB-API-2-Verbindung.

    +"gehärtete" steady_db-Version der zugrunde liegenden DB-API-2-Verbindung.

    Wenn Sie eine solche persistente Verbindung mit db.close() schließen, wird dies stillschweigend ignoriert, denn sie würde beim nächsten Zugriff sowieso wieder geöffnet, und das wäre nicht im Sinne persistenter Verbindungen. @@ -347,11 +337,11 @@

    PersistentDB

    mod_wsgi hier Probleme bereitet, da es Daten, die mit threading.local gespeichert wurden, zwischen Requests löscht).

-
-

PooledDB

-

Wenn Sie das PooledDB-Modul einsetzen möchten, müssen Sie zuerst einen +

+

PooledDB (pooled_db)

+

Wenn Sie das pooled_db-Modul einsetzen möchten, müssen Sie zuerst einen Pool für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten, -indem Sie eine Instanz der Klasse PooledDB erzeugen, wobei Sie folgende +indem Sie eine Instanz der Klasse pooled_db erzeugen, wobei Sie folgende Parameter angeben müssen:

  • creator: entweder eine Funktion, die neue DB-API-2-Verbindungen @@ -406,14 +396,14 @@

    PooledDB

    und einen Pool von mindestens fünf Datenbankverbindungen zu Ihrer Datenbank meinedb verwenden möchten, dann sieht die Initialisierung so aus:

    import pgdb  # importiere das verwendete DB-API-2-Modul
    -from DBUtils.PooledDB import PooledDB
    +from dbutils.pooled_db import PooledDB
     pool = PooledDB(pgdb, 5, database='meinedb')

    Nachdem Sie den Pool für Datenbankverbindungen so eingerichtet haben, können Sie Verbindungen daraus wie folgt anfordern:

    db = pool.connection()

    Sie können diese Verbindungen verwenden, als wären sie gewöhnliche DB-API-2-Datenbankverbindungen. Genauer genommen erhalten Sie die -"gehärtete" SteadyDB-Version der zugrunde liegenden DB-API-2-Verbindung.

    +"gehärtete" steady_db-Version der zugrunde liegenden DB-API-2-Verbindung.

    Bitte beachten Sie, dass die Verbindung von anderen Threads mitgenutzt werden kann, wenn Sie den Parameter maxshared auf einen Wert größer als Null gesetzt haben, und der zugrunde liegende DB-API-2-Datenbankadapter dies erlaubt. @@ -444,48 +434,24 @@

    PooledDB

    ausgesetzt wird, und dass die Verbindung zurückgerollt wird, bevor sie wieder an den Verbindungspool zurückgegeben wird.

-
-

Benutzung in Webware for Python

-

Wenn Sie DBUtils verwenden, um von Servlets des Web-Frameworks Webware -for Python auf eine Datenbank zuzugreifen, dann müssen Sie sicherstellen, -dass die Generatoren zur Erzeugung von Datenbankverbindungen nur einmal -eingerichtet werden, wenn die Anwendung startet, und nicht jedes Mal, wenn -eine Servlet-Instanz erzeugt wird. Den hierfür nötigen Code können Sie -bei der Basis-Servlet-Klasse einfügen, dort wo das Modul oder die Klasse -initialisiert wird, oder Sie können die Funktion contextInitialize() -im __init__.py-Skript Ihres Anwendungskontextes verwenden.

-

Das zusammen mit DButils ausgelieferte Verzeichnis Examples enthält -einen Beispielkontext für Webware for Python, der eine kleine Demo-Datenbank -verwendet, um Teilnehmer an einer Seminarreihe zu verwalten (die Idee für -dieses Beispiel wurde dem Artikel "The Python DB-API" von Andrew Kuchling -entnommen).

-

Der Beispielkontext kann konfiguriert werden, indem entweder eine Konfig-Datei -Configs/Database.config angelegt wird, oder indem die Standard-Parameter -direkt im Beispielservlet Examples/DBUtilsExample.py geändert werden. -Auf diese Weise können Sie einen passenden Datenbanknutzer und sein Passwort -festlegen, sowie den zugrunde liegenden Datenbankadapter auswählen (das -klassische PyGreSQL-Modul oder irgendein DB-API-2-Modul). Wenn der Parameter -maxcached vorhanden ist, verwendet das Beispielservlet die -Pooled-Variante, andernfalls die Persistent-Variante.

-

Anmerkungen

-

Wenn Sie einen der bekannten "Object-Relational Mapper" SQLObject oder -SQLAlchemy verwenden, dann benötigen Sie DBUtils nicht, denn diese haben +

Wenn Sie einen der bekannten "Object-Relational Mapper" SQLObject oder +SQLAlchemy verwenden, dann benötigen Sie DBUtils nicht, denn diese haben ihre eigenen Mechanismen zum Pooling von Datenbankverbindungen eingebaut. Tatsächlich hat SQLObject 2 (SQL-API) das Pooling in eine separate Schicht ausgelagert, in der Code von DBUtils verwendet wird.

Wenn Sie eine Lösung verwenden wie den Apache-Webserver mit mod_python oder mod_wsgi, dann sollten Sie bedenken, dass Ihr Python-Code normalerweise im Kontext der Kindprozesse des Webservers läuft. Wenn Sie also das -PooledDB-Modul einsetzen, und mehrere dieser Kindprozesse laufen, dann +pooled_db-Modul einsetzen, und mehrere dieser Kindprozesse laufen, dann werden Sie ebensoviele Pools mit Datenbankverbindungen erhalten. Wenn diese Prozesse viele Threads laufen lassen, dann mag dies eine sinnvoller Ansatz sein, wenn aber diese Prozesse nicht mehr als einen Worker-Thread starten, wie im Fall des Multi-Processing Moduls "prefork" für den Apache-Webserver, dann sollten Sie auf eine Middleware für das Connection-Pooling zurückgreifen, -die Multi-Processing unterstützt, wie zum Beispiel pgpool oder pgbouncer +die Multi-Processing unterstützt, wie zum Beispiel pgpool oder pgbouncer für die PostgreSQL-Datenbank.

@@ -494,7 +460,7 @@

Zukunft

  • Alternativ zur Obergrenze in der Anzahl der Nutzung einer Datenbankverbindung könnte eine maximale Lebensdauer für die Verbindung implementiert werden.

  • -
  • Es könnten Module MonitorDB und MonitorPg hinzugefügt werden, die +

  • Es könnten Module monitor_db und monitor_pg hinzugefügt werden, die in einem separaten Thread ständig den "idle pool" und eventuell auch den "shared pool" bzw. die persistenten Verbindungen überwachen. Wenn eine unterbrochene Datenbankverbindung entdeckt wird, wird diese automatisch durch @@ -512,10 +478,8 @@

    Zukunft

Fehlermeldungen und Feedback

-

Bitte Senden Sie Fehlermeldungen, Patches und Feedback direkt an den -Autor (unter Verwendung der unten angegebenen E-Mail-Adresse).

-

Probleme, die Webware betreffen, können auch in der Webware for Python -mailing list diskutiert werden.

+

Fehlermeldungen, Patches und Feedback können Sie als Issues oder +Pull Requests auf der GitHub-Projektseite von DBUtils übermitteln.

Autoren

Autor
-

Christoph Zwerschke <cito@online.de>

+

Christoph Zwerschke

Beiträge

DBUtils benutzt Code, Anmerkungen und Vorschläge von @@ -550,7 +514,7 @@

Autoren