• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Products.GenericSetup / 5104175550

pending completion
5104175550

push

github

web-flow
Merge pull request #128 from zopefoundation/serialize_utility_registration_global

Prefer `component` (over  `factory`) in the serialization of utility registrations

1668 of 1988 branches covered (83.9%)

Branch coverage included in aggregate %.

26 of 26 new or added lines in 3 files covered. (100.0%)

9340 of 9819 relevant lines covered (95.12%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.97
/src/Products/GenericSetup/components.py
1
##############################################################################
2
#
3
# Copyright (c) 2006 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE.
11
#
12
##############################################################################
13
"""Local component registry export / import handler.
1✔
14
"""
15

16
from operator import itemgetter
1✔
17

18
from Acquisition import aq_base
1✔
19
from Acquisition import aq_parent
1✔
20
from zope.component import adapts
1✔
21
from zope.component import getUtilitiesFor
1✔
22
from zope.component import queryMultiAdapter
1✔
23
from zope.component.interfaces import IPossibleSite
1✔
24
from zope.interface.interfaces import ComponentLookupError
1✔
25
from zope.interface.interfaces import IComponentRegistry
1✔
26

27
from .interfaces import IBody
1✔
28
from .interfaces import IComponentsHandlerBlacklist
1✔
29
from .interfaces import ISetupEnviron
1✔
30
from .utils import XMLAdapterBase
1✔
31
from .utils import _getDottedName
1✔
32
from .utils import _isGlobalObject
1✔
33
from .utils import _resolveDottedName
1✔
34

35

36
BLACKLIST_SELF = _getDottedName(IComponentsHandlerBlacklist)
1✔
37

38

39
class ComponentRegistryXMLAdapter(XMLAdapterBase):
1✔
40

41
    """XML im- and exporter for a local component registry.
42
    """
43

44
    adapts(IComponentRegistry, ISetupEnviron)
1✔
45

46
    _LOGGER_ID = 'componentregistry'
1✔
47

48
    name = 'componentregistry'
1✔
49

50
    def _constructBlacklist(self):
1✔
51
        blacklist = {BLACKLIST_SELF}
1✔
52
        utils = getUtilitiesFor(IComponentsHandlerBlacklist)
1✔
53
        for _, util in utils:
1✔
54
            names = [_getDottedName(i) for i in util.getExcludedInterfaces()]
1✔
55
            blacklist.update(names)
1✔
56
        return blacklist
1✔
57

58
    def _exportNode(self):
1✔
59
        node = self._doc.createElement('componentregistry')
1✔
60
        fragment = self._doc.createDocumentFragment()
1✔
61

62
        child = self._doc.createElement('adapters')
1✔
63
        child.appendChild(self._extractAdapters())
1✔
64
        self._logger.info('Adapters exported.')
1✔
65
        fragment.appendChild(child)
1✔
66

67
        child = self._doc.createElement('subscribers')
1✔
68
        child.appendChild(self._extractSubscriptionAdapters())
1✔
69
        child.appendChild(self._extractHandlers())
1✔
70
        self._logger.info('Subscribers exported.')
1✔
71
        fragment.appendChild(child)
1✔
72

73
        child = self._doc.createElement('utilities')
1✔
74
        child.appendChild(self._extractUtilities())
1✔
75
        self._logger.info('Utilities exported.')
1✔
76
        fragment.appendChild(child)
1✔
77

78
        node.appendChild(fragment)
1✔
79

80
        return node
1✔
81

82
    def _importNode(self, node):
1✔
83
        if self.environ.shouldPurge():
1✔
84
            self._purgeAdapters()
1✔
85
            self._logger.info('Adapters purged.')
1✔
86
            self._purgeSubscriptionAdapters()
1✔
87
            self._purgeHandlers()
1✔
88
            self._logger.info('Subscribers purged.')
1✔
89
            self._purgeUtilities()
1✔
90
            self._logger.info('Utilities purged.')
1✔
91

92
        for child in node.childNodes:
1✔
93
            if child.nodeName == 'adapters':
1✔
94
                self._initAdapters(child)
1✔
95
                self._logger.info('Adapters registered.')
1✔
96
            if child.nodeName == 'subscribers':
1✔
97
                # These should have either a factory or a handler.  Not both.
98
                # Handle factory:
99
                self._initSubscriptionAdapters(child)
1✔
100
                # Handle handler:
101
                self._initHandlers(child)
1✔
102
                self._logger.info('Subscribers registered.')
1✔
103
            if child.nodeName == 'utilities':
1✔
104
                self._initUtilities(child)
1✔
105
                self._logger.info('Utilities registered.')
1✔
106

107
    def _purgeAdapters(self):
1✔
108
        registrations = tuple(self.context.registeredAdapters())
1✔
109
        blacklist = self._constructBlacklist()
1✔
110

111
        for registration in registrations:
1!
112
            factory = registration.factory
×
113
            required = registration.required
×
114
            provided = registration.provided
×
115
            name = registration.name
×
116
            if _getDottedName(provided) in blacklist:
×
117
                continue
×
118

119
            self.context.unregisterAdapter(factory=factory,
×
120
                                           required=required,
121
                                           provided=provided,
122
                                           name=name)
123

124
    def _purgeSubscriptionAdapters(self):
1✔
125
        registrations = tuple(self.context.registeredSubscriptionAdapters())
1✔
126
        blacklist = self._constructBlacklist()
1✔
127

128
        for registration in registrations:
1!
129
            factory = registration.factory
×
130
            required = registration.required
×
131
            provided = registration.provided
×
132
            if _getDottedName(provided) in blacklist:
×
133
                continue
×
134

135
            self.context.unregisterSubscriptionAdapter(factory=factory,
×
136
                                                       required=required,
137
                                                       provided=provided)
138

139
    def _purgeHandlers(self):
1✔
140
        registrations = tuple(self.context.registeredHandlers())
1✔
141

142
        for registration in registrations:
1!
143
            factory = registration.factory
×
144
            required = registration.required
×
145

146
            self.context.unregisterHandler(factory=factory, required=required)
×
147

148
    def _purgeUtilities(self):
1✔
149
        registrations = tuple(self.context.registeredUtilities())
1✔
150
        blacklist = self._constructBlacklist()
1✔
151

152
        for registration in registrations:
1!
153
            provided = registration.provided
×
154
            name = registration.name
×
155
            if _getDottedName(provided) in blacklist:
×
156
                continue
×
157
            self.context.unregisterUtility(provided=provided, name=name)
×
158

159
    def _initAdapters(self, node):
1✔
160
        blacklist = self._constructBlacklist()
1✔
161

162
        for child in node.childNodes:
1✔
163
            if child.nodeName != 'adapter':
1✔
164
                continue
1✔
165

166
            factory = _resolveDottedName(child.getAttribute('factory'))
1✔
167

168
            provided = child.getAttribute('provides')
1✔
169
            if provided in blacklist:
1!
170
                continue
×
171

172
            provided = _resolveDottedName(provided)
1✔
173
            name = str(child.getAttribute('name'))
1✔
174

175
            # BBB
176
            for_ = child.getAttribute('for') or child.getAttribute('for_')
1✔
177
            required = []
1✔
178
            for interface in for_.split():
1✔
179
                required.append(_resolveDottedName(interface))
1✔
180

181
            if child.hasAttribute('remove'):
1✔
182
                self.context.unregisterAdapter(factory,
1✔
183
                                               required, provided, name)
184
                continue
1✔
185

186
            self.context.registerAdapter(factory,
1✔
187
                                         required=required,
188
                                         provided=provided,
189
                                         name=name)
190

191
    def _initSubscriptionAdapters(self, node):
1✔
192
        blacklist = self._constructBlacklist()
1✔
193

194
        for child in node.childNodes:
1✔
195
            if child.nodeName != 'subscriber':
1✔
196
                continue
1✔
197

198
            # We only handle subscribers with a factory attribute.
199
            # The handler attribute is handled by _initHandlers.
200
            # Only one of the two attributes is supported in a subscriber.
201
            factory = child.getAttribute('factory')
1✔
202
            if not factory:
1✔
203
                continue
1✔
204

205
            handler = child.getAttribute('handler')
1✔
206
            if handler:
1!
207
                raise ValueError("Can not specify both a factory and a "
×
208
                                 "handler in a subscriber registration.")
209

210
            factory = _resolveDottedName(factory)
1✔
211

212
            provided = child.getAttribute('provides')
1✔
213
            if provided in blacklist:
1!
214
                continue
×
215

216
            provided = _resolveDottedName(provided)
1✔
217

218
            # BBB
219
            for_ = child.getAttribute('for') or child.getAttribute('for_')
1✔
220
            required = []
1✔
221
            for interface in for_.split():
1✔
222
                required.append(_resolveDottedName(interface))
1✔
223

224
            # Uninstall to prevent duplicate registrations. While this is
225
            # allowed in ZCML, GS profiles can be run multiple times.
226
            self.context.unregisterSubscriptionAdapter(factory,
1✔
227
                                                       required=required,
228
                                                       provided=provided)
229

230
            if child.hasAttribute('remove'):
1✔
231
                continue
1✔
232

233
            self.context.registerSubscriptionAdapter(factory,
1✔
234
                                                     required=required,
235
                                                     provided=provided)
236

237
    def _initHandlers(self, node):
1✔
238
        for child in node.childNodes:
1✔
239
            if child.nodeName != 'subscriber':
1✔
240
                continue
1✔
241

242
            # We only handle subscribers with a handler attribute.
243
            # The factory attribute is handled by _initSubscriptionAdapters.
244
            # Only one of the two attributes is supported in a subscriber.
245
            handler = child.getAttribute('handler')
1✔
246
            if not handler:
1✔
247
                continue
1✔
248

249
            factory = child.getAttribute('factory')
1✔
250
            if factory:
1!
251
                raise ValueError("Can not specify both a factory and a "
×
252
                                 "handler in a subscriber registration.")
253

254
            if child.hasAttribute('provides'):
1!
255
                raise ValueError("Cannot use handler with provides in a "
×
256
                                 "subscriber registration.")
257

258
            handler = _resolveDottedName(handler)
1✔
259

260
            # BBB
261
            for_ = child.getAttribute('for') or child.getAttribute('for_')
1✔
262
            required = []
1✔
263

264
            for interface in for_.split():
1✔
265
                required.append(_resolveDottedName(interface))
1✔
266

267
            # Uninstall to prevent duplicate registrations. While this is
268
            # allowed in ZCML, GS profiles can be run multiple times.
269
            self.context.unregisterHandler(handler, required=required)
1✔
270

271
            if child.hasAttribute('remove'):
1✔
272
                continue
1✔
273

274
            self.context.registerHandler(handler, required=required)
1✔
275

276
    def _getSite(self):
1✔
277
        # Get the site by either __parent__ or Acquisition
278
        site = getattr(self.context, '__parent__', None)
1✔
279
        if site is None:
1!
280
            site = aq_parent(self.context)
×
281
        return site
1✔
282

283
    def _initUtilities(self, node):
1✔
284
        site = self._getSite()
1✔
285
        blacklist = self._constructBlacklist()
1✔
286

287
        current_utilities = self.context.registeredUtilities()
1✔
288

289
        for child in node.childNodes:
1✔
290
            if child.nodeName != 'utility':
1✔
291
                continue
1✔
292

293
            provided = child.getAttribute('interface')
1✔
294
            if provided in blacklist:
1✔
295
                continue
1✔
296

297
            provided = _resolveDottedName(provided)
1✔
298
            name = str(child.getAttribute('name'))
1✔
299

300
            component = child.getAttribute('component')
1✔
301
            component = component and _resolveDottedName(component) or None
1✔
302

303
            factory = child.getAttribute('factory')
1✔
304
            factory = factory and _resolveDottedName(factory) or None
1✔
305

306
            if child.hasAttribute('remove'):
1✔
307
                if self.context.queryUtility(provided, name) is not None:
1!
308
                    ofs_id = self._ofs_id(child)
1✔
309
                    if ofs_id in self.context.objectIds():
1✔
310
                        self.context._delObject(ofs_id, suppress_events=True)
1✔
311
                    self.context.unregisterUtility(provided=provided,
1✔
312
                                                   name=name)
313
                continue
1✔
314

315
            if component and factory:
1!
316
                raise ValueError("Can not specify both a factory and a "
×
317
                                 "component in a utility registration.")
318

319
            obj_path = child.getAttribute('object')
1✔
320
            if not component and not factory and obj_path is not None:
1✔
321
                # Support for registering the site itself
322
                if obj_path in ('', '/'):
1!
323
                    obj = site
×
324
                else:
325
                    # BBB: filter out path segments, we did claim to support
326
                    # nested paths once
327
                    id_ = [p for p in obj_path.split('/') if p][0]
1✔
328
                    obj = getattr(site, id_, None)
1✔
329

330
                if obj is not None:
1!
331
                    self.context.registerUtility(aq_base(obj), provided, name)
1✔
332
                else:
333
                    # Log an error, object not found
334
                    self._logger.warning("The object %s was not found, while "
×
335
                                         "trying to register an utility. The "
336
                                         "provided object definition was %s. "
337
                                         "The site used was: %s"
338
                                         % (repr(obj), obj_path, repr(site)))
339
            elif component:
1✔
340
                self.context.registerUtility(component, provided, name)
1✔
341
            elif factory:
1!
342
                current = [utility for utility in current_utilities
1✔
343
                           if utility.provided == provided and
344
                           utility.name == name]
345

346
                if current and getattr(current[0], "factory", None) == factory:
1!
347
                    continue
×
348

349
                obj = factory()
1✔
350
                ofs_id = self._ofs_id(child)
1✔
351
                if ofs_id not in self.context.objectIds():
1✔
352
                    self.context._setObject(ofs_id, aq_base(obj),
1✔
353
                                            set_owner=False,
354
                                            suppress_events=True)
355
                obj = self.context.get(ofs_id)
1✔
356
                obj.__name__ = ofs_id
1✔
357
                obj.__parent__ = aq_base(self.context)
1✔
358
                self.context.registerUtility(aq_base(obj), provided, name)
1✔
359
            else:
360
                self._logger.warning("Invalid utility registration for "
×
361
                                     "interface %s" % provided)
362

363
    def _ofs_id(self, child):
1✔
364
        # We build a valid OFS id by using the interface's full
365
        # dotted path or using the specified id
366
        name = str(child.getAttribute('name'))
1✔
367
        ofs_id = str(child.getAttribute('id'))
1✔
368
        if not ofs_id:
1✔
369
            ofs_id = str(child.getAttribute('interface'))
1✔
370
            # In case of named utilities we append the name
371
            if name:
1✔
372
                ofs_id += '-' + str(name)
1✔
373
        return ofs_id
1✔
374

375
    def _extractAdapters(self):
1✔
376
        fragment = self._doc.createDocumentFragment()
1✔
377

378
        registrations = [{'factory': _getDottedName(reg.factory),
1✔
379
                          'provided': _getDottedName(reg.provided),
380
                          'required': reg.required,
381
                          'name': reg.name}
382
                         for reg in self.context.registeredAdapters()]
383
        registrations.sort(key=itemgetter('name'))
1✔
384
        registrations.sort(key=itemgetter('provided'))
1✔
385
        blacklist = self._constructBlacklist()
1✔
386

387
        for reg_info in registrations:
1✔
388
            if reg_info['provided'] in blacklist:
1!
389
                continue
×
390

391
            child = self._doc.createElement('adapter')
1✔
392

393
            for_ = ''
1✔
394
            for interface in reg_info['required']:
1✔
395
                for_ = for_ + _getDottedName(interface) + '\n           '
1✔
396

397
            child.setAttribute('factory', reg_info['factory'])
1✔
398
            child.setAttribute('provides', reg_info['provided'])
1✔
399
            child.setAttribute('for', for_.strip())
1✔
400
            if reg_info['name']:
1✔
401
                child.setAttribute('name', reg_info['name'])
1✔
402

403
            fragment.appendChild(child)
1✔
404

405
        return fragment
1✔
406

407
    def _extractSubscriptionAdapters(self):
1✔
408
        fragment = self._doc.createDocumentFragment()
1✔
409

410
        registrations = [{'factory': _getDottedName(reg.factory),
1✔
411
                          'provided': _getDottedName(reg.provided),
412
                          'required': reg.required} for reg
413
                         in self.context.registeredSubscriptionAdapters()]
414
        registrations.sort(key=itemgetter('factory'))
1✔
415
        registrations.sort(key=itemgetter('provided'))
1✔
416
        blacklist = self._constructBlacklist()
1✔
417

418
        for reg_info in registrations:
1✔
419
            if reg_info['provided'] in blacklist:
1!
420
                continue
×
421

422
            child = self._doc.createElement('subscriber')
1✔
423

424
            for_ = ''
1✔
425
            for interface in reg_info['required']:
1✔
426
                for_ = for_ + _getDottedName(interface) + '\n           '
1✔
427

428
            child.setAttribute('factory', reg_info['factory'])
1✔
429
            child.setAttribute('provides', reg_info['provided'])
1✔
430
            child.setAttribute('for', for_.strip())
1✔
431

432
            fragment.appendChild(child)
1✔
433

434
        return fragment
1✔
435

436
    def _extractHandlers(self):
1✔
437
        fragment = self._doc.createDocumentFragment()
1✔
438

439
        registrations = [{'factory': _getDottedName(reg.factory),
1✔
440
                          'required': reg.required}
441
                         for reg in self.context.registeredHandlers()]
442
        registrations.sort(key=itemgetter('factory'))
1✔
443
        registrations.sort(key=itemgetter('required'))
1✔
444

445
        for reg_info in registrations:
1✔
446
            child = self._doc.createElement('subscriber')
1✔
447

448
            for_ = ''
1✔
449
            for interface in reg_info['required']:
1✔
450
                for_ = for_ + _getDottedName(interface) + '\n           '
1✔
451

452
            child.setAttribute('handler', reg_info['factory'])
1✔
453
            child.setAttribute('for', for_.strip())
1✔
454

455
            fragment.appendChild(child)
1✔
456

457
        return fragment
1✔
458

459
    def _extractUtilities(self):
1✔
460
        fragment = self._doc.createDocumentFragment()
1✔
461

462
        registrations = [{'component': reg.component,
1✔
463
                          'factory': getattr(reg, 'factory', None),
464
                          'provided': _getDottedName(reg.provided),
465
                          'name': reg.name}
466
                         for reg in self.context.registeredUtilities()]
467
        registrations.sort(key=itemgetter('name'))
1✔
468
        registrations.sort(key=itemgetter('provided'))
1✔
469
        site = aq_base(self._getSite())
1✔
470
        blacklist = self._constructBlacklist()
1✔
471

472
        for reg_info in registrations:
1✔
473
            if reg_info['provided'] in blacklist:
1✔
474
                continue
1✔
475

476
            child = self._doc.createElement('utility')
1✔
477
            child.setAttribute('interface', reg_info['provided'])
1✔
478

479
            if reg_info['name']:
1✔
480
                child.setAttribute('name', reg_info['name'])
1✔
481

482
            if reg_info['factory'] is not None:
1!
483
                factory = _getDottedName(reg_info['factory'])
×
484
                child.setAttribute('factory', factory)
×
485
            else:
486
                factory = None
1✔
487
                comp = reg_info['component']
1✔
488
                # check if the component is acquisition wrapped. If it is,
489
                # export an object reference instead of a factory reference
490
                if getattr(comp, 'aq_base', None) is not None:
1✔
491
                    if aq_base(comp) is site:
1!
492
                        child.setAttribute('object', '')
×
493
                    elif hasattr(aq_base(comp), 'getId'):
1!
494
                        child.setAttribute('object', comp.getId())
1✔
495
                    else:
496
                        # This is a five.localsitemanager wrapped utility
497
                        factory = _getDottedName(type(aq_base(comp)))
×
498
                        child.setAttribute('factory', factory)
×
499
                elif _isGlobalObject(comp):
1✔
500
                    child.setAttribute('component', _getDottedName(comp))
1✔
501
                else:
502
                    factory = _getDottedName(type(comp))
1✔
503
                    child.setAttribute('factory', factory)
1✔
504
                if factory is not None:
1✔
505
                    ofs_id = self._ofs_id(child)
1✔
506
                    name = getattr(comp, '__name__', None)
1✔
507
                    if ofs_id != name:
1✔
508
                        if name is not None:
1!
509
                            child.setAttribute('id', name)
1✔
510
                        else:
511
                            child.setAttribute('id', ofs_id)
×
512

513
            fragment.appendChild(child)
1✔
514

515
        return fragment
1✔
516

517

518
def importComponentRegistry(context):
1✔
519
    """Import local components.
520
    """
521
    site = context.getSite()
1✔
522
    sm = None
1✔
523
    if IPossibleSite.providedBy(site):
1!
524
        # All object managers are an IPossibleSite, but this
525
        # defines the getSiteManager method to be available
526
        try:
1✔
527
            sm = site.getSiteManager()
1✔
528
        except ComponentLookupError:
1✔
529
            sm = None
1✔
530

531
    if sm is None or not IComponentRegistry.providedBy(sm):
1✔
532
        logger = context.getLogger('componentregistry')
1✔
533
        logger.info("Can not register components, as no registry was found.")
1✔
534
        return
1✔
535

536
    importer = queryMultiAdapter((sm, context), IBody)
1✔
537
    if importer:
1!
538
        body = context.readDataFile('componentregistry.xml')
1✔
539
        if body is not None:
1!
540
            importer.body = body
1✔
541
        else:
542
            logger = context.getLogger('componentregistry')
×
543
            logger.debug("Nothing to import")
×
544

545

546
def exportComponentRegistry(context):
1✔
547
    """Export local components.
548
    """
549
    site = context.getSite()
1✔
550
    sm = None
1✔
551
    if IPossibleSite.providedBy(site):
1!
552
        # All object managers are an IPossibleSite, but this
553
        # defines the getSiteManager method to be available
554
        try:
1✔
555
            sm = site.getSiteManager()
1✔
556
        except ComponentLookupError:
1✔
557
            sm = None
1✔
558

559
    if sm is None or not IComponentRegistry.providedBy(sm):
1!
560
        logger = context.getLogger('componentregistry')
1✔
561
        logger.debug("Nothing to export.")
1✔
562
        return
1✔
563

564
    exporter = queryMultiAdapter((sm, context), IBody)
×
565
    if exporter:
×
566
        body = exporter.body
×
567
        if body is not None:
×
568
            context.writeDataFile('componentregistry.xml', body,
×
569
                                  exporter.mime_type)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc