Commit 96e1bec4 authored by Gregory Armer's avatar Gregory Armer

Merge pull request #8 from ashish-gore/modelcache

Dynamic Data-API using introspection (Hackathon)
parents 81696be1 39dd5724
"""
API Builder
Build dynamic API based on the provided SQLAlchemy model
"""
from managers import AlchemyModelManager
from viewsets import AlchemyModelViewSet
from rest_framework_nested import routers
class APIModelBuilder(object):
def __init__(self,
models,
base_managers,
base_viewsets=None,
*args, **kwargs):
self.models = models
if not isinstance(base_managers, (tuple, list)):
base_managers = [base_managers]
if not isinstance(base_managers, tuple):
base_managers = tuple(base_managers)
if base_viewsets is None:
base_viewsets = (AlchemyModelViewSet,)
self.base_managers = base_managers
self.base_viewsets = base_viewsets
@property
def urls(self):
router = routers.SimpleRouter()
for model in self.models:
manager = type(
str('{}Manager'.format(model.__name__)),
self.base_managers + (AlchemyModelManager,),
{
'model_class': model,
}
)
viewset = type(
str('{}ModelViewSet'.format(model.__name__)),
self.base_viewsets,
{
'manager_class': manager,
'paginate_by': 10,
}
)
router.register('data-api/' + model.__name__.lower() + 's',
viewset,
base_name=model.__name__)
return router.urls
import importlib
import inspect
import itertools
import os
import six
from django.conf import settings
def module_walk(root_module, include_self=True):
if isinstance(root_module, six.string_types):
root_module = importlib.import_module(root_module)
if include_self:
yield root_module
root_path = os.path.abspath(root_module.__file__)
if '__init__' not in os.path.basename(root_path):
return
root_path = os.path.dirname(root_path)
for path, dirs, files in os.walk(root_path):
path = os.path.abspath(path)
for file in files:
if not file.rsplit('.', 1)[-1] == 'py':
continue
if file == '__init__.py':
file = ''
file = os.path.join(path, file)
file = file.replace(root_path + os.sep, '')
file = file.rsplit('.', 1)[0]
if '.' in file:
continue
module_path = '.'.join(filter(
None,
root_module.__name__.split('.')
+ file.split(os.sep)
))
module = importlib.import_module(module_path)
yield module
class ModelCache(object):
"""
Store for all the models in the project
Based on Django's AppCache, this collects all the
models in the application for introspection.
Future:
There will be a settings.py file which will allow
customization
"""
__shared_state = dict(
modules={},
)
def __init__(self):
self.__dict__ = self.__shared_state
@property
def models(self):
if hasattr(self, '_models'):
return self._models
self._models = list(set(itertools.chain(
*self.modules.values()
)))
return self._models
def find_models_in_module(self, module_path):
for module in module_walk(module_path, include_self=True):
models = self._get_models_from_module(module)
if models:
self.modules[module.__name__] = models
def _get_models_from_module(self, module):
models = [
obj for name, obj in vars(module).items()
if (inspect.isclass(obj)
and hasattr(obj, '_sa_class_manager'))
]
return models
model_cache = ModelCache()
for path in getattr(settings, 'SA_MODEL_LOADER', []):
model_cache.find_models_in_module(path)
import unittest
from djangorest_alchemy.apibuilder import APIModelBuilder
import mock
class TestAPIBuilder(unittest.TestCase):
def test_urls(self):
"""
Test basic urls property
"""
class Model(object):
pass
class Model2(object):
pass
class SessionMixin(object):
def __init__(self):
self.session = mock.Mock()
builder = APIModelBuilder([Model, Model2], SessionMixin)
self.assertIsNotNone(builder.urls)
......@@ -79,7 +79,6 @@ class AlchemyModelViewSet(MultipleObjectMixin, ManagerMixin, viewsets.ViewSet):
* URI contains the same pk field
* Complete URI with server/port is returned back
'''
mgr = self.manager_factory(context={'request': request})
queryset = mgr.list(other_pks=self.get_other_pks(request),
......
......@@ -3,6 +3,8 @@ import os
import sys
if __name__ == "__main__":
sys.path.append(os.path.abspath(os.path.join(__file__, '..')))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
from django.core.management import execute_from_command_line
......
......@@ -123,6 +123,8 @@ INSTALLED_APPS = (
# 'django.contrib.admindocs',
)
SA_MODEL_LOADER = ('models',)
# DJANGO REST FRAMEWORK SETTINGS
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
......
......@@ -3,10 +3,15 @@ Url registrations
'''
from django.conf.urls import patterns, include, url
from rest_framework_nested import routers
from djangorest_alchemy.apibuilder import APIModelBuilder
from djangorest_alchemy.managers import AlchemyModelManager
from viewsets import CarViewSet
from viewsets import PartViewSet
from models import SessionMixin
viewset_router = routers.SimpleRouter()
viewset_router.register(r'api/cars', CarViewSet,
base_name='car')
......@@ -17,7 +22,14 @@ child_router = routers.NestedSimpleRouter(viewset_router, r'api/cars',
child_router.register("parts", PartViewSet,
base_name='part')
# Demonstrate dynamic API builder featire
from djangorest_alchemy.model_cache import model_cache
builder = APIModelBuilder(model_cache.models,
(SessionMixin, AlchemyModelManager))
urlpatterns = patterns('',
url(r'^', include(viewset_router.urls)),
url(r'^', include(child_router.urls)),
url(r'^', include(builder.urls)),
)
......@@ -19,7 +19,7 @@ history = open('HISTORY.rst').read().replace('.. :changelog:', '')
setup(
name='djangorest-alchemy',
version='0.1.1',
version='0.1.2',
description='Django REST Framework and SQLAlchemy integration',
long_description=readme + '\n\n' + history,
author='Ashish Gore',
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment