Ignore:
Timestamp:
Sep 24, 2006 12:19:42 AM (11 years ago)
Author:
hazmat
Message:
  • add event based indexing of flavor names
  • add ui for browsing content flavors and cache statistics.
  • switch global schema cache from tls to global (1 generation per combination for lifetime )
  • remove integration content class, its not advised to use this, it was a prototype for flavor management with at but it has bad end user semantics.
  • add some info to the readme regarding flavor querying
Location:
Products.ContentFlavors/trunk
Files:
5 added
2 deleted
9 edited

Legend:

Unmodified
Added
Removed
  • Products.ContentFlavors/trunk/__init__.py

    r1663 r1683  
     1################################################################## 
    12# 
     3# (C) Copyright 2006 ObjectRealms, LLC 
     4# All Rights Reserved 
     5# 
     6# This file is part of ContentFlavors. 
     7# 
     8# ContentFlavors is free software; you can redistribute it and/or modify 
     9# it under the terms of the GNU General Public License as published by 
     10# the Free Software Foundation; either version 2 of the License, or 
     11# (at your option) any later version. 
     12# 
     13# ContentFlavors is distributed in the hope that it will be useful, 
     14# but WITHOUT ANY WARRANTY; without even the implied warranty of 
     15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     16# GNU General Public License for more details. 
     17# 
     18# You should have received a copy of the GNU General Public License 
     19# along with CMFDeployment; if not, write to the Free Software 
     20# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
     21################################################################## 
     22""" 
     23$Id$ 
     24""" 
     25 
     26from interfaces import FLAVOR_INDEX 
     27from index import indexFlavors 
     28 
     29methods = { 
     30    FLAVOR_INDEX : indexFlavors 
     31    } 
  • Products.ContentFlavors/trunk/browser/configure.zcml

    r1673 r1683  
    77      name="flavors" 
    88      class=".flavor.FlavorManagementView" 
    9  
    109      permission="cmf.ModifyPortalContent" 
    1110      menu="object" 
     
    1312      /> 
    1413 
     14  <browser:page 
     15      for="Products.ContentFlavors.interfaces.IFlavorAware" 
     16      name="browse_flavors" 
     17      class=".flavor.FlavorBrowseView" 
     18      permission="cmf.ModifyPortalContent" 
     19      /> 
     20 
    1521</configure> 
  • Products.ContentFlavors/trunk/browser/flavor.py

    r1676 r1683  
    2323$Id$ 
    2424""" 
    25 from Products.ContentFlavors.interfaces import IFlavorProvider 
     25from Products.ContentFlavors.interfaces import IFlavorProvider, FLAVOR_INDEX 
     26from Products.ContentFlavors.schema import CacheStats, DEBUG_CACHE 
    2627 
    2728from zope.formlib import form 
    2829from Products.Five.formlib import formbase 
    29 from Products.Five import BrowserView 
     30from Products.Five.browser.pagetemplatefile import  ZopeTwoPageTemplateFile 
     31from Products.Five.browser import BrowserView 
     32from widget import CustomWidget 
    3033 
    31 from widget import CustomWidget 
    3234 
    3335class FlavorManagementView( formbase.PageEditForm ): 
    3436 
    35     #template = ZopeTwoPageTemplateFile('flavor.pt') 
     37    template = ZopeTwoPageTemplateFile('manage_flavors.pt') 
    3638    form_fields = form.Fields( IFlavorProvider ).select('flavor_names') 
    3739    form_fields['flavor_names'].custom_widget = CustomWidget 
     
    5052            if hacked_request: 
    5153                del self.request.debug 
     54 
     55    def activeFlavors( self ): 
     56        return self.context.portal_catalog.uniqueValuesFor( FLAVOR_INDEX ) 
     57 
     58    def activeFlavorsHistogram( self ): 
     59        return self.context.portal_catalog._catalog.getIndex(FLAVOR_INDEX).uniqueValues( withLengths=True ) 
     60     
     61    def debug( self ): 
     62        if DEBUG_CACHE: 
     63            return str(CacheStats) 
     64        return "Cache Stats Not Enabled" 
     65         
    5266     
    5367     
     68class FlavorBrowseView( BrowserView ): 
     69 
     70    template = ZopeTwoPageTemplateFile('browse_flavors.pt') 
     71     
     72    def __call__(self, *args, **kw): 
     73        #import pdb; pdb.set_trace() 
     74        self.name = name = self.request.get('name') 
     75        if not name: 
     76            for i in self.request.keys(): 
     77                print self.request.keys() 
     78            self.request.redirect(request.get('HTTP_REFERER', '.')) 
     79        self.results = self.context.portal_catalog( content_flavors = name ) 
     80        return self.template() 
     81             
     82             
     83     
  • Products.ContentFlavors/trunk/configure.zcml

    r1677 r1683  
    2020    /> 
    2121 
     22 <!-- Schema Instance Cache Invalidation --> 
    2223 <subscriber 
    2324    handler=".schema.handleModifiedFlavors" 
    2425    for=".interfaces.IFlavorsModifiedEvent"/> 
    2526 
     27 <!-- Flavor Indexing --> 
     28 <subscriber 
     29    handler=".index.handleModifiedFlavors" 
     30    for=".interfaces.IFlavorsModifiedEvent"/> 
     31  
     32 
    2633</configure> 
    2734 
  • Products.ContentFlavors/trunk/interfaces.py

    r1674 r1683  
    2828from zope.app.event.interfaces import IObjectEvent 
    2929from zope.app.event.objectevent import ObjectEvent 
     30 
     31FLAVOR_INDEX = "content_flavors" 
    3032 
    3133class IFlavor( Interface ): 
  • Products.ContentFlavors/trunk/provider.py

    r1677 r1683  
    102102        removed = original - current 
    103103 
    104         # gather old markers  
     104        # gather old markers for removal 
    105105        for name in removed: 
    106106            flavor = component.queryAdapter( self.context, IFlavor, name=name) 
  • Products.ContentFlavors/trunk/readme.txt

    r1682 r1683  
    2929 
    3030Content Flavors bridges a number of frameworks, and requires a 
    31 specific version to operate. Its tested on and requires 
    32  
    33  - Plone 2.5 
     31specific version to operate. Its tested on 
     32 
     33 - Plone 2.5.x 
    3434 - Zope 2.9.4 
    35  - Five 1.4 
     35 - Five 1.4.x 
    3636 - CMFonFive 1.3.3  
     37 
     38it may run on other variations, but they aren't supported by the author 
     39at this time. 
    3740 
    3841Status 
     
    176179than one attribute. 
    177180 
    178  
     181Querying Flavors  
     182---------------- 
     183 
     184a content's flavors are indexed and can be queried via the catalog 
     185 
     186example query, get all the content in the portal which has both of  
     187these flavors which were created by jim, reverse sort by modification  
     188date. 
     189 
     190  >> flavors = ('ore.traits.RainForestHabitat', 'ore.traits.Mammal') 
     191  >> flavor_query = dict( query = flavors, operator = 'and' ) 
     192  
     193  >> contraints = dict( Creator='jim', sort_on='modified', sort_order='reverse') 
     194  >> results = portal.portal_catalog( content_flavors = flavor_query, **constraints ) 
     195   
    179196 
    180197Component Responsibilities 
  • Products.ContentFlavors/trunk/schema.py

    r1682 r1683  
    3232from Products.Archetypes.atapi import Schema, CompositeSchema 
    3333 
     34from StringIO import StringIO 
    3435from threading import local 
     36 
     37DEBUG_CACHE = True 
     38 
     39class _CacheStats(object): 
     40    """ Debug Stats Collector """ 
     41    def __init__( self ): 
     42        # we check local just by h/m  
     43        self.local_total = 0 
     44        self.local_hits  = 0 
     45        # we check global by per flavor set h/m should only be 1 per  
     46        self.global_hits = {} 
     47        self.global_total = {} 
     48         
     49    def hitGlobal(self, names): 
     50        self.global_hits.setdefault(names, 0) 
     51        self.global_hits[ names ] += 1 
     52        self.global_total.setdefault(names, 0) 
     53        self.global_total[ names ] += 1 
     54 
     55    globalHit = hitGlobal 
     56     
     57    def missGlobal( self, names): 
     58        self.global_total.setdefault( names, 0) 
     59        self.global_total[ names ] += 1 
     60 
     61    globalMiss = missGlobal 
     62     
     63    def __str__( self ): 
     64   
     65        out = StringIO() 
     66         
     67        print >> out, "Schema Cache Statistics" 
     68        print >> out, "#"*20 
     69        print >> out, "\n"*2         
     70 
     71        print >> out, " Instance Caches" 
     72        print >> out, "  Hit Ratio %d Percentage %d  Hits %s and %s Misses of %s Total"%( 
     73            (float(self.local_hits)/(self.local_total-self.local_hits)), 
     74            (float(self.local_hits)/self.local_total)*100, 
     75            self.local_hits, 
     76            self.local_total-self.local_hits, 
     77            self.local_total 
     78            ) 
     79         
     80        print >> out, "" 
     81        print >> out, "","#"*5 
     82        print >> out, "" 
     83         
     84        for names in self.global_total.keys(): 
     85            print >> out, " Flavor Set - %s"%(', '.join( names ) ) 
     86 
     87            hits = self.global_hits.setdefault( names, 0) 
     88            total = self.global_total[ names ] 
     89            print >> out, "   Hit Ratio %d Percentage %s Hits %s and %s Misses of %s Total"%( 
     90                (float(hits)/(total-hits)), 
     91                (float(hits)/total)*100, 
     92                hits, 
     93                total-hits, 
     94                total 
     95                ) 
     96            print >> out, "\n" 
     97             
     98        return out.getvalue() 
     99         
     100    def hitLocal( self ): 
     101        self.local_hits += 1 
     102        self.local_total += 1 
     103 
     104    localHit = hitLocal 
     105 
     106    def missLocal( self ): 
     107        self.local_total += 1 
     108 
     109    localMiss = missLocal 
     110 
     111CacheStats = _CacheStats() 
    35112 
    36113class FlavorSchemaComposer( object ): 
     
    43120    def getSchema( self ): 
    44121        if self.context._v_schema is None: 
     122            if DEBUG_CACHE: CacheStats.missLocal() 
    45123            self._composeFlavorSchema() 
     124        else: 
     125            if DEBUG_CACHE: CacheStats.hitLocal() 
    46126        return ImplicitAcquisitionWrapper( self.context._v_schema, self.context ) 
    47127     
     
    59139         
    60140        # try to pickup schema from global cache 
    61         cache = GlobalSchemaCache.get( self.context, provider.flavor_names ) 
     141        flavor_names = provider.flavor_names  
     142        cache = GlobalSchemaCache.get( self.context, flavor_names ) 
    62143        if cache is not None: 
     144            if DEBUG_CACHE: CacheStats.hitGlobal( flavor_names ) 
    63145            schema, method_map = cache 
    64146            self.context.__dict__.update( method_map ) 
    65147            self.context._v_schema = schema 
    66148            return 
    67  
     149        else: 
     150            if DEBUG_CACHE: CacheStats.missGlobal( flavor_names ) 
    68151        # okay compose the schema from class and flavors 
    69152        schema = FlavorComposedSchema() 
     
    88171 
    89172        # stuff in cache 
    90         GlobalSchemaCache.set( self.context, provider.flavor_names, # key 
     173        GlobalSchemaCache.set( self.context, flavor_names, # key 
    91174                               schema, method_map ) 
    92175         
     
    133216# copies. instances now keep a pointer to the same schema for quick 
    134217# access. 
     218# 
     219# TODO.. update on tls semantics, we don't need them. the value being set 
     220# will generate the same semantic value (composed schema, method map) 
     221# regardless of construction context, though different python references, 
     222# ie. loss of the value on key overwrite race is fine. 
    135223 
    136224class TLSContext( object ): 
     
    146234    cache = property( getCache ) 
    147235 
     236class GlobalContext( object ): 
     237    cache = {} 
     238 
    148239class SchemaCache( object ): 
    149240 
    150241    def __init__(self): 
    151         self.storage = TLSContext() 
     242        # self.storage = TLSContext() - no race condition  
     243        self.storage = GlobalContext() # i think its safe since the value is context independent on construction and use 
    152244         
    153245    def get( self, object, flavor_names ): 
  • Products.ContentFlavors/trunk/tests/example.py

    r1682 r1683  
     1""" 
     2$Id$ 
     3""" 
     4 
    15from Products.ContentFlavors.aware import FlavorAware 
    26from Products.Archetypes import atapi 
    3  
    47from zope.interface import Interface 
    58 
    6                 
    7  
    89class ExampleContent( FlavorAware ): 
    9  
     10    """ 
     11    I am an example content classs 
     12    """ 
    1013    def getMealTypes(self): 
    1114        return ("Meat","Veggie","Mud") 
    1215 
     16class IMealContent( Interface ): 
     17    """ 
     18    i am interface describing content about food 
     19    """ 
    1320 
    14 class IMealContent( Interface ): 
    15     """ i am content about food 
    16     """ 
    1721 
    1822MealSchema = atapi.Schema(( 
Note: See TracChangeset for help on using the changeset viewer.