Procházet zdrojové kódy

refactor view using hapic + add temporary example_api

Guénaël Muller před 7 roky
rodič
revize
2b41b3a7f4

+ 2 - 0
setup.py Zobrazit soubor

@@ -23,6 +23,8 @@ requires = [
23 23
     'filedepot',
24 24
     'babel',
25 25
     'alembic',
26
+    'hapic',
27
+    'marshmallow',
26 28
 ]
27 29
 
28 30
 tests_require = [

+ 35 - 7
tracim/__init__.py Zobrazit soubor

@@ -1,15 +1,43 @@
1 1
 # -*- coding: utf-8 -*-
2
+import json
3
+import time
4
+
2 5
 from pyramid.config import Configurator
6
+from hapic.ext.pyramid import PyramidContext
7
+
8
+from tracim.extensions import hapic
3 9
 from tracim.config import RequestWithCFG
10
+from tracim.views.example_api.example_api_controller import ExampleApiController
11
+from tracim.views.default.default_controller import DefaultController
4 12
 
5 13
 
6 14
 def main(global_config, **settings):
7 15
     """ This function returns a Pyramid WSGI application.
8 16
     """
9
-    config = Configurator(settings=settings)
10
-    config.include('pyramid_jinja2')
11
-    config.include('.models')
12
-    config.include('.routes')
13
-    config.set_request_factory(RequestWithCFG)
14
-    config.scan()
15
-    return config.make_wsgi_app()
17
+    configurator = Configurator(settings=settings, autocommit=True)
18
+    # Pyramids "plugin" include.
19
+    configurator.include('pyramid_jinja2')
20
+    # Add SqlAlchemy DB
21
+    configurator.include('.models')
22
+    # Override default request
23
+    configurator.set_request_factory(RequestWithCFG)
24
+    # set Hapic
25
+    hapic.set_context(PyramidContext(configurator))
26
+    # Add controllers
27
+    default_controllers = DefaultController()
28
+    default_controllers.bind(configurator)
29
+    example_api_controllers = ExampleApiController()
30
+    example_api_controllers.bind(configurator)
31
+
32
+    time.sleep(1)
33
+    s = json.dumps(
34
+        hapic.generate_doc(
35
+            title='Fake API',
36
+            description='just an example of hapic API'
37
+        )
38
+    )
39
+    time.sleep(1)
40
+    # print swagger doc
41
+    print(s)
42
+    time.sleep(1)
43
+    return configurator.make_wsgi_app()

+ 1 - 4
tracim/config.py Zobrazit soubor

@@ -3,18 +3,15 @@ from urllib.parse import urlparse
3 3
 from paste.deploy.converters import asbool
4 4
 from tracim.lib.utils.logger import logger
5 5
 from depot.manager import DepotManager
6
-
7 6
 from pyramid.request import Request
8 7
 
9
-
10 8
 class RequestWithCFG(Request):
11 9
 
12 10
     def app_config(self):
13 11
         cfg = CFG(self.registry.settings)
14
-        cfg.configure_filedepot()
12
+        #cfg.configure_filedepot()
15 13
         return cfg
16 14
 
17
-
18 15
 class CFG(object):
19 16
     """Object used for easy access to config file parameters."""
20 17
 

+ 3 - 0
tracim/extensions.py Zobrazit soubor

@@ -0,0 +1,3 @@
1
+from hapic import Hapic
2
+
3
+hapic = Hapic()

+ 0 - 4
tracim/routes.py Zobrazit soubor

@@ -1,4 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-def includeme(config):
3
-    config.add_static_view('static', 'static', cache_max_age=3600)
4
-    config.add_route('home', '/')

+ 7 - 0
tracim/views/controllers.py Zobrazit soubor

@@ -0,0 +1,7 @@
1
+from pyramid.config import Configurator
2
+
3
+
4
+class Controller(object):
5
+
6
+    def bind(self, configurator: Configurator):
7
+        raise NotImplementedError()

+ 0 - 41
tracim/views/default.py Zobrazit soubor

@@ -1,41 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-# TODO - G.M - 29-03-2018 - [Cleanup] Drop this file
3
-from pyramid.response import Response
4
-from pyramid.view import view_config
5
-
6
-from sqlalchemy.exc import DBAPIError
7
-from ..models import Content
8
-
9
-
10
-@view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
11
-def test_config(request):
12
-    try:
13
-        project = request.app_config().WEBSITE_TITLE
14
-    except Exception as e:
15
-        return Response(e, content_type='text/plain', status=500)
16
-    return {'project': project}
17
-
18
-# @view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
19
-# def my_view(request):
20
-#     try:#         query = request.dbsession.query(MyModel)
21
-#         one = query.filter(MyModel.name == 'one').first()
22
-#     except DBAPIError:
23
-#         return Response(db_err_msg, content_type='text/plain', status=500)
24
-#    return {'one': one, 'project': 'tracim'}
25
-
26
-
27
-db_err_msg = """\
28
-Pyramid is having a problem using your SQL database.  The problem
29
-might be caused by one of the following things:
30
-
31
-1.  You may need to run the "initialize_tracim_db" script
32
-    to initialize your database tables.  Check your virtual
33
-    environment's "bin" directory for this script and try to run it.
34
-
35
-2.  Your database server may not be running.  Check that the
36
-    database server referred to by the "sqlalchemy.url" setting in
37
-    your "development.ini" file is running.
38
-
39
-After you fix the problem, please restart the Pyramid application to
40
-try it again.
41
-"""

+ 0 - 0
tracim/views/default/__init__.py Zobrazit soubor


+ 36 - 0
tracim/views/default/default_controller.py Zobrazit soubor

@@ -0,0 +1,36 @@
1
+# coding=utf-8
2
+from tracim.views.controllers import Controller
3
+from pyramid.config import Configurator
4
+from pyramid.response import Response
5
+from pyramid.exceptions import NotFound
6
+
7
+
8
+class DefaultController(Controller):
9
+
10
+    @classmethod
11
+    def notfound_view(cls, request):
12
+        request.response.status = 404
13
+        return {}
14
+
15
+    @classmethod
16
+    def test_config(cls, request):
17
+        try:
18
+            project = request.app_config().WEBSITE_TITLE
19
+        except Exception as e:
20
+            return Response(e, content_type='text/plain', status=500)
21
+        return {'project': project}
22
+
23
+    def bind(self, configurator: Configurator):
24
+        configurator.add_static_view('static', 'static', cache_max_age=3600)
25
+        configurator.add_view(
26
+            self.notfound_view,
27
+            renderer='tracim:templates/404.jinja2',
28
+            context=NotFound,
29
+        )
30
+
31
+        configurator.add_route('test_config', '/')
32
+        configurator.add_view(
33
+            self.test_config,
34
+            route_name='test_config',
35
+            renderer='tracim:templates/mytemplate.jinja2',
36
+        )

+ 1 - 0
tracim/views/example_api/__init__.py Zobrazit soubor

@@ -0,0 +1 @@
1
+# -*- coding: utf-8 -*-

+ 109 - 0
tracim/views/example_api/example_api_controller.py Zobrazit soubor

@@ -0,0 +1,109 @@
1
+# -*- coding: utf-8 -*-
2
+from datetime import datetime
3
+
4
+from pyramid.config import Configurator
5
+
6
+from hapic.data import HapicData
7
+
8
+from tracim.extensions import hapic
9
+from tracim.views.controllers import Controller
10
+from tracim.views.example_api.schema import *
11
+
12
+
13
+class ExampleApiController(Controller):
14
+
15
+    @hapic.with_api_doc()
16
+    @hapic.output_body(AboutResponseSchema())
17
+    def about(self, context, request):
18
+        """
19
+        General information about this API.
20
+        """
21
+        return {
22
+            'version': '1.2.3',
23
+            'datetime': datetime(2017, 12, 7, 10, 55, 8, 488996),
24
+        }
25
+
26
+    @hapic.with_api_doc()
27
+    @hapic.output_body(ListsUserSchema())
28
+    def get_users(self, context, request):
29
+        """
30
+        Obtain users list.
31
+        """
32
+        return {
33
+            'item_nb': 1,
34
+            'items': [
35
+                {
36
+                    'id': 4,
37
+                    'username': 'some_user',
38
+                    'display_name': 'Damien Accorsi',
39
+                    'company': 'Algoo',
40
+                },
41
+            ],
42
+            'pagination': {
43
+                'first_id': 0,
44
+                'last_id': 5,
45
+                'current_id': 0,
46
+            }
47
+        }
48
+
49
+    @hapic.with_api_doc()
50
+    @hapic.input_path(UserPathSchema())
51
+    @hapic.output_body(UserSchema())
52
+    def get_user(self, context, request, hapic_data: HapicData):
53
+        """
54
+        Obtain one user
55
+        """
56
+        return {
57
+             'id': 4,
58
+             'username': 'some_user',
59
+             'email_address': 'some.user@hapic.com',
60
+             'first_name': 'Damien',
61
+             'last_name': 'Accorsi',
62
+             'display_name': 'Damien Accorsi',
63
+             'company': 'Algoo',
64
+        }
65
+
66
+    @hapic.with_api_doc()
67
+    # TODO - G.M - 2017-12-5 - Support input_forms ?
68
+    # TODO - G.M - 2017-12-5 - Support exclude, only ?
69
+    @hapic.input_body(UserSchema(exclude=('id',)))
70
+    @hapic.output_body(UserSchema())
71
+    def add_user(self, context, request, hapic_data: HapicData):
72
+        """
73
+        Add new user
74
+        """
75
+        return {
76
+             'id': 4,
77
+             'username': 'some_user',
78
+             'email_address': 'some.user@hapic.com',
79
+             'first_name': 'Damien',
80
+             'last_name': 'Accorsi',
81
+             'display_name': 'Damien Accorsi',
82
+             'company': 'Algoo',
83
+        }
84
+
85
+    @hapic.with_api_doc()
86
+    @hapic.output_body(NoContentSchema(),
87
+                       default_http_code=204)
88
+    @hapic.input_path(UserPathSchema())
89
+    def del_user(self, context, request, hapic_data: HapicData):
90
+        """
91
+        delete user
92
+        """
93
+        return NoContentSchema()
94
+
95
+    def bind(self, configurator: Configurator):
96
+        configurator.add_route('about', '/about', request_method='GET')
97
+        configurator.add_view(self.about, route_name='about', renderer='json')
98
+
99
+        configurator.add_route('get_users', '/users', request_method='GET')  # nopep8
100
+        configurator.add_view(self.get_users, route_name='get_users', renderer='json')  # nopep8
101
+
102
+        configurator.add_route('get_user', '/users/{id}', request_method='GET')  # nopep8
103
+        configurator.add_view(self.get_user, route_name='get_user', renderer='json')  # nopep8
104
+
105
+        configurator.add_route('add_user', '/users/', request_method='POST')  # nopep8
106
+        configurator.add_view(self.add_user, route_name='add_user', renderer='json')  # nopep8
107
+
108
+        configurator.add_route('del_user', '/users/{id}', request_method='DELETE')  # nopep8
109
+        configurator.add_view(self.del_user, route_name='del_user', renderer='json')  # nopep8

+ 56 - 0
tracim/views/example_api/schema.py Zobrazit soubor

@@ -0,0 +1,56 @@
1
+# -*- coding: utf-8 -*-
2
+import marshmallow
3
+
4
+
5
+class NoContentSchema(marshmallow.Schema):
6
+    pass
7
+
8
+
9
+class AboutResponseSchema(marshmallow.Schema):
10
+    version = marshmallow.fields.String(required=True,)
11
+    datetime = marshmallow.fields.DateTime(required=True)
12
+
13
+
14
+class UserPathSchema(marshmallow.Schema):
15
+    id = marshmallow.fields.Int(
16
+        required=True,
17
+        validate=marshmallow.validate.Range(min=1),
18
+    )
19
+
20
+
21
+class UserSchema(marshmallow.Schema):
22
+    id = marshmallow.fields.Int(required=True)
23
+    username = marshmallow.fields.String(
24
+        required=True,
25
+        validate = marshmallow.validate.Regexp(regex='[\w-]+'),
26
+    )
27
+    email_address = marshmallow.fields.Email(required=True)
28
+    first_name = marshmallow.fields.String(required=True)
29
+    last_name = marshmallow.fields.String(required=True)
30
+    display_name = marshmallow.fields.String(required=True)
31
+    company = marshmallow.fields.String(required=True)
32
+
33
+
34
+class PaginationSchema(marshmallow.Schema):
35
+    first_id = marshmallow.fields.Int(required=True)
36
+    last_id = marshmallow.fields.Int(required=True)
37
+    current_id = marshmallow.fields.Int(required=True)
38
+
39
+
40
+class ListsUserSchema(marshmallow.Schema):
41
+    item_nb = marshmallow.fields.Int(
42
+        required=True,
43
+        validate=marshmallow.validate.Range(min=0)
44
+    )
45
+    items = marshmallow.fields.Nested(
46
+        UserSchema,
47
+        many=True,
48
+        only=['id', 'username', 'display_name', 'company']
49
+    )
50
+    # TODO - G.M - 2017-12-05 - Fix nested schema import into doc !
51
+    # Can't add doc for nested Schema properly
52
+    # When schema item isn't added through their own method
53
+    # Ex : Pagination Schema doesn't work here but UserSchema is ok.
54
+    pagination = marshmallow.fields.Nested(
55
+        PaginationSchema
56
+    )

+ 0 - 8
tracim/views/notfound.py Zobrazit soubor

@@ -1,8 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from pyramid.view import notfound_view_config
3
-# TODO - G.M - 29-03-2018 - [Cleanup] Drop this file
4
-
5
-@notfound_view_config(renderer='../templates/404.jinja2')
6
-def notfound_view(request):
7
-    request.response.status = 404
8
-    return {}