Package oort :: Module sitebase
[hide private]
[frames] | no frames]

Source Code for Module oort.sitebase

  1  # -*- coding: UTF-8 -*- 
  2  """This module contains everything needed to setup an Oort-based web  
  3  application. It also imports relevant parts from ``oort.display``. 
  4  """ 
  5  #======================================================================= 
  6  import urllib 
  7  import logging 
  8  from pkg_resources import EntryPoint 
  9  from rdflib import URIRef 
 10  from paste.wsgiwrappers import WSGIRequest, WSGIResponse 
 11  from paste.httpexceptions import HTTPTemporaryRedirect 
 12  from oort.display import DisplayBase, Display, SubTypeAwareDisplay 
 13  from oort.display import AspectBase, Aspect, TpltAspect, JsonAspect, RdfAspect 
 14  from oort.rdfview import RdfQuery 
 15  from oort.util.code import SlotStruct, contract, autosuper 
 16  __metaclass__ = autosuper 
 17  #======================================================================= 
 18  # FIXME: really, fix the "variant" concept.. 
 19  # TODO: option to give ignored suffix for e.g. downloading..? 
 20  # TODO: Work on SubTypeAwareDisplay (partially done; consider using "poorMansInference") 
 21  #   - or <http://dev.w3.org/cvsweb/~checkout~/2004/PythonLib-IH/RDFSClosure.py> 
 22   
 23   
24 -class resource_viewer_meta(autosuper):
25 - def __init__(cls, clsName, bases, clsDict):
26 autosuper.__init__(cls, clsName, bases, clsDict) 27 displayClasses = clsDict.setdefault('displayClasses', {}) 28 defaultDisplay = clsDict.get('defaultDisplay') 29 for member in clsDict.values(): 30 if isinstance(member, type) and issubclass(member, DisplayBase): 31 name = getattr(member, 'name', None) 32 if not name: 33 continue 34 displayClasses[name] = member 35 if not defaultDisplay and getattr(member, 'default', False): 36 defaultDisplay = name 37 cls.displayClasses = displayClasses 38 cls.defaultDisplay = defaultDisplay
39 40
41 -class ResourceViewerBase:
42 """Base class - not for direct usage.""" 43 __metaclass__ = resource_viewer_meta 44 45 @classmethod
46 - def app_factory(cls, global_conf=None, 47 graph_factory=None, **cfg):
48 """Factory method compatible with the app_factory feature of Paste Deploy.""" 49 mk_graph = EntryPoint.parse("x=%s" % graph_factory).load(False) 50 graph = mk_graph() 51 return cls(graph, cfg)
52 53 VARIANT_SEP = '-' 54 RESOURCE_QUERY_KEY = 'resource' 55 56 #defaultDisplay = None 57 langOrder = () 58
59 - def __init__(self, graph, cfg={}):
60 self.graph = graph 61 self._logger = logging.getLogger("oort.sitebase.ResourceViewerBase <%s>" % id(self)) 62 configure_logging(self._logger, cfg) 63 self.displays = dict((name, dispCls(cfg=cfg)) 64 for name, dispCls in self.displayClasses.items())
65
66 - def __call__(self, environ, start_response):
67 req = WSGIRequest(environ) 68 69 self._viewPathTemplate = req.script_name + '/%s/%s/' 70 args = req.path_info.split('/') 71 try: 72 args.pop(0) # remove first '' 73 lang = args.pop(0) 74 displayArg = args.pop(0) 75 except IndexError: 76 lang, displayArg = None, None 77 if not displayArg: 78 return self._default_redirect(lang, environ, start_response) 79 80 uri = req.GET.get(self.RESOURCE_QUERY_KEY) or self.resource_from(args) 81 82 get_result, contentType = self.prepared_display( 83 URIRef(uri), lang, displayArg, req) 84 85 headerMap = {} 86 if contentType: 87 headerMap['Content-Type'] = contentType 88 89 start_response('200 OK', headerMap.items()) 90 return get_result(self.graph)
91
92 - def _default_redirect(self, lang, environ, start_response):
93 redirect = HTTPTemporaryRedirect( 94 self.app_url_for(lang or self.langOrder[0], self.defaultDisplay)) 95 return redirect(environ, start_response)
96 97 @contract.template_method
98 - def resource_from(self, pathArgs):
99 raise NotImplementedError
100 101 @contract.template_method
102 - def resource_to_app_path(self, resource):
103 raise NotImplementedError
104
105 - def app_url_for(self, lang, displayArg, resource=None, fallback=True):
106 urlBase = self._viewPathTemplate % (lang, displayArg) 107 if resource: 108 resource, success = self.resource_to_app_path(resource) 109 if success: 110 return urlBase + resource.replace('#', '%23') 111 elif fallback: 112 return '%s?%s=%s' % (urlBase, self.RESOURCE_QUERY_KEY, 113 urllib.quote(resource)) 114 else: 115 return None # TODO: raise NoAppUrlForResourceError(resource) 116 else: 117 return urlBase
118
119 - def prepared_display(self, resource, lang, displayArg, req):
120 args = displayArg.split(self.VARIANT_SEP, 1) 121 name, variantArg = args[0], len(args) > 1 and args[1] 122 display = self.displays[name] 123 current = Current(resource, lang) 124 appUtil = AppUtil(current, displayArg, self, req) 125 126 self._logger.debug("Prepared display with: %s %s %s" 127 % (current, appUtil, display )) 128 # TODO: use __repr__? 129 130 def get_result(graph): 131 return display.get_result_generator(graph, appUtil, variantArg)
132 133 return get_result, display.contentType
134 135 136 LOG_LEVEL_NAMES = 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL' 137
138 -def configure_logging(logger, cfg):
139 levelName = cfg.get('logging_level', 'warning').upper() 140 if levelName in LOG_LEVEL_NAMES: 141 level = getattr(logging, levelName) 142 logger.setLevel(level) 143 # format, filename, filemode .. 144 handler = logging.StreamHandler() 145 logger.addHandler(handler)
146 147
148 -class Current(SlotStruct):
149 """Representation of the current *resource* request.""" 150 __slots__ = 'resource', 'lang'
151
152 -class AppUtil(SlotStruct):
153 """"An instance of this class is provided to the templates under the name 154 'app'. It provides a controlled interface to the application and the 155 current state (request).""" 156 157 __slots__ = 'current', '_displayArg', '_viewer', '_req' 158
159 - def queries(self, queries):
160 for query in queries: 161 yield query(self._viewer.graph, self.current.resource, self.current.lang)
162 187 190 191 @property
192 - def baseurl(self):
193 """Returns the base URL of this web app.""" 194 return self._req.script_name
195 196
197 -class ResourceViewer(ResourceViewerBase):
198 """Use for simple apps where all URIs in the graph share the same base URI 199 (set ``resourceBase`` to that).""" 200 resourceBase =None 201 trailSep='/' 202
203 - def __init__(self, *args):
204 self.__super.__init__(*args) 205 self._uriBase = self.resourceBase + "%s"
206
207 - def resource_from(self, pathArgs):
208 return self._uriBase % self.trailSep.join(pathArgs)
209
210 - def resource_to_app_path(self, resource):
211 base = self.resourceBase 212 if resource.find(base) == 0: 213 return str(resource)[len(base):], True 214 else: 215 return resource, False
216 217
218 -class MultiBaseResourceViewer(ResourceViewerBase):
219 """Use for full-fledged apps where many different base URIs will be 220 "mounted" at different named bases (done with the dictionary 221 ``resourceBases``).""""" 222 resourceBases = {} 223 defaultResource = '' 224 # TODO: skip defaultResource and use defaultResourceBase? 225 # TODO: if bad baseKey, raise Exception instead? 226
227 - def resource_from(self, pathArgs):
228 if not pathArgs: 229 return self.defaultResource 230 pathArgs = list(pathArgs) 231 baseKey = pathArgs.pop(0) 232 lookup = self.resourceBases.get(baseKey) 233 if not lookup: 234 return self.defaultResource 235 uriBase, trailSep = lookup 236 return uriBase + trailSep.join(pathArgs)
237
238 - def resource_to_app_path(self, resource):
239 for baseKey, (base, trailSep) in self.resourceBases.items(): 240 if resource.find(base) == 0: 241 respath = str(resource)[len(base):] 242 if trailSep: 243 respath = respath.replace(trailSep, '/') 244 lpath = "%s/%s" % (baseKey, respath) 245 return lpath, True 246 return resource, False
247