Commits

Ionel Maries Cristian committed 882d384 Merge

Merge with upstream changes.

Comments (0)

Files changed (7)

piston/__init__.py

+try:
+    import pkg_resources
+    pkg_resources.declare_namespace(__name__)
+except ImportError:
+    # don't prevent use of paste if pkg_resources isn't installed
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__) 
+
+try:
+    import modulefinder
+except ImportError:
+    pass
+else:
+    for p in __path__:
+        modulefinder.AddPackagePath(__name__, p)
     for the given handler. Use this to generate
     documentation for your API.
     """
-    if not type(handler_cls) is handler.HandlerMetaClass:
+    if isinstance(type(handler_cls), handler.HandlerMetaClass):
         raise ValueError("Give me handler, not %s" % type(handler_cls))
         
     return HandlerDocumentation(handler_cls)
             if not met:
                 continue
                 
-            stale = inspect.getmodule(met) is handler
+            stale = inspect.getmodule(met.im_func) is not inspect.getmodule(self.handler)
 
             if not self.handler.is_anonymous:
                 if met and (not stale or include_default):

piston/emitters.py

     as the methods on the handler. Issue58 says that's no good.
     """
     EMITTERS = { }
-    RESERVED_FIELDS = set([ 'read', 'update', 'create', 
+    RESERVED_FIELDS = set([ 'read', 'update', 'create',
                             'delete', 'model', 'anonymous',
                             'allowed_methods', 'fields', 'exclude' ])
 
         self.handler = handler
         self.fields = fields
         self.anonymous = anonymous
-        
+
         if isinstance(self.data, Exception):
             raise
-    
+
     def method_fields(self, handler, fields):
         if not handler:
             return { }
 
         ret = dict()
-            
+
         for field in fields - Emitter.RESERVED_FIELDS:
             t = getattr(handler, str(field), None)
 
                 ret[field] = t
 
         return ret
-    
+
     def construct(self):
         """
         Recursively serialize a lot of types, and
         in cases where it doesn't recognize the type,
         it will fall back to Django's `smart_unicode`.
-        
+
         Returns `dict`.
         """
         def _any(thing, fields=()):
             Dispatch, all types are routed through here.
             """
             ret = None
-            
+
             if isinstance(thing, QuerySet):
                 ret = _qs(thing, fields=fields)
-            elif isinstance(thing, (tuple, list)):
-                ret = _list(thing)
+            elif isinstance(thing, (tuple, list, set)):
+                ret = _list(thing, fields=fields)
             elif isinstance(thing, dict):
-                ret = _dict(thing)
+                ret = _dict(thing, fields)
             elif isinstance(thing, decimal.Decimal):
                 ret = str(thing)
             elif isinstance(thing, Model):
             Foreign keys.
             """
             return _any(getattr(data, field.name))
-        
+
         def _related(data, fields=()):
             """
             Foreign keys.
             """
             return [ _model(m, fields) for m in data.iterator() ]
-        
+
         def _m2m(data, field, fields=()):
             """
             Many to many (re-route to `_model`.)
             """
             return [ _model(m, fields) for m in getattr(data, field.name).iterator() ]
-        
+
         def _model(data, fields=()):
             """
             Models. Will respect the `fields` and/or
             ret = { }
             handler = self.in_typemapper(type(data), self.anonymous)
             get_absolute_uri = False
-            
+
             if handler or fields:
                 v = lambda f: getattr(data, f.attname)
 
 
                     if 'absolute_uri' in get_fields:
                         get_absolute_uri = True
-                
+
                     if not get_fields:
                         get_fields = set([ f.attname.replace("_id", "", 1)
-                            for f in data._meta.fields ])
-                
+                            for f in data._meta.fields + data._meta.virtual_fields])
+                    
+                    if hasattr(mapped, 'extra_fields'):
+                        get_fields.update(mapped.extra_fields)
+
                     # sets can be negated.
                     for exclude in exclude_fields:
                         if isinstance(exclude, basestring):
                             get_fields.discard(exclude)
-                            
+
                         elif isinstance(exclude, re._pattern_type):
                             for field in get_fields.copy():
                                 if exclude.match(field):
                                     get_fields.discard(field)
-                                    
+
                 else:
                     get_fields = set(fields)
 
                 met_fields = self.method_fields(handler, get_fields)
-                           
-                for f in data._meta.local_fields:
+
+                for f in data._meta.local_fields + data._meta.virtual_fields:
                     if f.serialize and not any([ p in met_fields for p in [ f.attname, f.name ]]):
                         if not f.rel:
                             if f.attname in get_fields:
                             if f.attname[:-3] in get_fields:
                                 ret[f.name] = _fk(data, f)
                                 get_fields.remove(f.name)
-                
+
                 for mf in data._meta.many_to_many:
                     if mf.serialize and mf.attname not in met_fields:
                         if mf.attname in get_fields:
                             ret[mf.name] = _m2m(data, mf)
                             get_fields.remove(mf.name)
-                
+
                 # try to get the remainder of fields
                 for maybe_field in get_fields:
                     if isinstance(maybe_field, (list, tuple)):
                         # using different names.
                         ret[maybe_field] = _any(met_fields[maybe_field](data))
 
-                    else:                    
+                    else:
                         maybe = getattr(data, maybe_field, None)
                         if maybe:
                             if callable(maybe):
             else:
                 for f in data._meta.fields:
                     ret[f.attname] = _any(getattr(data, f.attname))
-                
+
                 fields = dir(data.__class__) + ret.keys()
                 add_ons = [k for k in dir(data) if k not in fields]
-                
+
                 for k in add_ons:
                     ret[k] = _any(getattr(data, k))
-            
+
             # resouce uri
             if self.in_typemapper(type(data), self.anonymous):
                 handler = self.in_typemapper(type(data), self.anonymous)
                         ret['resource_uri'] = reverser( lambda: (url_id, fields) )()
                     except NoReverseMatch, e:
                         pass
-            
+
             if hasattr(data, 'get_api_url') and 'resource_uri' not in ret:
                 try: ret['resource_uri'] = data.get_api_url()
                 except: pass
-            
+
             # absolute uri
             if hasattr(data, 'get_absolute_url') and get_absolute_uri:
                 try: ret['absolute_uri'] = data.get_absolute_url()
                 except: pass
-            
+
             return ret
-        
+
         def _qs(data, fields=()):
             """
             Querysets.
             """
             return [ _any(v, fields) for v in data ]
-                
-        def _list(data):
+
+        def _list(data, fields=()):
             """
             Lists.
             """
-            return [ _any(v) for v in data ]
-            
-        def _dict(data):
+            return [ _any(v, fields) for v in data ]
+
+        def _dict(data, fields):
             """
             Dictionaries.
             """
-            return dict([ (k, _any(v)) for k, v in data.iteritems() ])
-            
+            return dict([ (k, _any(v, fields)) for k, v in data.iteritems() ])
+
         # Kickstart the seralizin'.
         return _any(self.data, self.fields)
-    
+
     def in_typemapper(self, model, anonymous):
         for klass, (km, is_anon) in self.typemapper.iteritems():
             if model is km and is_anon is anonymous:
                 return klass
-        
+
     def render(self):
         """
         This super emitter does not implement `render`,
         this is a job for the specific emitter below.
         """
         raise NotImplementedError("Please implement render.")
-        
+
     def stream_render(self, request, stream=True):
         """
         Tells our patched middleware not to look
         more memory friendly for large datasets.
         """
         yield self.render(request)
-        
+
     @classmethod
     def get(cls, format):
         """
             return cls.EMITTERS.get(format)
 
         raise ValueError("No emitters found for type %s" % format)
-    
+
     @classmethod
     def register(cls, name, klass, content_type='text/plain'):
         """
         Register an emitter.
-        
+
         Parameters::
          - `name`: The name of the emitter ('json', 'xml', 'yaml', ...)
          - `klass`: The emitter class.
          - `content_type`: The content type to serve response as.
         """
         cls.EMITTERS[name] = (klass, content_type)
-        
+
     @classmethod
     def unregister(cls, name):
         """
         want to provide output in one of the built-in emitters.
         """
         return cls.EMITTERS.pop(name, None)
-    
+
 class XMLEmitter(Emitter):
     def _to_xml(self, xml, data):
         if isinstance(data, (list, tuple)):
 
     def render(self, request):
         stream = StringIO.StringIO()
-        
+
         xml = SimplerXMLGenerator(stream, "utf-8")
         xml.startDocument()
         xml.startElement("response", {})
-        
+
         self._to_xml(xml, self.construct())
-        
+
         xml.endElement("response")
         xml.endDocument()
-        
+
         return stream.getvalue()
 
 Emitter.register('xml', XMLEmitter, 'text/xml; charset=utf-8')
             return '%s(%s)' % (cb, seria)
 
         return seria
-    
+
 Emitter.register('json', JSONEmitter, 'application/json; charset=utf-8')
 Mimer.register(simplejson.loads, ('application/json',))
-    
+
 class YAMLEmitter(Emitter):
     """
     YAML emitter, uses `safe_dump` to omit the
     """
     def render(self, request):
         return pickle.dumps(self.construct())
-        
+
 Emitter.register('pickle', PickleEmitter, 'application/python-pickle')
 
 """
             response = serializers.serialize(format, self.data, indent=True)
 
         return response
-        
+
 Emitter.register('django', DjangoEmitter, 'text/xml; charset=utf-8')

piston/handlers_doc.py

+from piston.doc import generate_doc
+from piston.handler import handler_tracker
+import re
+
+def generate_piston_documentation(app, docname, source):
+    e = re.compile(r"^\.\. piston_handlers:: ([\w\.]+)$")
+    old_source = source[0].split("\n")
+    new_source = old_source[:]
+    for line_nr, line in enumerate(old_source):
+        m = e.match(line)
+        if m:
+            module = m.groups()[0]
+            try:
+                __import__(module)
+            except ImportError:
+                pass
+            else:
+                new_lines = []
+                for handler in handler_tracker:
+                    doc = generate_doc(handler)
+                    new_lines.append(doc.name)
+                    new_lines.append("-" * len(doc.name))
+                    new_lines.append('::\n')
+                    new_lines.append('\t' + doc.get_resource_uri_template() + '\n')
+                    new_lines.append('Accepted methods:')
+                    for method in doc.allowed_methods:
+                        new_lines.append('\t* ' + method)
+                    new_lines.append('')
+                    if doc.doc:
+                        new_lines.append(doc.doc)
+                new_source[line_nr:line_nr+1] = new_lines
+
+    source[0] = "\n".join(new_source)
+    return source
+
+def setup(app):
+    app.connect('source-read', generate_piston_documentation)

piston/resource.py

             result = self.error_handler(e, request, meth)
 
 
-        emitter, ct = Emitter.get(em_format)
-        fields = handler.fields
-        if hasattr(handler, 'list_fields') and (
-                isinstance(result, list) or isinstance(result, QuerySet)):
-            fields = handler.list_fields
+        try:
+            emitter, ct = Emitter.get(em_format)
+        except ValueError:
+            result = rc.BAD_REQUEST
+            result.content = "Invalid output format specified '%s'." % em_format
+            return result
+
+        try:
+            result, fields = result
+        except ValueError:
+            fields = handler.fields
+            if hasattr(handler, 'list_fields') and (
+                    isinstance(result, list) or isinstance(result, QuerySet)):
+                fields = handler.list_fields
 
         status_code = 200
 
 from django.core import mail
 from django.contrib.auth.models import User
 from django.conf import settings
+from django.template import loader, TemplateDoesNotExist
 from django.http import HttpRequest, HttpResponse
 from django.utils import simplejson
 
         self.consumer.user = User.objects.get(pk=3)
         self.consumer.generate_random_codes()
 
+    def _pre_test_email(self):
+        template = "piston/mails/consumer_%s.txt" % self.consumer.status
+        try:
+            loader.render_to_string(template, {
+                'consumer': self.consumer,
+                'user': self.consumer.user
+            })
+            return True
+        except TemplateDoesNotExist:
+            """
+            They haven't set up the templates, which means they might not want
+            these emails sent.
+            """
+            return False
+
     def test_create_pending(self):
         """ Ensure creating a pending Consumer sends proper emails """
-        # If it's pending we should have two messages in the outbox; one 
+        # Verify if the emails can be sent
+        if not self._pre_test_email():
+            return
+
+        # If it's pending we should have two messages in the outbox; one
         # to the consumer and one to the site admins.
         if len(settings.ADMINS):
             self.assertEquals(len(mail.outbox), 2)
         mail.outbox = []
 
         # Delete the consumer, which should fire off the cancel email.
-        self.consumer.delete() 
-        
+        self.consumer.delete()
+
+        # Verify if the emails can be sent
+        if not self._pre_test_email():
+            return
+
         self.assertEquals(len(mail.outbox), 1)
         expected = "Your API Consumer for example.com has been canceled."
         self.assertEquals(mail.outbox[0].subject, expected)
 
         self.assertTrue(isinstance(response, HttpResponse), "Expected a response, not: %s" 
             % response)
-
     author = 'Jesper Noehr',
     author_email = 'jesper@noehr.org',
     packages = find_packages(),
+    namespace_packages = ['piston'],
     include_package_data = True,
     zip_safe = False,
     classifiers = [
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.