Skip to content

Commit

Permalink
making code insertion possible
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Davies committed Dec 18, 2018
1 parent 3b478ea commit 2b2c180
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 13 deletions.
8 changes: 8 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,12 @@ models:
type: foreignkey
to: author

methods:
- path: say-hello/<string:arg1>
resource: HelloResource
file: hello.py
swagger:
get:
summary: a summary

users:
2 changes: 1 addition & 1 deletion crudcast/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = '0.1.0a7'
__version__ = '0.1.0a8'

10 changes: 8 additions & 2 deletions crudcast/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


def get_api(app):
base_path = app.crudcast_config['swagger']['basePath']
api = Api(app, doc=False)

mr = ModelResource
Expand All @@ -11,8 +12,13 @@ def get_api(app):
ir = InstanceResource
ir.set_app(app)

api.add_resource(mr, '%s/<string:model_name>/' % app.crudcast_config['swagger']['basePath'])
api.add_resource(ir, '%s/<string:model_name>/<string:_id>/' % app.crudcast_config['swagger']['basePath'])
api.add_resource(mr, '%s/<string:model_name>/' % base_path)
api.add_resource(ir, '%s/<string:model_name>/<string:_id>/' % base_path)

for path, method in app.methods.items():
resource = method.get_resource()
resource.set_app(app)
api.add_resource(resource, '%s/%s' % (base_path, path))

if app.user_manager:
umr = UserModelResource
Expand Down
13 changes: 11 additions & 2 deletions crudcast/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from flask_swagger_ui import get_swaggerui_blueprint
from crudcast.models import Model
from crudcast.users import User
from crudcast.methods import Method
import os


class CrudcastApp(Flask):
Expand Down Expand Up @@ -35,6 +37,7 @@ class CrudcastApp(Flask):
} #: this is the default crudcast config. Any options supplied in your `config.yml` will overwrite these

models = {}
methods = {}
user_config = {
'fields': {},
'options': {
Expand All @@ -55,7 +58,7 @@ def set_crudcast_config(self, config_file):
options = load(f.read())

for key, val in options.items():
if key != 'models':
if key not in ['models', 'methods']:
self.crudcast_config[key] = val

self.client = pymongo.MongoClient(self.crudcast_config['mongo_url'])
Expand Down Expand Up @@ -87,6 +90,10 @@ def set_crudcast_config(self, config_file):
'options': self.user_config['options']
}

for method in options.get('methods', []):
file = os.path.abspath(method.pop('file'))
self.methods[method['path']] = Method(file=file, **method)

def get_tag(self, model):
"""
Returns the tag name/description to be used in the swagger view for each model
Expand Down Expand Up @@ -308,14 +315,16 @@ def swagger_config(self):
paths['/%s/{_id}/' % model_name] = self.get_instance_path(model)
definitions[model.name] = self.get_definition(model)

for method_path, method in self.methods.items():
paths['/' + method_path] = method.swagger_definition

config['tags'] = tags
config['paths'] = paths
config['definitions'] = definitions

if self.user_manager:
config['securityDefinitions'] = self.get_security_definitions()

print(config)
return config

def get_swagger_ui_view(self):
Expand Down
67 changes: 67 additions & 0 deletions crudcast/methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import importlib.util


class Method(object):
def __init__(self, file, path, resource, **options):
self.file = file
self.path = path
self.code = resource
self.options = options

def get_resource(self):
_cls = self.code.split('.')[-1]
spec = importlib.util.spec_from_file_location(self.code, self.file)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return getattr(module, _cls)

@property
def swagger_definition(self):
extra_swagger = self.options.get('swagger', {})

res = self.get_resource()
auth_type = self.options.get('auth_type')
swagger = {}

get = getattr(res, 'get', None)
post = getattr(res, 'post', None)
put = getattr(res, 'put', None)
delete = getattr(res, 'delete', None)

obj = {
'tags': [self.code],
'summary': 'Custom function',
'consumes': 'application/json',
'produces': 'application/json',
'responses': {
'200': {
'description': 'successful operation',
}
},
'security': [
{
'basicAuth': []
}
] if auth_type else []
}

if get:
swagger['get'] = obj
for key, val in extra_swagger.get('get', {}).items():
obj[key] = val
if post:
swagger['post'] = obj
for key, val in extra_swagger.get('post', {}).items():
obj[key] = val
if put:
swagger['put'] = obj
for key, val in extra_swagger.get('put', {}).items():
obj[key] = val
if delete:
swagger['delete'] = obj
for key, val in extra_swagger.get('delete', {}).items():
obj[key] = val

return swagger


18 changes: 14 additions & 4 deletions crudcast/mocks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from crudcast.resources import Resource


class MockApi(object):
pass

Expand All @@ -17,10 +20,16 @@ def __init__(self, *args, **kwargs):
self.count = kwargs.get('count', 0)


class MockMethod(object):
def __init__(self, *args, **kwargs):
pass


class MockApp(object):
user_manager = None
handle_exception = None
handle_user_exception = None
methods = {}

def run(self, *args, **kwargs):
pass
Expand Down Expand Up @@ -93,16 +102,12 @@ def __iter__(self):
for item in self.items():
yield item


def count(self):
return self._count

inserted_id = '507f1f77bcf86cd799439011'





class MockModel(object):
models = ['test', 'test2']
count = 0
Expand Down Expand Up @@ -134,3 +139,8 @@ def __getitem__(self, item):

def __init__(self, *args, **kwargs):
pass


class TestResource(Resource):
def get(self):
return {'hello': True}
1 change: 0 additions & 1 deletion crudcast/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class Resource(BaseResource):

def check_auth(self):
auth_type = self.model.get_auth_type()
print(self.model)
if auth_type:
return auth_type.authenticate(request=request, user=self.app.user_manager)

Expand Down
16 changes: 13 additions & 3 deletions crudcast/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from api import get_api
from app import CrudcastApp
import json
from argparse import ArgumentError
from datetime import datetime


Expand Down Expand Up @@ -45,7 +44,7 @@ def test_app(self, *args):
self.assertEqual(app.swagger_config['swagger'], '2.0')

with mock.patch('flask_swagger_ui.get_swaggerui_blueprint'):
print(app.get_swagger_ui_view())
app.get_swagger_ui_view()

@mock.patch('crudcast.app.CrudcastApp', MockApp)
@mock.patch('argparse.ArgumentParser', MockParser)
Expand All @@ -57,7 +56,7 @@ def test_entrypoint(self, *args):
def test_exceptions(self, *args,):
from crudcast.exceptions import handle_invalid_usage, ValidationError
with self.assertRaises(RuntimeError):
print(handle_invalid_usage(ValidationError('test', status_code=415)))
handle_invalid_usage(ValidationError('test', status_code=415))

def test_fields(self):
f = BaseField(name='test', model=MockModel(), unique=True)
Expand Down Expand Up @@ -141,3 +140,14 @@ def test_model(self):

with self.assertRaises(Exception):
model.update(_id='507f1f77bcf86cd799439011', data={})

def test_methods(self):
from crudcast.methods import Method
from crudcast.resources import Resource
import os
root = os.path.abspath(os.path.join(__file__, os.pardir))
file = os.path.join(root, 'mocks.py')

method = Method(file, '/test/<string:arg1>', 'mocks.TestResource')
_cls = method.get_resource()
self.assertTrue(issubclass(_cls, Resource))
96 changes: 96 additions & 0 deletions docs/source/extra_methods.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
Adding additional methods to your API
-------------------------------------

You can very easily extend your Crudcast project by creating additional API methods from your own code. This is done
by adding a `methods:` section to your config file

.. code-block:: yaml
methods:
- path: say-hello/<string:arg1>
resource: HelloResource
file: hello.py
The above will look for a class called `HelloResource` in the file `hello.py`, and insert this method into your API
at the provided path

.. note::
The `file` attribute can be relative, as shown above, but its generally best to provide the absolute path to this
file, especially if you're using a custom config file location

Writing methods
---------------

Your custom resources must subclass the `crudcast.resources.Resource` class, which itself extends the
`flask_restplus.Resource` object.

.. code-block:: python
from crudcast.resources import Resource
class HelloResource(Resource):
def get(self, arg1):
return {'hello': arg1}
Request types
-------------

You can also specify multiple method types in the same resource, as follows:

.. code-block:: python
from crudcast.resources import Resource
from flask import request
class HelloResource(Resource):
def get(self, arg1):
return {'hello': arg1}
def post(self, arg1):
# do something
return request.json
Additionally, it is also possible to modify data in your MongoDB collections in your own methods, by using the
`Resource` object's `app` property. For example:

.. code-block:: yaml
book:
fields:
name:
published:
type: boolean
methods:
- path: books/<string:_id>/publish
resource: MyBooks
file: books.py
.. code-block:: python
from crudcast.resources import Resource
from pymongo.collection import ObjectId
class MyBooks(Resource):
def get(self, _id):
collection = self.app.db['books']
collection.find_one_and_update({'_id': ObjectId(_id)}, {'$set': {'published': True}})
return {'updated': True}
Swagger
-------

As with other methods, your own custom methods will be added to the swagger view. You can modify the information
displayed there by adding a `swagger:` property to the config for your method:

.. code-block:: yaml
methods:
- path: books/<string:_id>/publish
resource: MyBooks
file: books.py
swagger:
summary: Mark a book as published
26 changes: 26 additions & 0 deletions docs/source/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,29 @@ The above will ensure that you must be authenticated using Basic authorization i

.. note::
More auth types will be added in future versions of crudcast

Adding custom methods
---------------------

It's also possible to create API methods with your own custom code - methods can be defined as follows:

.. code-block:: yaml
methods:
- path: say-hello/<string:arg1>
resource: HelloResource
file: hello.py
`hello.py`:

.. code-block:: python
from crudcast.resources import Resource
class HelloResource(Resource):
def get(self, arg1):
return {'hello': arg1}
The above will create an additional route as `basePath/say-hello/{arg1}` which returns a simple response. For more
help on this function, see `extra_methods`_

0 comments on commit 2b2c180

Please sign in to comment.