dbapi.py
changeset 2675 f84ba1a66abb
parent 2665 0c6281487f90
child 2770 356e9d7c356d
child 3110 757d36162235
equal deleted inserted replaced
2674:ff6114c2c416 2675:f84ba1a66abb
    14 from logging import getLogger
    14 from logging import getLogger
    15 from time import time, clock
    15 from time import time, clock
    16 from itertools import count
    16 from itertools import count
    17 
    17 
    18 from logilab.common.logging_ext import set_log_methods
    18 from logilab.common.logging_ext import set_log_methods
       
    19 from logilab.common.decorators import monkeypatch
       
    20 from logilab.common.deprecation import deprecated
       
    21 
    19 from cubicweb import ETYPE_NAME_MAP, ConnectionError, RequestSessionMixIn
    22 from cubicweb import ETYPE_NAME_MAP, ConnectionError, RequestSessionMixIn
    20 from cubicweb.cwvreg import CubicWebRegistry, MulCnxCubicWebRegistry
    23 from cubicweb import cwvreg, cwconfig
    21 from cubicweb.cwconfig import CubicWebNoAppConfiguration
       
    22 
    24 
    23 _MARKER = object()
    25 _MARKER = object()
    24 
    26 
    25 def _fake_property_value(self, name):
    27 def _fake_property_value(self, name):
    26     try:
    28     try:
    27         return super(dbapi.DBAPIRequest, self).property_value(name)
    29         return super(dbapi.DBAPIRequest, self).property_value(name)
    28     except KeyError:
    30     except KeyError:
    29         return ''
    31         return ''
       
    32 
       
    33 def _fix_cls_attrs(reg, appobject):
       
    34     appobject.vreg = reg.vreg
       
    35     appobject.schema = reg.schema
       
    36     appobject.config = reg.config
       
    37 
       
    38 def multiple_connections_fix():
       
    39     """some monkey patching necessary when an application has to deal with
       
    40     several connections to different repositories. It tries to hide buggy class
       
    41     attributes since classes are not designed to be shared among multiple
       
    42     registries.
       
    43     """
       
    44     defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None]
       
    45     orig_select_best = defaultcls.orig_select_best = defaultcls.select_best
       
    46     @monkeypatch(defaultcls)
       
    47     def select_best(self, appobjects, *args, **kwargs):
       
    48         """return an instance of the most specific object according
       
    49         to parameters
       
    50 
       
    51         raise NoSelectableObject if no object apply
       
    52         """
       
    53         for appobjectcls in appobjects:
       
    54             _fix_cls_attrs(self, appobjectcls)
       
    55         selected = orig_select_best(self, appobjects, *args, **kwargs)
       
    56         # redo the same thing on the instance so it won't use equivalent class
       
    57         # attributes (which may change)
       
    58         _fix_cls_attrs(self, selected)
       
    59         return selected
       
    60 
       
    61     etypescls = cwvreg.VRegistry.REGISTRY_FACTORY['etypes']
       
    62     orig_etype_class = etypescls.orig_etype_class = etypescls.etype_class
       
    63     @monkeypatch(defaultcls)
       
    64     def etype_class(self, etype):
       
    65         """return an entity class for the given entity type.
       
    66         Try to find out a specific class for this kind of entity or
       
    67         default to a dump of the class registered for 'Any'
       
    68         """
       
    69         usercls = orig_etype_class(self, etype)
       
    70         if etype == 'Any':
       
    71             return usercls
       
    72         usercls.e_schema = self.schema.eschema(etype)
       
    73         return usercls
       
    74 
       
    75 def multiple_connections_unfix():
       
    76     defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None]
       
    77     defaultcls.select_best = defaultcls.orig_select_best
       
    78     etypescls = cwvreg.VRegistry.REGISTRY_FACTORY['etypes']
       
    79     etypescls.etype_class = etypescls.orig_etype_class
    30 
    80 
    31 class ConnectionProperties(object):
    81 class ConnectionProperties(object):
    32     def __init__(self, cnxtype=None, lang=None, close=True, log=False):
    82     def __init__(self, cnxtype=None, lang=None, close=True, log=False):
    33         self.cnxtype = cnxtype or 'pyro'
    83         self.cnxtype = cnxtype or 'pyro'
    34         self.lang = lang
    84         self.lang = lang
    49     if method == 'inmemory':
    99     if method == 'inmemory':
    50         # get local access to the repository
   100         # get local access to the repository
    51         from cubicweb.server.repository import Repository
   101         from cubicweb.server.repository import Repository
    52         return Repository(config, vreg=vreg)
   102         return Repository(config, vreg=vreg)
    53     else: # method == 'pyro'
   103     else: # method == 'pyro'
    54         from Pyro import core, naming
       
    55         from Pyro.errors import NamingError, ProtocolError
       
    56         core.initClient(banner=0)
       
    57         nsid = ':%s.%s' % (config['pyro-ns-group'], database)
       
    58         locator = naming.NameServerLocator()
       
    59         # resolve the Pyro object
   104         # resolve the Pyro object
       
   105         from logilab.common.pyro_ext import ns_get_proxy
    60         try:
   106         try:
    61             nshost, nsport = config['pyro-ns-host'], config['pyro-ns-port']
   107             return ns_get_proxy(database,
    62             uri = locator.getNS(nshost, nsport).resolve(nsid)
   108                                 defaultnsgroup=config['pyro-ns-group'],
    63         except ProtocolError:
   109                                 nshost=config['pyro-ns-host'])
    64             raise ConnectionError('Could not connect to the Pyro name server '
   110         except Exception, ex:
    65                                   '(host: %s:%i)' % (nshost, nsport))
   111             raise ConnectionError(str(ex))
    66         except NamingError:
       
    67             raise ConnectionError('Could not get repository for %s '
       
    68                                   '(not registered in Pyro), '
       
    69                                   'you may have to restart your server-side '
       
    70                                   'instance' % nsid)
       
    71         return core.getProxyForURI(uri)
       
    72 
   112 
    73 def repo_connect(repo, login, password, cnxprops=None):
   113 def repo_connect(repo, login, password, cnxprops=None):
    74     """Constructor to create a new connection to the CubicWeb repository.
   114     """Constructor to create a new connection to the CubicWeb repository.
    75 
   115 
    76     Returns a Connection instance.
   116     Returns a Connection instance.
    80     cnx = Connection(repo, cnxid, cnxprops)
   120     cnx = Connection(repo, cnxid, cnxprops)
    81     if cnxprops.cnxtype == 'inmemory':
   121     if cnxprops.cnxtype == 'inmemory':
    82         cnx.vreg = repo.vreg
   122         cnx.vreg = repo.vreg
    83     return cnx
   123     return cnx
    84 
   124 
    85 def connect(database=None, login=None, password=None, host=None,
   125 def connect(database=None, login=None, password=None, host=None, group=None,
    86             group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True,
   126             cnxprops=None, setvreg=True, mulcnx=True, initlog=True):
    87             initlog=True):
       
    88     """Constructor for creating a connection to the CubicWeb repository.
   127     """Constructor for creating a connection to the CubicWeb repository.
    89     Returns a Connection object.
   128     Returns a Connection object.
    90 
   129 
    91     When method is 'pyro' and setvreg is True, use a special registry class
   130     When method is 'pyro', setvreg is True, try to deal with connections to
    92     (MulCnxCubicWebRegistry) made to deal with connections to differents instances
   131     differents instances in the same process unless specified otherwise by
    93     in the same process unless specified otherwise by setting the mulcnx to
   132     setting the mulcnx to False.
    94     False.
       
    95     """
   133     """
    96     config = CubicWebNoAppConfiguration()
   134     config = cwconfig.CubicWebNoAppConfiguration()
    97     if host:
   135     if host:
    98         config.global_set_option('pyro-ns-host', host)
   136         config.global_set_option('pyro-ns-host', host)
    99     if port:
       
   100         config.global_set_option('pyro-ns-port', port)
       
   101     if group:
   137     if group:
   102         config.global_set_option('pyro-ns-group', group)
   138         config.global_set_option('pyro-ns-group', group)
   103     cnxprops = cnxprops or ConnectionProperties()
   139     cnxprops = cnxprops or ConnectionProperties()
   104     method = cnxprops.cnxtype
   140     method = cnxprops.cnxtype
   105     repo = get_repository(method, database, config=config)
   141     repo = get_repository(method, database, config=config)
   106     if method == 'inmemory':
   142     if method == 'inmemory':
   107         vreg = repo.vreg
   143         vreg = repo.vreg
   108     elif setvreg:
   144     elif setvreg:
   109         if mulcnx:
   145         if mulcnx:
   110             vreg = MulCnxCubicWebRegistry(config, initlog=initlog)
   146             multiple_connections_fix()
   111         else:
   147         vreg = cwvreg.CubicWebVRegistry(config, initlog=initlog)
   112             vreg = CubicWebRegistry(config, initlog=initlog)
       
   113         schema = repo.get_schema()
   148         schema = repo.get_schema()
   114         for oldetype, newetype in ETYPE_NAME_MAP.items():
   149         for oldetype, newetype in ETYPE_NAME_MAP.items():
   115             if oldetype in schema:
   150             if oldetype in schema:
   116                 print 'aliasing', newetype, 'to', oldetype
   151                 print 'aliasing', newetype, 'to', oldetype
   117                 schema._entities[newetype] = schema._entities[oldetype]
   152                 schema._entities[newetype] = schema._entities[oldetype]
   124 
   159 
   125 def in_memory_cnx(config, login, password):
   160 def in_memory_cnx(config, login, password):
   126     """usefull method for testing and scripting to get a dbapi.Connection
   161     """usefull method for testing and scripting to get a dbapi.Connection
   127     object connected to an in-memory repository instance
   162     object connected to an in-memory repository instance
   128     """
   163     """
   129     if isinstance(config, CubicWebRegistry):
   164     if isinstance(config, cwvreg.CubicWebVRegistry):
   130         vreg = config
   165         vreg = config
   131         config = None
   166         config = None
   132     else:
   167     else:
   133         vreg = None
   168         vreg = None
   134     # get local access to the repository
   169     # get local access to the repository
   399         """
   434         """
   400         if self._closed is not None:
   435         if self._closed is not None:
   401             raise ProgrammingError('Closed connection')
   436             raise ProgrammingError('Closed connection')
   402         return self._repo.get_schema()
   437         return self._repo.get_schema()
   403 
   438 
   404     def load_vobjects(self, cubes=_MARKER, subpath=None, expand=True,
   439     def load_appobjects(self, cubes=_MARKER, subpath=None, expand=True,
   405                       force_reload=None):
   440                       force_reload=None):
   406         config = self.vreg.config
   441         config = self.vreg.config
   407         if cubes is _MARKER:
   442         if cubes is _MARKER:
   408             cubes = self._repo.get_cubes()
   443             cubes = self._repo.get_cubes()
   409         elif cubes is None:
   444         elif cubes is None:
   432             hm.register_system_hooks(config)
   467             hm.register_system_hooks(config)
   433             # instance specific hooks
   468             # instance specific hooks
   434             if self._repo.config.instance_hooks:
   469             if self._repo.config.instance_hooks:
   435                 hm.register_hooks(config.load_hooks(self.vreg))
   470                 hm.register_hooks(config.load_hooks(self.vreg))
   436 
   471 
       
   472     load_vobjects = deprecated()(load_appobjects)
       
   473 
   437     def use_web_compatible_requests(self, baseurl, sitetitle=None):
   474     def use_web_compatible_requests(self, baseurl, sitetitle=None):
   438         """monkey patch DBAPIRequest to fake a cw.web.request, so you should
   475         """monkey patch DBAPIRequest to fake a cw.web.request, so you should
   439         able to call html views using rset from a simple dbapi connection.
   476         able to call html views using rset from a simple dbapi connection.
   440 
   477 
   441         You should call `load_vobjects` at some point to register those views.
   478         You should call `load_appobjects` at some point to register those views.
   442         """
   479         """
   443         from cubicweb.web.request import CubicWebRequestBase as cwrb
   480         from cubicweb.web.request import CubicWebRequestBase as cwrb
   444         DBAPIRequest.build_ajax_replace_url = cwrb.build_ajax_replace_url.im_func
   481         DBAPIRequest.build_ajax_replace_url = cwrb.build_ajax_replace_url.im_func
   445         DBAPIRequest.list_form_param = cwrb.list_form_param.im_func
   482         DBAPIRequest.list_form_param = cwrb.list_form_param.im_func
   446         DBAPIRequest.property_value = _fake_property_value
   483         DBAPIRequest.property_value = _fake_property_value
   474         eid, login, groups, properties = self._repo.user_info(self.sessionid,
   511         eid, login, groups, properties = self._repo.user_info(self.sessionid,
   475                                                               props)
   512                                                               props)
   476         if req is None:
   513         if req is None:
   477             req = self.request()
   514             req = self.request()
   478         rset = req.eid_rset(eid, 'CWUser')
   515         rset = req.eid_rset(eid, 'CWUser')
   479         user = self.vreg.etype_class('CWUser')(req, rset, row=0, groups=groups,
   516         user = self.vreg['etypes'].etype_class('CWUser')(req, rset, row=0,
   480                                                properties=properties)
   517                                                          groups=groups,
       
   518                                                          properties=properties)
   481         user['login'] = login # cache login
   519         user['login'] = login # cache login
   482         return user
   520         return user
   483 
   521 
   484     def __del__(self):
   522     def __del__(self):
   485         """close the remote connection if necessary"""
   523         """close the remote connection if necessary"""