Source

bloodhound-mq / t115 / t115_r1427886_product_envs.diff

The branch 't115_product_env' does not exist.
Full commit
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# HG changeset patch
# Parent d143652322b7ec13c6bd1f0b7aaf7fa01297ba6a
BH Multiproduct #115 : Product environments

diff -r d143652322b7 bloodhound_multiproduct/multiproduct/env.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bloodhound_multiproduct/multiproduct/env.py	Thu Jan 03 01:10:05 2013 -0500
@@ -0,0 +1,476 @@
+
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+"""Bloodhound product environment and related APIs"""
+
+import os.path
+
+from trac.config import ConfigSection, Option
+from trac.core import Component, ComponentManager, ExtensionPoint, \
+        implements, TracError
+from trac.env import Environment, ISystemInfoProvider
+from trac.util import get_pkginfo, lazy
+from trac.util.compat import sha1
+
+from multiproduct.model import Product
+
+class ProductEnvironment(Component, ComponentManager):
+    """Bloodhound product-aware environment manager.
+
+    Bloodhound encapsulates access to product resources stored inside a
+    Trac environment via product environments. They are compatible lightweight
+    irepresentations of top level environment. 
+
+    Product environments contain among other things:
+
+    * a configuration file, 
+    * product-aware clones of the wiki and ticket attachments files,
+
+    Product environments do not have:
+
+    * product-specific templates and plugins,
+    * a separate database
+    * active participation in database upgrades and other setup tasks
+
+    See https://issues.apache.org/bloodhound/wiki/Proposals/BEP-0003
+    """
+
+    implements(ISystemInfoProvider)
+
+    @property
+    def system_info_providers(self):
+        r"""System info will still be determined by the global environment.
+        """
+        return self.env.system_info_providers
+
+    @property
+    def setup_participants(self):
+        """Setup participants list for product environments will always
+        be empty based on the fact that upgrades will only be handled by
+        the global environment.
+        """
+        return ()
+
+    components_section = ConfigSection('components',
+        """This section is used to enable or disable components
+        provided by plugins, as well as by Trac itself.
+
+        See also: TracIni , TracPlugins
+        """)
+
+    @property
+    def shared_plugins_dir():
+        """Product environments may not add plugins.
+        """
+        return ''
+
+    # TODO: Estimate product base URL considering global base URL, pattern, ...
+    base_url = ''
+
+    # TODO: Estimate product base URL considering global base URL, pattern, ...
+    base_url_for_redirect = ''
+
+    @property
+    def secure_cookies(self):
+        """Restrict cookies to HTTPS connections.
+        """
+        return self.env.secure_cookies
+
+    @property
+    def project_name(self):
+        """Name of the product.
+        """
+        return self.product.name
+
+    @property
+    def project_description(self):
+        """Short description of the product.
+        """
+        return self.product.description
+
+    @property
+    def project_url(self):
+        """URL of the main project web site, usually the website in
+        which the `base_url` resides. This is used in notification
+        e-mails.
+        """
+        return self.env.project_url
+
+    project_admin = Option('project', 'admin', '',
+        """E-Mail address of the product's leader / administrator.""")
+
+    @property
+    def project_admin_trac_url(self):
+        """Base URL of a Trac instance where errors in this Trac
+        should be reported.
+        """
+        return self.env.project_admin_trac_url
+
+    # FIXME: Should products have different values i.e. config option ?
+    @property
+    def project_footer(self):
+        """Page footer text (right-aligned).
+        """
+        return self.env.project_footer
+
+    project_icon = Option('project', 'icon', 'common/trac.ico',
+        """URL of the icon of the product.""")
+
+    log_type = Option('logging', 'log_type', 'inherit',
+        """Logging facility to use.
+
+        Should be one of (`inherit`, `none`, `file`, `stderr`, 
+        `syslog`, `winlog`).""")
+
+    log_file = Option('logging', 'log_file', 'trac.log',
+        """If `log_type` is `file`, this should be a path to the
+        log-file.  Relative paths are resolved relative to the `log`
+        directory of the environment.""")
+
+    log_level = Option('logging', 'log_level', 'DEBUG',
+        """Level of verbosity in log.
+
+        Should be one of (`CRITICAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`).""")
+
+    log_format = Option('logging', 'log_format', None,
+        """Custom logging format.
+
+        If nothing is set, the following will be used:
+
+        Trac[$(module)s] $(levelname)s: $(message)s
+
+        In addition to regular key names supported by the Python
+        logger library (see
+        http://docs.python.org/library/logging.html), one could use:
+
+        - $(path)s     the path for the current environment
+        - $(basename)s the last path component of the current environment
+        - $(project)s  the project name
+
+        Note the usage of `$(...)s` instead of `%(...)s` as the latter form
+        would be interpreted by the ConfigParser itself.
+
+        Example:
+        `($(thread)d) Trac[$(basename)s:$(module)s] $(levelname)s: $(message)s`
+
+        ''(since 0.10.5)''""")
+
+    def __init__(self, env, product):
+        """Initialize the product environment.
+
+        :param env:     the global Trac environment
+        :param product: product prefix or an instance of
+                        multiproduct.model.Product
+        """
+        ComponentManager.__init__(self)
+
+        if isinstance(product, Product):
+            if product._env is not env:
+                raise ValueError("Product's environment mismatch")
+        elif isinstance(product, basestring):
+            products = Product.select(env, where={'prefix': product})
+            if len(products) == 1 :
+                product = products[0]
+            else:
+                env.log.debug("Products for '%s' : %s",
+                        product, products)
+                raise LookupError("Missing product %s" % (product,))
+
+        self.env = env
+        self.product = product
+        self.systeminfo = []
+        self._href = self._abs_href = None
+
+        self.setup_config()
+
+    # ISystemInfoProvider methods
+
+    def get_system_info(self):
+        return self.env.get_system_info()
+
+    # Same as parent environment's . Avoid duplicated code
+    component_activated = Environment.component_activated.im_func
+    _component_name = Environment._component_name.im_func
+    _component_rules = Environment._component_rules
+    enable_component = Environment.enable_component.im_func
+    get_known_users = Environment.get_known_users.im_func
+    get_systeminfo = Environment.get_system_info.im_func
+    get_repository = Environment.get_repository.im_func
+    is_component_enabled = Environment.is_component_enabled.im_func
+
+    def get_db_cnx(self):
+        """Return a database connection from the connection pool
+
+        :deprecated: Use :meth:`db_transaction` or :meth:`db_query` instead
+
+        `db_transaction` for obtaining the `db` database connection
+        which can be used for performing any query
+        (SELECT/INSERT/UPDATE/DELETE)::
+
+           with env.db_transaction as db:
+               ...
+
+
+        `db_query` for obtaining a `db` database connection which can
+        be used for performing SELECT queries only::
+
+           with env.db_query as db:
+               ...
+        """
+        # TODO: Install database schema proxy with limited scope (see #288)
+        #return DatabaseManager(self).get_connection()
+        raise NotImplementedError
+
+    @lazy
+    def db_exc(self):
+        """Return an object (typically a module) containing all the
+        backend-specific exception types as attributes, named
+        according to the Python Database API
+        (http://www.python.org/dev/peps/pep-0249/).
+
+        To catch a database exception, use the following pattern::
+
+            try:
+                with env.db_transaction as db:
+                    ...
+            except env.db_exc.IntegrityError, e:
+                ...
+        """
+        return DatabaseManager(self).get_exceptions()
+
+    def with_transaction(self, db=None):
+        """Decorator for transaction functions :deprecated:"""
+        # TODO: What shall we do ?
+        #return with_transaction(self, db)
+        raise NotImplementedError
+
+    def get_read_db(self):
+        """Return a database connection for read purposes :deprecated:
+
+        See `trac.db.api.get_read_db` for detailed documentation."""
+        # TODO: Install database schema proxy with limited scope (see #288)
+        #return DatabaseManager(self).get_connection(readonly=True)
+        raise NotImplementedError
+
+    @property
+    def db_query(self):
+        """Return a context manager which can be used to obtain a
+        read-only database connection.
+
+        Example::
+
+            with env.db_query as db:
+                cursor = db.cursor()
+                cursor.execute("SELECT ...")
+                for row in cursor.fetchall():
+                    ...
+
+        Note that a connection retrieved this way can be "called"
+        directly in order to execute a query::
+
+            with env.db_query as db:
+                for row in db("SELECT ..."):
+                    ...
+
+        If you don't need to manipulate the connection itself, this
+        can even be simplified to::
+
+            for row in env.db_query("SELECT ..."):
+                ...
+
+        :warning: after a `with env.db_query as db` block, though the
+          `db` variable is still available, you shouldn't use it as it
+          might have been closed when exiting the context, if this
+          context was the outermost context (`db_query` or
+          `db_transaction`).
+        """
+        # TODO: Install database schema proxy with limited scope (see #288)
+        #return QueryContextManager(self)
+        raise NotImplementedError
+
+    @property
+    def db_transaction(self):
+        """Return a context manager which can be used to obtain a
+        writable database connection.
+
+        Example::
+
+            with env.db_transaction as db:
+                cursor = db.cursor()
+                cursor.execute("UPDATE ...")
+
+        Upon successful exit of the context, the context manager will
+        commit the transaction. In case of nested contexts, only the
+        outermost context performs a commit. However, should an
+        exception happen, any context manager will perform a rollback.
+
+        Like for its read-only counterpart, you can directly execute a
+        DML query on the `db`::
+
+            with env.db_transaction as db:
+                db("UPDATE ...")
+
+        If you don't need to manipulate the connection itself, this
+        can also be simplified to::
+
+            env.db_transaction("UPDATE ...")
+
+        :warning: after a `with env.db_transaction` as db` block,
+          though the `db` variable is still available, you shouldn't
+          use it as it might have been closed when exiting the
+          context, if this context was the outermost context
+          (`db_query` or `db_transaction`).
+        """
+        # TODO: Install database schema proxy with limited scope (see #288)
+        #return TransactionContextManager(self)
+        raise NotImplementedError
+
+    def shutdown(self, tid=None):
+        """Close the environment."""
+        RepositoryManager(self).shutdown(tid)
+        # FIXME: Shared DB so IMO this should not happen ... at least not here
+        #DatabaseManager(self).shutdown(tid)
+        if tid is None:
+            self.log.removeHandler(self._log_handler)
+            self._log_handler.flush()
+            self._log_handler.close()
+            del self._log_handler
+
+    def create(self, options=[]):
+        """Placeholder for compatibility when trying to create the basic 
+        directory structure of the environment, etc ...
+
+        This method does nothing at all.
+        """
+        # TODO: Handle options args
+
+    def get_version(self, db=None, initial=False):
+        """Return the current version of the database.  If the
+        optional argument `initial` is set to `True`, the version of
+        the database used at the time of creation will be returned.
+
+        In practice, for database created before 0.11, this will
+        return `False` which is "older" than any db version number.
+
+        :since: 0.11
+
+        :since 1.0: deprecation warning: the `db` parameter is no
+                    longer used and will be removed in version 1.1.1
+        """
+        return self.env.get_version(db, initial)
+
+    def setup_config(self):
+        """Load the configuration object.
+        """
+        # FIXME: Install product-specific configuration object
+        self.config = self.env.config
+        self.setup_log()
+
+    def get_templates_dir(self):
+        """Return absolute path to the templates directory.
+        """
+        return self.env.get_templates_dir()
+
+    def get_htdocs_dir(self):
+        """Return absolute path to the htdocs directory."""
+        return self.env.get_htdocs_dir()
+
+    def get_log_dir(self):
+        """Return absolute path to the log directory."""
+        return self.env.get_log_dir()
+
+    def setup_log(self):
+        """Initialize the logging sub-system."""
+        from trac.log import logger_handler_factory
+        logtype = self.log_type
+        self.env.log.debug("Log type '%s' for product '%s'", 
+                logtype, self.product.prefix)
+        if logtype == 'inherit':
+            logtype = self.env.log_type
+            logfile = self.env.log_file
+            format = self.env.log_format
+        else:
+            logfile = self.log_file
+            format = self.log_format
+        if logtype == 'file' and not os.path.isabs(logfile):
+            logfile = os.path.join(self.get_log_dir(), logfile)
+        logid = 'Trac.%s.%s' % \
+                (sha1(self.env.path).hexdigest(), self.product.prefix)
+        if format:
+            format = format.replace('$(', '%(') \
+                     .replace('%(path)s', self.path) \
+                     .replace('%(basename)s', os.path.basename(self.path)) \
+                     .replace('%(project)s', self.project_name)
+        self.log, self._log_handler = logger_handler_factory(
+            logtype, logfile, self.log_level, logid, format=format)
+
+        from trac import core, __version__ as VERSION
+        self.log.info('-' * 32 + ' environment startup [Trac %s] ' + '-' * 32,
+                      get_pkginfo(core).get('version', VERSION))
+
+    def backup(self, dest=None):
+        """Create a backup of the database.
+
+        :param dest: Destination file; if not specified, the backup is
+                     stored in a file called db_name.trac_version.bak
+        """
+        return self.env.backup(dest)
+
+    def needs_upgrade(self):
+        """Return whether the environment needs to be upgraded."""
+        #for participant in self.setup_participants:
+        #    with self.db_query as db:
+        #        if participant.environment_needs_upgrade(db):
+        #            self.log.warn("Component %s requires environment upgrade",
+        #                          participant)
+        #            return True
+
+        # FIXME: For the time being no need to upgrade the environment
+        # TODO: Determine the role of product environments at upgrade time
+        return False
+
+    def upgrade(self, backup=False, backup_dest=None):
+        """Upgrade database.
+
+        :param backup: whether or not to backup before upgrading
+        :param backup_dest: name of the backup file
+        :return: whether the upgrade was performed
+        """
+        # (Database) upgrades handled by global environment
+        # FIXME: True or False ?
+        return True
+
+    @property
+    def href(self):
+        """The application root path"""
+        if not self._href:
+            self._href = Href(urlsplit(self.abs_href.base)[2])
+        return self._href
+
+    @property
+    def abs_href(self):
+        """The application URL"""
+        if not self._abs_href:
+            if not self.base_url:
+                self.log.warn("base_url option not set in configuration, "
+                              "generated links may be incorrect")
+                self._abs_href = Href('')
+            else:
+                self._abs_href = Href(self.base_url)
+        return self._abs_href
+