78 etypescls = cwvreg.CWRegistryStore.REGISTRY_FACTORY['etypes'] |
80 etypescls = cwvreg.CWRegistryStore.REGISTRY_FACTORY['etypes'] |
79 etypescls.etype_class = etypescls.orig_etype_class |
81 etypescls.etype_class = etypescls.orig_etype_class |
80 |
82 |
81 |
83 |
82 class ConnectionProperties(object): |
84 class ConnectionProperties(object): |
83 def __init__(self, cnxtype=None, lang=None, close=True, log=False): |
85 def __init__(self, cnxtype=None, close=True, log=False): |
84 self.cnxtype = cnxtype or 'pyro' |
86 if cnxtype is not None: |
85 self.lang = lang |
87 warn('[3.16] cnxtype argument is deprecated', DeprecationWarning, |
|
88 stacklevel=2) |
|
89 self.cnxtype = cnxtype |
86 self.log_queries = log |
90 self.log_queries = log |
87 self.close_on_del = close |
91 self.close_on_del = close |
88 |
92 |
89 |
93 |
90 def get_repository(method, database=None, config=None, vreg=None): |
94 def _get_inmemory_repo(config, vreg=None): |
91 """get a proxy object to the CubicWeb repository, using a specific RPC method. |
95 from cubicweb.server.repository import Repository |
92 |
96 from cubicweb.server.utils import TasksManager |
93 Only 'in-memory' and 'pyro' are supported for now. Either vreg or config |
97 return Repository(config, TasksManager(), vreg=vreg) |
94 argument should be given |
98 |
|
99 def get_repository(uri=None, config=None, vreg=None): |
|
100 """get a repository for the given URI or config/vregistry (in case we're |
|
101 loading the repository for a client, eg web server, configuration). |
|
102 |
|
103 The returned repository may be an in-memory repository or a proxy object |
|
104 using a specific RPC method, depending on the given URI (pyro or zmq). |
95 """ |
105 """ |
96 assert method in ('pyro', 'inmemory', 'zmq') |
106 if uri is None: |
97 assert vreg or config |
107 return _get_inmemory_repo(config, vreg) |
98 if vreg and not config: |
108 |
99 config = vreg.config |
109 protocol, hostport, appid = parse_repo_uri(uri) |
100 if method == 'inmemory': |
110 |
101 # get local access to the repository |
111 if protocol == 'inmemory': |
102 from cubicweb.server.repository import Repository |
112 # me may have been called with a dummy 'inmemory://' uri ... |
103 from cubicweb.server.utils import TasksManager |
113 return _get_inmemory_repo(config, vreg) |
104 return Repository(config, TasksManager(), vreg=vreg) |
114 |
105 elif method == 'zmq': |
115 if protocol == 'pyroloc': # direct connection to the instance |
|
116 from logilab.common.pyro_ext import get_proxy |
|
117 uri = uri.replace('pyroloc', 'PYRO') |
|
118 return get_proxy(uri) |
|
119 |
|
120 if protocol == 'pyro': # connection mediated through the pyro ns |
|
121 from logilab.common.pyro_ext import ns_get_proxy |
|
122 path = appid.strip('/') |
|
123 if not path: |
|
124 raise ConnectionError( |
|
125 "can't find instance name in %s (expected to be the path component)" |
|
126 % uri) |
|
127 if '.' in path: |
|
128 nsgroup, nsid = path.rsplit('.', 1) |
|
129 else: |
|
130 nsgroup = 'cubicweb' |
|
131 nsid = path |
|
132 return ns_get_proxy(nsid, defaultnsgroup=nsgroup, nshost=hostport) |
|
133 |
|
134 if protocol.startswith('zmqpickle-'): |
106 from cubicweb.zmqclient import ZMQRepositoryClient |
135 from cubicweb.zmqclient import ZMQRepositoryClient |
107 return ZMQRepositoryClient(database) |
136 return ZMQRepositoryClient(uri) |
108 else: # method == 'pyro' |
137 else: |
109 # resolve the Pyro object |
138 raise ConnectionError('unknown protocol: `%s`' % protocol) |
110 from logilab.common.pyro_ext import ns_get_proxy, get_proxy |
139 |
111 pyroid = database or config['pyro-instance-id'] or config.appid |
140 |
112 try: |
141 def _repo_connect(repo, login, **kwargs): |
113 if config['pyro-ns-host'] == 'NO_PYRONS': |
142 """Constructor to create a new connection to the given CubicWeb repository. |
114 return get_proxy(pyroid) |
|
115 else: |
|
116 return ns_get_proxy(pyroid, defaultnsgroup=config['pyro-ns-group'], |
|
117 nshost=config['pyro-ns-host']) |
|
118 except Exception, ex: |
|
119 raise ConnectionError(str(ex)) |
|
120 |
|
121 def repo_connect(repo, login, **kwargs): |
|
122 """Constructor to create a new connection to the CubicWeb repository. |
|
123 |
143 |
124 Returns a Connection instance. |
144 Returns a Connection instance. |
|
145 |
|
146 Raises AuthenticationError if authentication failed |
125 """ |
147 """ |
126 if not 'cnxprops' in kwargs: |
|
127 kwargs['cnxprops'] = ConnectionProperties('inmemory') |
|
128 cnxid = repo.connect(unicode(login), **kwargs) |
148 cnxid = repo.connect(unicode(login), **kwargs) |
129 cnx = Connection(repo, cnxid, kwargs['cnxprops']) |
149 cnx = Connection(repo, cnxid, kwargs.get('cnxprops')) |
130 if kwargs['cnxprops'].cnxtype == 'inmemory': |
150 if cnx.is_repo_in_memory: |
131 cnx.vreg = repo.vreg |
151 cnx.vreg = repo.vreg |
132 return cnx |
152 return cnx |
133 |
153 |
134 def connect(database=None, login=None, host=None, group=None, |
154 def connect(database, login=None, |
135 cnxprops=None, setvreg=True, mulcnx=True, initlog=True, **kwargs): |
155 cnxprops=None, setvreg=True, mulcnx=True, initlog=True, **kwargs): |
136 """Constructor for creating a connection to the CubicWeb repository. |
156 """Constructor for creating a connection to the CubicWeb repository. |
137 Returns a :class:`Connection` object. |
157 Returns a :class:`Connection` object. |
138 |
158 |
139 Typical usage:: |
159 Typical usage:: |
140 |
160 |
141 cnx = connect('myinstance', login='me', password='toto') |
161 cnx = connect('myinstance', login='me', password='toto') |
142 |
162 |
143 Arguments: |
163 `database` may be: |
144 |
164 |
145 :database: |
165 * a simple instance id for in-memory connection |
146 the instance's pyro identifier. |
166 |
|
167 * an uri like scheme://host:port/instanceid where scheme may be one of |
|
168 'pyro', 'inmemory' or 'zmqpickle' |
|
169 |
|
170 * if scheme is 'pyro', <host:port> determine the name server address. If |
|
171 not specified (e.g. 'pyro:///instanceid'), it will be detected through a |
|
172 broadcast query. The instance id is the name of the instance in the name |
|
173 server and may be prefixed by a group (e.g. |
|
174 'pyro:///:cubicweb.instanceid') |
|
175 |
|
176 * if scheme is handled by ZMQ (eg 'tcp'), you should not specify an |
|
177 instance id |
|
178 |
|
179 Other arguments: |
147 |
180 |
148 :login: |
181 :login: |
149 the user login to use to authenticate. |
182 the user login to use to authenticate. |
150 |
183 |
151 :host: |
|
152 - pyro: nameserver host. Will be detected using broadcast query if unspecified |
|
153 - zmq: repository host socket address |
|
154 |
|
155 :group: |
|
156 the instance's pyro nameserver group. You don't have to specify it unless |
|
157 tweaked in instance's configuration. |
|
158 |
|
159 :cnxprops: |
184 :cnxprops: |
160 an optional :class:`ConnectionProperties` instance, allowing to specify |
185 a :class:`ConnectionProperties` instance, allowing to specify |
161 the connection method (eg in memory or pyro). A Pyro connection will be |
186 the connection method (eg in memory or pyro). A Pyro connection will be |
162 established if you don't specify that argument. |
187 established if you don't specify that argument. |
163 |
188 |
164 :setvreg: |
189 :setvreg: |
165 flag telling if a registry should be initialized for the connection. |
190 flag telling if a registry should be initialized for the connection. |
177 |
202 |
178 :kwargs: |
203 :kwargs: |
179 there goes authentication tokens. You usually have to specify a password |
204 there goes authentication tokens. You usually have to specify a password |
180 for the given user, using a named 'password' argument. |
205 for the given user, using a named 'password' argument. |
181 """ |
206 """ |
182 cnxprops = cnxprops or ConnectionProperties() |
207 if urlparse(database).scheme is None: |
183 method = cnxprops.cnxtype |
208 warn('[3.16] give an qualified URI as database instead of using ' |
184 if method == 'pyro': |
209 'host/cnxprops to specify the connection method', |
|
210 DeprecationWarning, stacklevel=2) |
|
211 if cnxprops.cnxtype == 'zmq': |
|
212 database = kwargs.pop('host') |
|
213 elif cnxprops.cnxtype == 'inmemory': |
|
214 database = 'inmemory://' + database |
|
215 else: |
|
216 database = 'pyro://%s/%s.%s' % (kwargs.pop('host', ''), |
|
217 kwargs.pop('group', 'cubicweb'), |
|
218 database) |
|
219 puri = urlparse(database) |
|
220 method = puri.scheme.lower() |
|
221 if method == 'inmemory': |
|
222 config = cwconfig.instance_configuration(puri.path) |
|
223 else: |
185 config = cwconfig.CubicWebNoAppConfiguration() |
224 config = cwconfig.CubicWebNoAppConfiguration() |
186 if host: |
225 repo = get_repository(database, config=config) |
187 config.global_set_option('pyro-ns-host', host) |
|
188 if group: |
|
189 config.global_set_option('pyro-ns-group', group) |
|
190 elif method == 'zmq': |
|
191 config = cwconfig.CubicWebNoAppConfiguration() |
|
192 else: |
|
193 assert database |
|
194 config = cwconfig.instance_configuration(database) |
|
195 repo = get_repository(method, database, config=config) |
|
196 if method == 'inmemory': |
226 if method == 'inmemory': |
197 vreg = repo.vreg |
227 vreg = repo.vreg |
198 elif setvreg: |
228 elif setvreg: |
199 if mulcnx: |
229 if mulcnx: |
200 multiple_connections_fix() |
230 multiple_connections_fix() |
217 vreg = config |
247 vreg = config |
218 config = None |
248 config = None |
219 else: |
249 else: |
220 vreg = None |
250 vreg = None |
221 # get local access to the repository |
251 # get local access to the repository |
222 return get_repository('inmemory', config=config, vreg=vreg) |
252 return get_repository('inmemory://', config=config, vreg=vreg) |
223 |
|
224 def in_memory_cnx(repo, login, **kwargs): |
|
225 """Establish a In memory connection to a <repo> for the user with <login> |
|
226 |
|
227 additionel credential might be required""" |
|
228 cnxprops = ConnectionProperties('inmemory') |
|
229 return repo_connect(repo, login, cnxprops=cnxprops, **kwargs) |
|
230 |
253 |
231 def in_memory_repo_cnx(config, login, **kwargs): |
254 def in_memory_repo_cnx(config, login, **kwargs): |
232 """useful method for testing and scripting to get a dbapi.Connection |
255 """useful method for testing and scripting to get a dbapi.Connection |
233 object connected to an in-memory repository instance |
256 object connected to an in-memory repository instance |
234 """ |
257 """ |
235 # connection to the CubicWeb repository |
258 # connection to the CubicWeb repository |
236 repo = in_memory_repo(config) |
259 repo = in_memory_repo(config) |
237 return repo, in_memory_cnx(repo, login, **kwargs) |
260 return repo, _repo_connect(repo, login, **kwargs) |
238 |
261 |
239 |
262 # XXX web only method, move to webconfig? |
240 def anonymous_session(vreg): |
263 def anonymous_session(vreg): |
241 """return a new anonymous session |
264 """return a new anonymous session |
242 |
265 |
243 raises an AuthenticationError if anonymous usage is not allowed |
266 raises an AuthenticationError if anonymous usage is not allowed |
244 """ |
267 """ |
245 anoninfo = vreg.config.anonymous_user() |
268 anoninfo = vreg.config.anonymous_user() |
246 if anoninfo is None: # no anonymous user |
269 if anoninfo is None: # no anonymous user |
247 raise AuthenticationError('anonymous access is not authorized') |
270 raise AuthenticationError('anonymous access is not authorized') |
248 anon_login, anon_password = anoninfo |
271 anon_login, anon_password = anoninfo |
249 cnxprops = ConnectionProperties(vreg.config.repo_method) |
|
250 # use vreg's repository cache |
272 # use vreg's repository cache |
251 repo = vreg.config.repository(vreg) |
273 repo = vreg.config.repository(vreg) |
252 anon_cnx = repo_connect(repo, anon_login, |
274 anon_cnx = _repo_connect(repo, anon_login, password=anon_password) |
253 cnxprops=cnxprops, password=anon_password) |
|
254 anon_cnx.vreg = vreg |
275 anon_cnx.vreg = vreg |
255 return DBAPISession(anon_cnx, anon_login) |
276 return DBAPISession(anon_cnx, anon_login) |
256 |
277 |
257 |
278 |
258 class _NeedAuthAccessMock(object): |
279 class _NeedAuthAccessMock(object): |
280 return not self.cnx or self.cnx.anonymous_connection |
301 return not self.cnx or self.cnx.anonymous_connection |
281 |
302 |
282 def __repr__(self): |
303 def __repr__(self): |
283 return '<DBAPISession %r>' % self.sessionid |
304 return '<DBAPISession %r>' % self.sessionid |
284 |
305 |
|
306 |
285 class DBAPIRequest(RequestSessionBase): |
307 class DBAPIRequest(RequestSessionBase): |
|
308 #: Request language identifier eg: 'en' |
|
309 lang = None |
286 |
310 |
287 def __init__(self, vreg, session=None): |
311 def __init__(self, vreg, session=None): |
288 super(DBAPIRequest, self).__init__(vreg) |
312 super(DBAPIRequest, self).__init__(vreg) |
289 #: 'language' => translation_function() mapping |
313 #: 'language' => translation_function() mapping |
290 try: |
314 try: |
291 # no vreg or config which doesn't handle translations |
315 # no vreg or config which doesn't handle translations |
292 self.translations = vreg.config.translations |
316 self.translations = vreg.config.translations |
293 except AttributeError: |
317 except AttributeError: |
294 self.translations = {} |
318 self.translations = {} |
295 #: Request language identifier eg: 'en' |
|
296 self.lang = None |
|
297 self.set_default_language(vreg) |
|
298 #: cache entities built during the request |
319 #: cache entities built during the request |
299 self._eid_cache = {} |
320 self._eid_cache = {} |
300 if session is not None: |
321 if session is not None: |
301 self.set_session(session) |
322 self.set_session(session) |
302 else: |
323 else: |
303 # these args are initialized after a connection is |
324 # these args are initialized after a connection is |
304 # established |
325 # established |
305 self.session = None |
326 self.session = None |
306 self.cnx = self.user = _NeedAuthAccessMock() |
327 self.cnx = self.user = _NeedAuthAccessMock() |
|
328 self.set_default_language(vreg) |
307 |
329 |
308 def from_controller(self): |
330 def from_controller(self): |
309 return 'view' |
331 return 'view' |
310 |
332 |
311 def get_option_value(self, option, foreid=None): |
333 def get_option_value(self, option, foreid=None): |
331 """ |
353 """ |
332 raise AuthenticationError() |
354 raise AuthenticationError() |
333 |
355 |
334 def set_default_language(self, vreg): |
356 def set_default_language(self, vreg): |
335 try: |
357 try: |
336 self.lang = vreg.property_value('ui.language') |
358 lang = vreg.property_value('ui.language') |
337 except Exception: # property may not be registered |
359 except Exception: # property may not be registered |
338 self.lang = 'en' |
360 lang = 'en' |
339 # use req.__ to translate a message without registering it to the catalog |
|
340 try: |
361 try: |
341 gettext, pgettext = self.translations[self.lang] |
362 self.set_language(lang) |
342 self._ = self.__ = gettext |
|
343 self.pgettext = pgettext |
|
344 except KeyError: |
363 except KeyError: |
345 # this occurs usually during test execution |
364 # this occurs usually during test execution |
346 self._ = self.__ = unicode |
365 self._ = self.__ = unicode |
347 self.pgettext = lambda x, y: unicode(y) |
366 self.pgettext = lambda x, y: unicode(y) |
348 self.debug('request default language: %s', self.lang) |
|
349 |
367 |
350 # server-side service call ################################################# |
368 # server-side service call ################################################# |
351 |
369 |
352 def call_service(self, regid, async=False, **kwargs): |
370 def call_service(self, regid, async=False, **kwargs): |
353 return self.cnx.call_service(regid, async, **kwargs) |
371 return self.cnx.call_service(regid, async, **kwargs) |
538 |
556 |
539 def __init__(self, repo, cnxid, cnxprops=None): |
557 def __init__(self, repo, cnxid, cnxprops=None): |
540 self._repo = repo |
558 self._repo = repo |
541 self.sessionid = cnxid |
559 self.sessionid = cnxid |
542 self._close_on_del = getattr(cnxprops, 'close_on_del', True) |
560 self._close_on_del = getattr(cnxprops, 'close_on_del', True) |
543 self._cnxtype = getattr(cnxprops, 'cnxtype', 'pyro') |
|
544 self._web_request = False |
561 self._web_request = False |
545 if cnxprops and cnxprops.log_queries: |
562 if cnxprops and cnxprops.log_queries: |
546 self.executed_queries = [] |
563 self.executed_queries = [] |
547 self.cursor_class = LogCursor |
564 self.cursor_class = LogCursor |
548 if self._cnxtype == 'pyro': |
565 |
549 # check client/server compat |
566 @property |
550 if self._repo.get_versions()['cubicweb'] < (3, 8, 6): |
567 def is_repo_in_memory(self): |
551 self._txid = lambda cursor=None: {} |
568 """return True if this is a local, aka in-memory, connection to the |
|
569 repository |
|
570 """ |
|
571 try: |
|
572 from cubicweb.server.repository import Repository |
|
573 except ImportError: |
|
574 # code not available, no way |
|
575 return False |
|
576 return isinstance(self._repo, Repository) |
552 |
577 |
553 def __repr__(self): |
578 def __repr__(self): |
554 if self.anonymous_connection: |
579 if self.anonymous_connection: |
555 return '<Connection %s (anonymous)>' % self.sessionid |
580 return '<Connection %s (anonymous)>' % self.sessionid |
556 return '<Connection %s>' % self.sessionid |
581 return '<Connection %s>' % self.sessionid |