79 etypescls.etype_class = etypescls.orig_etype_class |
80 etypescls.etype_class = etypescls.orig_etype_class |
80 |
81 |
81 |
82 |
82 class ConnectionProperties(object): |
83 class ConnectionProperties(object): |
83 def __init__(self, cnxtype=None, close=True, log=False): |
84 def __init__(self, cnxtype=None, close=True, log=False): |
84 self.cnxtype = cnxtype or 'pyro' |
85 if cnxtype is not None: |
|
86 warn('[3.16] cnxtype argument is deprecated', DeprecationWarning, |
|
87 stacklevel=2) |
|
88 self.cnxtype = cnxtype |
85 self.log_queries = log |
89 self.log_queries = log |
86 self.close_on_del = close |
90 self.close_on_del = close |
87 |
91 |
88 |
92 |
89 def get_repository(method, database=None, config=None, vreg=None): |
93 def get_repository(uri=None, config=None, vreg=None): |
90 """get a proxy object to the CubicWeb repository, using a specific RPC method. |
94 """get a repository for the given URI or config/vregistry (in case we're |
91 |
95 loading the repository for a client, eg web server, configuration). |
92 Only 'in-memory' and 'pyro' are supported for now. Either vreg or config |
96 |
93 argument should be given |
97 The returned repository may be an in-memory repository or a proxy object |
|
98 using a specific RPC method, depending on the given URI (pyro or zmq). |
94 """ |
99 """ |
95 assert method in ('pyro', 'inmemory', 'zmq') |
100 if uri is None: |
96 assert vreg or config |
101 uri = config['repository-uri'] or config.appid |
97 if vreg and not config: |
102 puri = urlparse(uri) |
98 config = vreg.config |
103 method = puri.scheme.lower() or 'inmemory' |
99 if method == 'inmemory': |
104 if method == 'inmemory': |
100 # get local access to the repository |
105 # get local access to the repository |
101 from cubicweb.server.repository import Repository |
106 from cubicweb.server.repository import Repository |
102 from cubicweb.server.utils import TasksManager |
107 from cubicweb.server.utils import TasksManager |
103 return Repository(config, TasksManager(), vreg=vreg) |
108 return Repository(config, TasksManager(), vreg=vreg) |
104 elif method == 'zmq': |
109 elif method in ('pyro', 'pyroloc'): |
105 from cubicweb.zmqclient import ZMQRepositoryClient |
|
106 return ZMQRepositoryClient(database) |
|
107 else: # method == 'pyro' |
|
108 # resolve the Pyro object |
110 # resolve the Pyro object |
109 from logilab.common.pyro_ext import ns_get_proxy, get_proxy |
111 from logilab.common.pyro_ext import ns_get_proxy, get_proxy |
110 pyroid = database or config['pyro-instance-id'] or config.appid |
|
111 try: |
112 try: |
112 if config['pyro-ns-host'] == 'NO_PYRONS': |
113 if puri.scheme == 'pyroloc': |
113 return get_proxy(pyroid) |
114 return get_proxy(uri) |
|
115 path = puri.path.rstrip('/') |
|
116 if not path: |
|
117 raise ConnectionError( |
|
118 "can't find instance name in %s (expected to be the path component)" |
|
119 % uri) |
|
120 if '.' in path: |
|
121 nsgroup, nsid = path.rsplit('.', 1) |
114 else: |
122 else: |
115 return ns_get_proxy(pyroid, defaultnsgroup=config['pyro-ns-group'], |
123 nsgroup = 'cubicweb' |
116 nshost=config['pyro-ns-host']) |
124 nsid = path |
|
125 return ns_get_proxy(nsid, defaultnsgroup=nsgroup, nshost=puri.netloc) |
117 except Exception, ex: |
126 except Exception, ex: |
118 raise ConnectionError(str(ex)) |
127 raise ConnectionError(str(ex)) |
|
128 elif method == 'tcp': # use zmq (see zmq documentation) |
|
129 from cubicweb.zmqclient import ZMQRepositoryClient |
|
130 return ZMQRepositoryClient(uri) |
|
131 else: |
|
132 raise ConnectionError('unknown protocol: `%s`' % method) |
|
133 |
119 |
134 |
120 def repo_connect(repo, login, **kwargs): |
135 def repo_connect(repo, login, **kwargs): |
121 """Constructor to create a new connection to the CubicWeb repository. |
136 """Constructor to create a new connection to the given CubicWeb repository. |
122 |
137 |
123 Returns a Connection instance. |
138 Returns a Connection instance. |
|
139 |
|
140 Raises AuthenticationError if authentication failed |
124 """ |
141 """ |
125 if not 'cnxprops' in kwargs: |
|
126 kwargs['cnxprops'] = ConnectionProperties('inmemory') |
|
127 cnxid = repo.connect(unicode(login), **kwargs) |
142 cnxid = repo.connect(unicode(login), **kwargs) |
128 cnx = Connection(repo, cnxid, kwargs['cnxprops']) |
143 cnx = Connection(repo, cnxid, kwargs.get('cnxprops')) |
129 if kwargs['cnxprops'].cnxtype == 'inmemory': |
144 if cnx.is_repo_in_memory: |
130 cnx.vreg = repo.vreg |
145 cnx.vreg = repo.vreg |
131 return cnx |
146 return cnx |
132 |
147 |
133 def connect(database=None, login=None, host=None, group=None, |
148 def connect(database, login=None, |
134 cnxprops=None, setvreg=True, mulcnx=True, initlog=True, **kwargs): |
149 cnxprops=None, setvreg=True, mulcnx=True, initlog=True, **kwargs): |
135 """Constructor for creating a connection to the CubicWeb repository. |
150 """Constructor for creating a connection to the CubicWeb repository. |
136 Returns a :class:`Connection` object. |
151 Returns a :class:`Connection` object. |
137 |
152 |
138 Typical usage:: |
153 Typical usage:: |
139 |
154 |
140 cnx = connect('myinstance', login='me', password='toto') |
155 cnx = connect('myinstance', login='me', password='toto') |
141 |
156 |
142 Arguments: |
157 `database` may be: |
143 |
158 |
144 :database: |
159 * a simple instance id for in-memory connection |
145 the instance's pyro identifier. |
160 |
|
161 * an uri like scheme://host:port/instanceid where scheme may be one of |
|
162 'pyro', 'pyroloc', 'inmemory' or a schema supported by ZMQ |
|
163 |
|
164 * if scheme is 'pyro', <host:port> determine the name server address. If |
|
165 not specified (e.g. 'pyro:///instanceid'), it will be detected through a |
|
166 broadcast query. The instance id is the name of the instance in the name |
|
167 server and may be prefixed by a group (e.g. |
|
168 'pyro:///:cubicweb.instanceid') |
|
169 |
|
170 * if scheme is 'pyroloc', it's expected to be a bare pyro location URI |
|
171 |
|
172 * if scheme is handled by ZMQ (eg 'tcp'), you should not specify an |
|
173 instance id |
|
174 |
|
175 Other arguments: |
146 |
176 |
147 :login: |
177 :login: |
148 the user login to use to authenticate. |
178 the user login to use to authenticate. |
149 |
179 |
150 :host: |
|
151 - pyro: nameserver host. Will be detected using broadcast query if unspecified |
|
152 - zmq: repository host socket address |
|
153 |
|
154 :group: |
|
155 the instance's pyro nameserver group. You don't have to specify it unless |
|
156 tweaked in instance's configuration. |
|
157 |
|
158 :cnxprops: |
180 :cnxprops: |
159 an optional :class:`ConnectionProperties` instance, allowing to specify |
181 a :class:`ConnectionProperties` instance, allowing to specify |
160 the connection method (eg in memory or pyro). A Pyro connection will be |
182 the connection method (eg in memory or pyro). A Pyro connection will be |
161 established if you don't specify that argument. |
183 established if you don't specify that argument. |
162 |
184 |
163 :setvreg: |
185 :setvreg: |
164 flag telling if a registry should be initialized for the connection. |
186 flag telling if a registry should be initialized for the connection. |
176 |
198 |
177 :kwargs: |
199 :kwargs: |
178 there goes authentication tokens. You usually have to specify a password |
200 there goes authentication tokens. You usually have to specify a password |
179 for the given user, using a named 'password' argument. |
201 for the given user, using a named 'password' argument. |
180 """ |
202 """ |
181 cnxprops = cnxprops or ConnectionProperties() |
203 if urlparse(database).scheme is None: |
182 method = cnxprops.cnxtype |
204 warn('[3.16] give an qualified URI as database instead of using ' |
183 if method == 'pyro': |
205 'host/cnxprops to specify the connection method', |
|
206 DeprecationWarning, stacklevel=2) |
|
207 if cnxprops.cnxtype == 'zmq': |
|
208 database = kwargs.pop('host') |
|
209 elif cnxprops.cnxtype == 'inmemory': |
|
210 database = 'inmemory://' + database |
|
211 else: |
|
212 database = 'pyro://%s/%s.%s' % (kwargs.pop('host', ''), |
|
213 kwargs.pop('group', 'cubicweb'), |
|
214 database) |
|
215 puri = urlparse(database) |
|
216 method = puri.scheme.lower() |
|
217 if method == 'inmemory': |
|
218 config = cwconfig.instance_configuration(puuri.path) |
|
219 else: |
184 config = cwconfig.CubicWebNoAppConfiguration() |
220 config = cwconfig.CubicWebNoAppConfiguration() |
185 if host: |
221 repo = get_repository(database, config=config) |
186 config.global_set_option('pyro-ns-host', host) |
|
187 if group: |
|
188 config.global_set_option('pyro-ns-group', group) |
|
189 elif method == 'zmq': |
|
190 config = cwconfig.CubicWebNoAppConfiguration() |
|
191 else: |
|
192 assert database |
|
193 config = cwconfig.instance_configuration(database) |
|
194 repo = get_repository(method, database, config=config) |
|
195 if method == 'inmemory': |
222 if method == 'inmemory': |
196 vreg = repo.vreg |
223 vreg = repo.vreg |
197 elif setvreg: |
224 elif setvreg: |
198 if mulcnx: |
225 if mulcnx: |
199 multiple_connections_fix() |
226 multiple_connections_fix() |
216 vreg = config |
243 vreg = config |
217 config = None |
244 config = None |
218 else: |
245 else: |
219 vreg = None |
246 vreg = None |
220 # get local access to the repository |
247 # get local access to the repository |
221 return get_repository('inmemory', config=config, vreg=vreg) |
248 return get_repository('inmemory://', config=config, vreg=vreg) |
222 |
|
223 def in_memory_cnx(repo, login, **kwargs): |
|
224 """Establish a In memory connection to a <repo> for the user with <login> |
|
225 |
|
226 additionel credential might be required""" |
|
227 cnxprops = ConnectionProperties('inmemory') |
|
228 return repo_connect(repo, login, cnxprops=cnxprops, **kwargs) |
|
229 |
249 |
230 def in_memory_repo_cnx(config, login, **kwargs): |
250 def in_memory_repo_cnx(config, login, **kwargs): |
231 """useful method for testing and scripting to get a dbapi.Connection |
251 """useful method for testing and scripting to get a dbapi.Connection |
232 object connected to an in-memory repository instance |
252 object connected to an in-memory repository instance |
233 """ |
253 """ |
234 # connection to the CubicWeb repository |
254 # connection to the CubicWeb repository |
235 repo = in_memory_repo(config) |
255 repo = in_memory_repo(config) |
236 return repo, in_memory_cnx(repo, login, **kwargs) |
256 return repo, repo_connect(repo, login, **kwargs) |
237 |
257 |
238 |
258 # XXX web only method, move to webconfig? |
239 def anonymous_session(vreg): |
259 def anonymous_session(vreg): |
240 """return a new anonymous session |
260 """return a new anonymous session |
241 |
261 |
242 raises an AuthenticationError if anonymous usage is not allowed |
262 raises an AuthenticationError if anonymous usage is not allowed |
243 """ |
263 """ |
244 anoninfo = vreg.config.anonymous_user() |
264 anoninfo = vreg.config.anonymous_user() |
245 if anoninfo is None: # no anonymous user |
265 if anoninfo is None: # no anonymous user |
246 raise AuthenticationError('anonymous access is not authorized') |
266 raise AuthenticationError('anonymous access is not authorized') |
247 anon_login, anon_password = anoninfo |
267 anon_login, anon_password = anoninfo |
248 cnxprops = ConnectionProperties(vreg.config.repo_method) |
|
249 # use vreg's repository cache |
268 # use vreg's repository cache |
250 repo = vreg.config.repository(vreg) |
269 repo = vreg.config.repository(vreg) |
251 anon_cnx = repo_connect(repo, anon_login, |
270 anon_cnx = repo_connect(repo, anon_login, password=anon_password) |
252 cnxprops=cnxprops, password=anon_password) |
|
253 anon_cnx.vreg = vreg |
271 anon_cnx.vreg = vreg |
254 return DBAPISession(anon_cnx, anon_login) |
272 return DBAPISession(anon_cnx, anon_login) |
255 |
273 |
256 |
274 |
257 class _NeedAuthAccessMock(object): |
275 class _NeedAuthAccessMock(object): |
533 |
552 |
534 def __init__(self, repo, cnxid, cnxprops=None): |
553 def __init__(self, repo, cnxid, cnxprops=None): |
535 self._repo = repo |
554 self._repo = repo |
536 self.sessionid = cnxid |
555 self.sessionid = cnxid |
537 self._close_on_del = getattr(cnxprops, 'close_on_del', True) |
556 self._close_on_del = getattr(cnxprops, 'close_on_del', True) |
538 self._cnxtype = getattr(cnxprops, 'cnxtype', 'pyro') |
|
539 self._web_request = False |
557 self._web_request = False |
540 if cnxprops and cnxprops.log_queries: |
558 if cnxprops and cnxprops.log_queries: |
541 self.executed_queries = [] |
559 self.executed_queries = [] |
542 self.cursor_class = LogCursor |
560 self.cursor_class = LogCursor |
543 if self._cnxtype == 'pyro': |
561 |
544 # check client/server compat |
562 @property |
545 if self._repo.get_versions()['cubicweb'] < (3, 8, 6): |
563 def is_repo_in_memory(self): |
546 self._txid = lambda cursor=None: {} |
564 """return True if this is a local, aka in-memory, connection to the |
|
565 repository |
|
566 """ |
|
567 try: |
|
568 from cubicweb.server.repository import Repository |
|
569 except ImportError: |
|
570 # code not available, no way |
|
571 return False |
|
572 return isinstance(self._repo, Repository) |
547 |
573 |
548 def __repr__(self): |
574 def __repr__(self): |
549 if self.anonymous_connection: |
575 if self.anonymous_connection: |
550 return '<Connection %s (anonymous)>' % self.sessionid |
576 return '<Connection %s (anonymous)>' % self.sessionid |
551 return '<Connection %s>' % self.sessionid |
577 return '<Connection %s>' % self.sessionid |