From 6ddd8fc8b6a746f5663ecf628caac55f3ec15331 Mon Sep 17 00:00:00 2001 From: Brenden Doyle Date: Fri, 28 Nov 2014 23:55:24 -0400 Subject: [PATCH 1/5] first commit api dev doc --- docs/source/contribute/developers/api.rst | 174 ++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 docs/source/contribute/developers/api.rst diff --git a/docs/source/contribute/developers/api.rst b/docs/source/contribute/developers/api.rst new file mode 100644 index 00000000..6b18aef9 --- /dev/null +++ b/docs/source/contribute/developers/api.rst @@ -0,0 +1,174 @@ +Developing new APIs +=================== + +Freeseer uses a REST API framework to remotely control a headless Freeseer host. This article will give a primer on how APIs are designed, and how they are implemented with `Flask `. + +RESTful API Primer +------------------ + +Freeseer's API strives to be RESTful, so you should learn the basics of REST theory to ensure that your endpoint is in fact RESTful. What follows is by no means a replacement for figuring out how RESTful APIs and endpoints are designed, but it is useful for understanding some of the design choices for our framework. + +A REST API is a resource based interface for interacting with software. The most common use case for a REST API, is to allow us to interact with software across a network, without requiring any knowledge of the software'srinternals. Any parts of your software you wish to control externally are abstracted into resources and signified by URIs called endpoints. So in other words to design and develop a RESTful API is to design and develop endpoints that will allow us to interact with parts of your software from the outside. If some set of endpoints is logically related, they are grouped into a set called an API (For example, the Recording API, or the Configuration API). + +So, to keep the somewhat vague terminology straight: generally when people talk about a “RESTful API”, they are referring to the entire RESTful API and also perhaps the framework on which the endpoints and APIs are built, but when they refer to “an API”, “the API”, or some variation, they are referring to some specific API or set of APIs. + +Designing Endpoints +------------------- + +In the most simplistic and crude sense, a RESTful endpoint will be the plural of the type of resource that you want perform a ``GET``, ``POST``, ``DELETE``, or ``PATCH`` on. For example if you want to have RESTful endpoint for handling a server's users, you could have an endpoint named: + +``/users`` + +You could either get a list of all users with + +``GET /users`` + +Or post a new user with + +``POST /users`` + +Or if you want to get specific instances of that resource, you use some identifier. + +``GET /users/1`` (to get user with id “1”) +``DELETE /users/1`` (to delete user with id “1”) + +You also want to have some parameters for your endpoint. For example for creating a user, you will want some set of parameters like, username, email, etc. + +**From endpoints to an API** + +In the Freeseer RESTful API there is a logical organization to our endpoints, which we refer to as APIs. For example, one set of RESTful endpoints is grouped under the Recording API, where each resource is related to creating, deleting, accessing, or performing an action on some recording. Very rarely is an endpoint designed in isolation, we consider what API needs to be developed first and then think of what endpoints would fall under that API. So make sure that your endpoint either falls under and existing API or create a new API for that endpoint to fall under. + +Developing an API +----------------- + +**Note:** *From this point forward we assume that you are creating a new API (a new logically grouped set of RESTful API endpoints). (If you simply need to add new endpoint to an existing API, you can skip to the section )* + +**Creating an API module** + +In the ``freeseer/frontend/controller/`` folder, create a new module .py replacing with your APIs name. You will need to import the ``Blueprint`` and ``request`` modules from flask with: + +.. code-block:: python + + from flask import Blueprint + from flask import request + +You will also need to import the following: + +.. code-block:: python + + from freeseer.frontend.controller import app + +``app`` is the server's ``Flask`` app. + +**Blueprints** + + To organize our endpoints into separate APIs, we make use of Flask's ``Blueprint`` module. All of our endpoints and API specific code and data will exist in an instance of the ``Blueprint`` module, which simply extends the existing Flask server app. + +So to instantiate our API, we add the following code to our API module. + +.. code-block:: python + + = Blueprint('', __name__) + +To associate our Blueprint with the Flask app, we need to add code to ``freeseer/frontend/controller/__init__.py``. + +.. code-block:: python + + from freeseer.frontend.controller. import + + app.register() + +**API-specific Functions and Data** + +Outside of the endpoints, there are a number of functions an API may need to function properly. For example, the ``recording`` api needs to instantiate the multimedia backend for any of its endpoints to work. The ``Blueprint`` can provide us with a number of decorators to wrap any functions that would be necessary for the functioning of our api. Furthermore, any api specific data can be saved to the ``Blueprint`` object. + +One of the most useful for developers will be the ``@.before_first_request`` decorator. Any code that needs to be run so that the endpoints can function should be decorated by this decorator so it can run before the first request is made to the REST framework. For example, in the recording api, we have a function called ``configure_recording()`` that loads references to existing videos from disk so our endpoints will work. By wrapping it with ``@recording.before_app_first_request``, that code will fire when the first call to the REST API is made. + + +Developing Endpoints +-------------------- + +**Route decorator** + +Every endpoint is wrapped with a ``@.route()`` decorator. + +*Route Parameters* + +*rule* + +the first parameter of the ``route()`` function. The path of the endpoint with any path parameters declared. Ex. route('/users') will establish a route at to http:///users + +*methods* + +a list of all methods (GET, POST, etc.) that this route should accept. Example: ``route('users/', methods=['GET'])`` means this function will only fire if a GET request is sent to the corresponding path. + +More information about route registration can be found in the `Flask documentation ` + +**Path parameters** + +Any path parameters are specified with angular brackets. Ex. ``route('/users/')`` means any text entered after ``/users/`` will be saved as a string under the variable ``username``. + +If you want your parameter to be coerced to a certain type, you use the format + +Available types include int, and float. + +**Parameters sent via request body** + +In Flask, for an endpoint to accept a parameters from a request body, we don't need to explicitly declare body parameters in our function definition or route decorator. A function can examine the body of data sent by some client by reading the data found in 'request.form' where our body would be contained. + + +**Request validation** + +Obviously we want some way to ensure our endpoint gets the right kind of data (in our case, ``JSON`` formatted), and gets the data the endpoint expects. So we have added a module called ``validate`` that ensures the body data is the correct format, and contains the data the endpoint needs to function. + +The validate module validates request data through ``validate_form(to_validate, schema)`` + +*Parameters:* + +*to_validate* - the body data of our request. In most cases this will be 'request.form'. + +*schema* - a `jsonschema ` formatted schema to describe what our request data should look like. + +If the validation fails, ``validate_form()`` throws an ``HTTPError`` which will be sent to the client as a response. + +**validation_schemas** + +Depending on the nature of your API, your validation schema may be automatically generated. . If your schema is not auto-generated, you may have to include any relevant schemas in the Blueprint object. (In the case of Recording API, we store the schemas in a dictionary called form_schema.) + +We use the library jsonschema to validate our json objects. The json-schema `documentation ` will have any information you need for creating json schemas to validate data against. + +**Returning a response** + +For your function to return information back to the client, the endpoint function needs to return a ``dict`` which represents the JSON object that will be the body of the response returned by the server. + +By wrapping our endpoint function with ``@http_response(status_code)`` (status_code being the HTTP status code that indicates success), the ``dict`` and ``status_code`` become the basis for our response to the client. The decorator should go between the route decorator and the endpoint function. + +**Error handling** + +Our endpoints needs some way of handling requests that would cause our endpoint functions to fail, and alert the client that their request was faulty. We do this by catching the error as it happens, or pre-empting it via some validation, and sending a response back to the client that includes error information for why the request failed. + +For example: an endpoint receiving a request for a non-existent resources like a non-existent recording. When we do run into one of these errors, we need to send a response with an appropriate status code, and error information in our responses body. In the case of a non-existent recording, we alert the user with a 404 status code, and our response body will be a JSON object that includes a useful message such as 'No recording with id was found.' + +When we run into this kind of situation, we raise an ``HTTPError`` in our endpoint function. + +``HTTPError(status_code, description=None)`` + +*status_code:* +the HTTP Error code that corresponds to our error, the error codes supported at present are (more can always be added): + +400: 'Bad Request: Request could not be understood due to malformed syntax.', +401: 'Unauthorized: Authentication was not provided or has failed.', +404: 'Not Found: Requested resource is not available.', +409: 'Conflict: Request could not be processed because of server conflict.', +422: 'Unprocessable Entity: Request could not be processed due to semantic errors.' + +*description:* +a string containing human readable information that a client user would find informative, and rectify the issue. If we don't supply a description method, the user will only read a generic message corresponding to the status code. + +*Errors handled by the framework* + +In some situations the framework or another module already handles these errors for us, so we do not need to worry about them. (The following list may not be exhaustive, feel free to add more) + +Faulty path parameters: If path parameters cannot be coerced to the type specified by the route's rule parameter, it will send the client a response with error information. + +Validation errors: As long as we are calling the validate_form method from the validate module, the validate module will raise an HTTPError and supply appropriate information. From d179fa4a7951f823cd1f615de036c555e08af23a Mon Sep 17 00:00:00 2001 From: Brenden Doyle Date: Sat, 29 Nov 2014 00:24:13 -0400 Subject: [PATCH 2/5] Second pass --- docs/source/contribute/developers/api.rst | 74 ++++++++++++----------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/docs/source/contribute/developers/api.rst b/docs/source/contribute/developers/api.rst index 6b18aef9..dacee156 100644 --- a/docs/source/contribute/developers/api.rst +++ b/docs/source/contribute/developers/api.rst @@ -1,7 +1,7 @@ Developing new APIs =================== -Freeseer uses a REST API framework to remotely control a headless Freeseer host. This article will give a primer on how APIs are designed, and how they are implemented with `Flask `. +Freeseer uses a REST API framework to remotely control a headless Freeseer host. This article will give a primer on how APIs are designed, and how they are implemented with `Flask `_. RESTful API Primer ------------------ @@ -30,22 +30,23 @@ Or post a new user with Or if you want to get specific instances of that resource, you use some identifier. ``GET /users/1`` (to get user with id “1”) + ``DELETE /users/1`` (to delete user with id “1”) You also want to have some parameters for your endpoint. For example for creating a user, you will want some set of parameters like, username, email, etc. -**From endpoints to an API** +**From Endpoints to an API** In the Freeseer RESTful API there is a logical organization to our endpoints, which we refer to as APIs. For example, one set of RESTful endpoints is grouped under the Recording API, where each resource is related to creating, deleting, accessing, or performing an action on some recording. Very rarely is an endpoint designed in isolation, we consider what API needs to be developed first and then think of what endpoints would fall under that API. So make sure that your endpoint either falls under and existing API or create a new API for that endpoint to fall under. Developing an API ----------------- -**Note:** *From this point forward we assume that you are creating a new API (a new logically grouped set of RESTful API endpoints). (If you simply need to add new endpoint to an existing API, you can skip to the section )* +**Note:** From this point forward we assume that you are creating a new API (a new logically grouped set of RESTful API endpoints). (If you simply need to add new endpoint to an existing API, you can skip ahead to *Developing Endpoints* **Creating an API module** -In the ``freeseer/frontend/controller/`` folder, create a new module .py replacing with your APIs name. You will need to import the ``Blueprint`` and ``request`` modules from flask with: +In the ``freeseer/frontend/controller/`` folder, create a new module .py (replacing with the name of your api. You will need to import the ``Blueprint`` and ``request`` modules from flask with: .. code-block:: python @@ -60,17 +61,17 @@ You will also need to import the following: ``app`` is the server's ``Flask`` app. -**Blueprints** +**Using Blueprints** To organize our endpoints into separate APIs, we make use of Flask's ``Blueprint`` module. All of our endpoints and API specific code and data will exist in an instance of the ``Blueprint`` module, which simply extends the existing Flask server app. -So to instantiate our API, we add the following code to our API module. +To instantiate our API, we add the following code to our API module. .. code-block:: python = Blueprint('', __name__) -To associate our Blueprint with the Flask app, we need to add code to ``freeseer/frontend/controller/__init__.py``. +Then we need to associate our Blueprint with the Flask app, to do this we add code to ``freeseer/frontend/controller/__init__.py``. .. code-block:: python @@ -92,17 +93,13 @@ Developing Endpoints Every endpoint is wrapped with a ``@.route()`` decorator. -*Route Parameters* - -*rule* +**Parameters**: -the first parameter of the ``route()`` function. The path of the endpoint with any path parameters declared. Ex. route('/users') will establish a route at to http:///users +**rule:** the first parameter of the ``route()`` function. The path of the endpoint with any path parameters declared. Ex. route('/users') will establish a route at to http:///users -*methods* +**methods:** a list of all methods (GET, POST, etc.) this route accepts. Example: ``route('users/', methods=['GET'])`` means this function will only fire if a GET request is sent to the corresponding path. -a list of all methods (GET, POST, etc.) that this route should accept. Example: ``route('users/', methods=['GET'])`` means this function will only fire if a GET request is sent to the corresponding path. - -More information about route registration can be found in the `Flask documentation ` +More information about route registration can be found in the `Flask documentation `_ **Path parameters** @@ -112,7 +109,7 @@ If you want your parameter to be coerced to a certain type, you use the format < Available types include int, and float. -**Parameters sent via request body** +**Request Body** In Flask, for an endpoint to accept a parameters from a request body, we don't need to explicitly declare body parameters in our function definition or route decorator. A function can examine the body of data sent by some client by reading the data found in 'request.form' where our body would be contained. @@ -123,19 +120,23 @@ Obviously we want some way to ensure our endpoint gets the right kind of data (i The validate module validates request data through ``validate_form(to_validate, schema)`` -*Parameters:* +**Parameters:** -*to_validate* - the body data of our request. In most cases this will be 'request.form'. +**to_validate:** the body data of our request. In most cases this will be 'request.form'. -*schema* - a `jsonschema ` formatted schema to describe what our request data should look like. +**schema:** a `jsonschema `_ formatted schema to describe what our request data should look like. If the validation fails, ``validate_form()`` throws an ``HTTPError`` which will be sent to the client as a response. -**validation_schemas** +**Validation Schemas** + +Depending on the nature of your API, your validation schema may be automatically generated. -Depending on the nature of your API, your validation schema may be automatically generated. . If your schema is not auto-generated, you may have to include any relevant schemas in the Blueprint object. (In the case of Recording API, we store the schemas in a dictionary called form_schema.) +.. todo:: Fill in information about how validation schemas are automatically generated -We use the library jsonschema to validate our json objects. The json-schema `documentation ` will have any information you need for creating json schemas to validate data against. +If your schema is not auto-generated, you may have to include any relevant schemas in the Blueprint object. (In the case of Recording API, we store the schemas in a dictionary called form_schema.) + +We use the library jsonschema to validate our json objects. The json-schema `documentation `_ will have any information you need for creating json schemas to validate data against. **Returning a response** @@ -147,28 +148,29 @@ By wrapping our endpoint function with ``@http_response(status_code)`` (status_c Our endpoints needs some way of handling requests that would cause our endpoint functions to fail, and alert the client that their request was faulty. We do this by catching the error as it happens, or pre-empting it via some validation, and sending a response back to the client that includes error information for why the request failed. -For example: an endpoint receiving a request for a non-existent resources like a non-existent recording. When we do run into one of these errors, we need to send a response with an appropriate status code, and error information in our responses body. In the case of a non-existent recording, we alert the user with a 404 status code, and our response body will be a JSON object that includes a useful message such as 'No recording with id was found.' +- Example: an endpoint receiving a request for a non-existent resources like a non-existent recording. When we do run into one of these errors, we need to send a response with an appropriate status code, and error information in our responses body. In the case of a non-existent recording, we alert the user with a 404 status code, and our response body will be a JSON object that includes a useful message such as 'No recording with id was found.' + +**HTTPError** -When we run into this kind of situation, we raise an ``HTTPError`` in our endpoint function. +If we encounter some error, we always raise an ``HTTPError`` in our endpoint function if that error is to be returned to the client. ``HTTPError(status_code, description=None)`` -*status_code:* -the HTTP Error code that corresponds to our error, the error codes supported at present are (more can always be added): +**status_code:** the HTTP Error code that corresponds to our error, the error codes supported at present are (more can always be added): + +- 400: 'Bad Request: Request could not be understood due to malformed syntax.', +- 401: 'Unauthorized: Authentication was not provided or has failed.', +- 404: 'Not Found: Requested resource is not available.', +- 409: 'Conflict: Request could not be processed because of server conflict.', +- 422: 'Unprocessable Entity: Request could not be processed due to semantic errors.' -400: 'Bad Request: Request could not be understood due to malformed syntax.', -401: 'Unauthorized: Authentication was not provided or has failed.', -404: 'Not Found: Requested resource is not available.', -409: 'Conflict: Request could not be processed because of server conflict.', -422: 'Unprocessable Entity: Request could not be processed due to semantic errors.' -*description:* -a string containing human readable information that a client user would find informative, and rectify the issue. If we don't supply a description method, the user will only read a generic message corresponding to the status code. +**description:** a string containing human readable information that a client user would find informative, and rectify the issue. If we don't supply a description method, the user will only read a generic message corresponding to the status code. -*Errors handled by the framework* +**Errors Handled by the Framework** In some situations the framework or another module already handles these errors for us, so we do not need to worry about them. (The following list may not be exhaustive, feel free to add more) -Faulty path parameters: If path parameters cannot be coerced to the type specified by the route's rule parameter, it will send the client a response with error information. +**Faulty Path Parameters:** If path parameters cannot be coerced to the type specified by the route's rule parameter, it will send the client a response with error information. -Validation errors: As long as we are calling the validate_form method from the validate module, the validate module will raise an HTTPError and supply appropriate information. +**Validation Errors:** when we call the validate_form method from the validate module, the validate module will always raise an HTTPError and supply appropriate information. From 4d8a59e1a9588a4e27a73a6dd5d9237619cdc576 Mon Sep 17 00:00:00 2001 From: Brenden Doyle Date: Sat, 29 Nov 2014 00:29:06 -0400 Subject: [PATCH 3/5] Fixed indentation --- docs/source/contribute/developers/api.rst | 33 +++++++++++------------ 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/docs/source/contribute/developers/api.rst b/docs/source/contribute/developers/api.rst index dacee156..e295f322 100644 --- a/docs/source/contribute/developers/api.rst +++ b/docs/source/contribute/developers/api.rst @@ -63,7 +63,7 @@ You will also need to import the following: **Using Blueprints** - To organize our endpoints into separate APIs, we make use of Flask's ``Blueprint`` module. All of our endpoints and API specific code and data will exist in an instance of the ``Blueprint`` module, which simply extends the existing Flask server app. +To organize our endpoints into separate APIs, we make use of Flask's ``Blueprint`` module. All of our endpoints and API specific code and data will exist in an instance of the ``Blueprint`` module, which simply extends the existing Flask server app. To instantiate our API, we add the following code to our API module. @@ -93,11 +93,11 @@ Developing Endpoints Every endpoint is wrapped with a ``@.route()`` decorator. -**Parameters**: + **Parameters**: -**rule:** the first parameter of the ``route()`` function. The path of the endpoint with any path parameters declared. Ex. route('/users') will establish a route at to http:///users + **rule:** the first parameter of the ``route()`` function. The path of the endpoint with any path parameters declared. Ex. route('/users') will establish a route at to http:///users -**methods:** a list of all methods (GET, POST, etc.) this route accepts. Example: ``route('users/', methods=['GET'])`` means this function will only fire if a GET request is sent to the corresponding path. + **methods:** a list of all methods (GET, POST, etc.) this route accepts. Example: ``route('users/', methods=['GET'])`` means this function will only fire if a GET request is sent to the corresponding path. More information about route registration can be found in the `Flask documentation `_ @@ -120,11 +120,11 @@ Obviously we want some way to ensure our endpoint gets the right kind of data (i The validate module validates request data through ``validate_form(to_validate, schema)`` -**Parameters:** + **Parameters:** -**to_validate:** the body data of our request. In most cases this will be 'request.form'. + **to_validate:** the body data of our request. In most cases this will be 'request.form'. -**schema:** a `jsonschema `_ formatted schema to describe what our request data should look like. + **schema:** a `jsonschema `_ formatted schema to describe what our request data should look like. If the validation fails, ``validate_form()`` throws an ``HTTPError`` which will be sent to the client as a response. @@ -156,21 +156,20 @@ If we encounter some error, we always raise an ``HTTPError`` in our endpoint fun ``HTTPError(status_code, description=None)`` -**status_code:** the HTTP Error code that corresponds to our error, the error codes supported at present are (more can always be added): + **status_code:** the HTTP Error code that corresponds to our error, the error codes supported at present are (more can always be added): -- 400: 'Bad Request: Request could not be understood due to malformed syntax.', -- 401: 'Unauthorized: Authentication was not provided or has failed.', -- 404: 'Not Found: Requested resource is not available.', -- 409: 'Conflict: Request could not be processed because of server conflict.', -- 422: 'Unprocessable Entity: Request could not be processed due to semantic errors.' + 400: 'Bad Request: Request could not be understood due to malformed syntax.', + 401: 'Unauthorized: Authentication was not provided or has failed.', + 404: 'Not Found: Requested resource is not available.', + 409: 'Conflict: Request could not be processed because of server conflict.', + 422: 'Unprocessable Entity: Request could not be processed due to semantic errors.' - -**description:** a string containing human readable information that a client user would find informative, and rectify the issue. If we don't supply a description method, the user will only read a generic message corresponding to the status code. + **description:** a string containing human readable information that a client user would find informative, and rectify the issue. If we don't supply a description method, the user will only read a generic message corresponding to the status code. **Errors Handled by the Framework** In some situations the framework or another module already handles these errors for us, so we do not need to worry about them. (The following list may not be exhaustive, feel free to add more) -**Faulty Path Parameters:** If path parameters cannot be coerced to the type specified by the route's rule parameter, it will send the client a response with error information. + **Faulty Path Parameters:** If path parameters cannot be coerced to the type specified by the route's rule parameter, it will send the client a response with error information. -**Validation Errors:** when we call the validate_form method from the validate module, the validate module will always raise an HTTPError and supply appropriate information. + **Validation Errors:** when we call the validate_form method from the validate module, the validate module will always raise an HTTPError and supply appropriate information. From ae447c7ca7ae58fb07828ff35480e711f66c03d7 Mon Sep 17 00:00:00 2001 From: Brenden Doyle Date: Sat, 29 Nov 2014 01:02:58 -0400 Subject: [PATCH 4/5] Dictionary format for function descriptions --- docs/source/contribute/developers/api.rst | 47 ++++++++++++----------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/docs/source/contribute/developers/api.rst b/docs/source/contribute/developers/api.rst index e295f322..986ce91b 100644 --- a/docs/source/contribute/developers/api.rst +++ b/docs/source/contribute/developers/api.rst @@ -17,21 +17,21 @@ Designing Endpoints In the most simplistic and crude sense, a RESTful endpoint will be the plural of the type of resource that you want perform a ``GET``, ``POST``, ``DELETE``, or ``PATCH`` on. For example if you want to have RESTful endpoint for handling a server's users, you could have an endpoint named: -``/users`` + ``/users`` You could either get a list of all users with -``GET /users`` + ``GET /users`` Or post a new user with -``POST /users`` + ``POST /users`` Or if you want to get specific instances of that resource, you use some identifier. -``GET /users/1`` (to get user with id “1”) + ``GET /users/1`` - to get user with id “1” -``DELETE /users/1`` (to delete user with id “1”) + ``DELETE /users/1`` - to delete user with id “1” You also want to have some parameters for your endpoint. For example for creating a user, you will want some set of parameters like, username, email, etc. @@ -93,11 +93,11 @@ Developing Endpoints Every endpoint is wrapped with a ``@.route()`` decorator. - **Parameters**: + **Decorator Parameters** - **rule:** the first parameter of the ``route()`` function. The path of the endpoint with any path parameters declared. Ex. route('/users') will establish a route at to http:///users + :rule: the first parameter of the ``route()`` function. The path of the endpoint with any path parameters declared. Ex. route('/users') will establish a route at to http:///users - **methods:** a list of all methods (GET, POST, etc.) this route accepts. Example: ``route('users/', methods=['GET'])`` means this function will only fire if a GET request is sent to the corresponding path. + :methods: a list of all methods (GET, POST, etc.) this route accepts. Example: ``route('users/', methods=['GET'])`` means this function will only fire if a GET request is sent to the corresponding path. More information about route registration can be found in the `Flask documentation `_ @@ -120,11 +120,10 @@ Obviously we want some way to ensure our endpoint gets the right kind of data (i The validate module validates request data through ``validate_form(to_validate, schema)`` - **Parameters:** + **Function Parameters:** - **to_validate:** the body data of our request. In most cases this will be 'request.form'. - - **schema:** a `jsonschema `_ formatted schema to describe what our request data should look like. + :to_validate: the body data of our request. In most cases this will be 'request.form'. + :schema: a `jsonschema `_ formatted schema to describe what our request data should look like. If the validation fails, ``validate_form()`` throws an ``HTTPError`` which will be sent to the client as a response. @@ -146,25 +145,29 @@ By wrapping our endpoint function with ``@http_response(status_code)`` (status_c **Error handling** -Our endpoints needs some way of handling requests that would cause our endpoint functions to fail, and alert the client that their request was faulty. We do this by catching the error as it happens, or pre-empting it via some validation, and sending a response back to the client that includes error information for why the request failed. +Our endpoints needs some way of handling requests that would cause our endpoint functions to fail, and alert the client that their request was faulty. We do this by catching the error as it happens, or pre-empting it via some validation, and sending a response back to the client that includes error information for why the request failed. For example, an endpoint receiving a request for a non-existent resources like a non-existing user. -- Example: an endpoint receiving a request for a non-existent resources like a non-existent recording. When we do run into one of these errors, we need to send a response with an appropriate status code, and error information in our responses body. In the case of a non-existent recording, we alert the user with a 404 status code, and our response body will be a JSON object that includes a useful message such as 'No recording with id was found.' +When we do run into one of these errors, we need to send a response with an appropriate status code, and error information in our responses body. In the case of a non-existent recording, we alert the user with a 404 status code, and our response body will be a JSON object that includes a useful message such as 'No recording with id was found.' **HTTPError** If we encounter some error, we always raise an ``HTTPError`` in our endpoint function if that error is to be returned to the client. -``HTTPError(status_code, description=None)`` + **HTTPError Parameters** + + :status_code: + the HTTP Error code that corresponds to our error. The error codes supported at present are below (more can be added as needed) + :: - **status_code:** the HTTP Error code that corresponds to our error, the error codes supported at present are (more can always be added): + 400: 'Bad Request: Request could not be understood due to malformed syntax.' + 401: 'Unauthorized: Authentication was not provided or has failed.' + 404: 'Not Found: Requested resource is not available.' + 409: 'Conflict: Request could not be processed because of server conflict.' + 422: 'Unprocessable Entity: Request could not be processed due to semantic errors.' - 400: 'Bad Request: Request could not be understood due to malformed syntax.', - 401: 'Unauthorized: Authentication was not provided or has failed.', - 404: 'Not Found: Requested resource is not available.', - 409: 'Conflict: Request could not be processed because of server conflict.', - 422: 'Unprocessable Entity: Request could not be processed due to semantic errors.' - **description:** a string containing human readable information that a client user would find informative, and rectify the issue. If we don't supply a description method, the user will only read a generic message corresponding to the status code. + :description: + a string containing human readable information that a client user would find informative, and rectify the issue. If we don't supply a description method, the user will only read a generic message corresponding to the status code. **Errors Handled by the Framework** From eb5894cc2a2310a7920769687ccf4fcb352c19a2 Mon Sep 17 00:00:00 2001 From: Brenden Doyle Date: Sun, 30 Nov 2014 14:22:19 -0400 Subject: [PATCH 5/5] Text properly wrapped --- docs/source/contribute/developers/api.rst | 181 +++++++++++++++++----- 1 file changed, 140 insertions(+), 41 deletions(-) diff --git a/docs/source/contribute/developers/api.rst b/docs/source/contribute/developers/api.rst index 986ce91b..2f91704f 100644 --- a/docs/source/contribute/developers/api.rst +++ b/docs/source/contribute/developers/api.rst @@ -1,21 +1,42 @@ Developing new APIs =================== -Freeseer uses a REST API framework to remotely control a headless Freeseer host. This article will give a primer on how APIs are designed, and how they are implemented with `Flask `_. +Freeseer uses a REST API framework to remotely control a headless Freeseer +host. This article will give a primer on how APIs are designed, and how they +are implemented with `Flask `_. RESTful API Primer ------------------ -Freeseer's API strives to be RESTful, so you should learn the basics of REST theory to ensure that your endpoint is in fact RESTful. What follows is by no means a replacement for figuring out how RESTful APIs and endpoints are designed, but it is useful for understanding some of the design choices for our framework. - -A REST API is a resource based interface for interacting with software. The most common use case for a REST API, is to allow us to interact with software across a network, without requiring any knowledge of the software'srinternals. Any parts of your software you wish to control externally are abstracted into resources and signified by URIs called endpoints. So in other words to design and develop a RESTful API is to design and develop endpoints that will allow us to interact with parts of your software from the outside. If some set of endpoints is logically related, they are grouped into a set called an API (For example, the Recording API, or the Configuration API). - -So, to keep the somewhat vague terminology straight: generally when people talk about a “RESTful API”, they are referring to the entire RESTful API and also perhaps the framework on which the endpoints and APIs are built, but when they refer to “an API”, “the API”, or some variation, they are referring to some specific API or set of APIs. +Freeseer's API strives to be RESTful, so you should learn the basics of REST +theory to ensure that your endpoint is in fact RESTful. What follows is by no +means a replacement for figuring out how RESTful APIs and endpoints are +designed, but it is useful for understanding some of the design choices for our +framework. + +A REST API is a resource based interface for interacting with software. The +most common use case for a REST API, is to allow us to interact with software +across a network, without requiring any knowledge of the software's internals. +Any parts of your software you wish to control externally are abstracted into +resources and signified by URIs called endpoints. So in other words to design +and develop a RESTful API is to design and develop endpoints that will allow +us to interact with parts of your software from the outside. If some set of +endpoints is logically related, they are grouped into a set called an API +(For example, the Recording API, or the Configuration API). + +So, to keep the somewhat vague terminology straight: generally when people talk +about a “RESTful API”, they are referring to the entire RESTful API and also +perhaps the framework on which the endpoints and APIs are built, but when they +refer to “an API”, “the API”, or some variation, they +are referring to some specific API or set of APIs. Designing Endpoints ------------------- -In the most simplistic and crude sense, a RESTful endpoint will be the plural of the type of resource that you want perform a ``GET``, ``POST``, ``DELETE``, or ``PATCH`` on. For example if you want to have RESTful endpoint for handling a server's users, you could have an endpoint named: +In the most simplistic and crude sense, a RESTful endpoint will be the plural +of the type of resource that you want perform a ``GET``, ``POST``, ``DELETE``, +or ``PATCH`` on. For example if you want to have RESTful endpoint for handling +a server's users, you could have an endpoint named: ``/users`` @@ -27,26 +48,40 @@ Or post a new user with ``POST /users`` -Or if you want to get specific instances of that resource, you use some identifier. +Or if you want to get specific instances of that resource, you use some +identifier. ``GET /users/1`` - to get user with id “1” ``DELETE /users/1`` - to delete user with id “1” -You also want to have some parameters for your endpoint. For example for creating a user, you will want some set of parameters like, username, email, etc. +You also want to have some parameters for your endpoint. For example for +creating a user, you will want some set of parameters like, username, email, +etc. **From Endpoints to an API** -In the Freeseer RESTful API there is a logical organization to our endpoints, which we refer to as APIs. For example, one set of RESTful endpoints is grouped under the Recording API, where each resource is related to creating, deleting, accessing, or performing an action on some recording. Very rarely is an endpoint designed in isolation, we consider what API needs to be developed first and then think of what endpoints would fall under that API. So make sure that your endpoint either falls under and existing API or create a new API for that endpoint to fall under. +In the Freeseer RESTful API there is a logical organization to our endpoints, +which we refer to as APIs. For example, one set of RESTful endpoints is grouped +under the Recording API, where each resource is related to creating, deleting, +accessing, or performing an action on some recording. Very rarely is an endpoint +designed in isolation, we consider what API needs to be developed first and then +think of what endpoints would fall under that API. So make sure that your +endpoint either falls under and existing API or create a new API for that +endpoint to fall under. Developing an API ----------------- -**Note:** From this point forward we assume that you are creating a new API (a new logically grouped set of RESTful API endpoints). (If you simply need to add new endpoint to an existing API, you can skip ahead to *Developing Endpoints* +**Note:** From this point forward we assume that you are creating a new API +(a new logically grouped set of RESTful API endpoints). If you simply need to +add new endpoint to an existing API, you can skip ahead to *Developing Endpoints* **Creating an API module** -In the ``freeseer/frontend/controller/`` folder, create a new module .py (replacing with the name of your api. You will need to import the ``Blueprint`` and ``request`` modules from flask with: +In the ``freeseer/frontend/controller/`` folder, create a new module +.py (replacing with the name of your api. You will need to +import the ``Blueprint`` and ``request`` modules from flask with: .. code-block:: python @@ -63,7 +98,10 @@ You will also need to import the following: **Using Blueprints** -To organize our endpoints into separate APIs, we make use of Flask's ``Blueprint`` module. All of our endpoints and API specific code and data will exist in an instance of the ``Blueprint`` module, which simply extends the existing Flask server app. +To organize our endpoints into separate APIs, we make use of Flask's +``Blueprint`` module. All of our endpoints and API specific code and data will +exist in an instance of the ``Blueprint`` module, which simply extends the +existing Flask server app. To instantiate our API, we add the following code to our API module. @@ -71,7 +109,8 @@ To instantiate our API, we add the following code to our API module. = Blueprint('', __name__) -Then we need to associate our Blueprint with the Flask app, to do this we add code to ``freeseer/frontend/controller/__init__.py``. +Then we need to associate our Blueprint with the Flask app, to do this we add +code to ``freeseer/frontend/controller/__init__.py``. .. code-block:: python @@ -81,9 +120,21 @@ Then we need to associate our Blueprint with the Flask app, to do this we add co **API-specific Functions and Data** -Outside of the endpoints, there are a number of functions an API may need to function properly. For example, the ``recording`` api needs to instantiate the multimedia backend for any of its endpoints to work. The ``Blueprint`` can provide us with a number of decorators to wrap any functions that would be necessary for the functioning of our api. Furthermore, any api specific data can be saved to the ``Blueprint`` object. +Outside of the endpoints, there are a number of functions an API may need to +function properly. For example, the ``recording`` api needs to instantiate the +multimedia backend for any of its endpoints to work. The ``Blueprint`` can +provide us with a number of decorators to wrap any functions that would be +necessary for the functioning of our api. Furthermore, any api specific data can +be saved to the ``Blueprint`` object. -One of the most useful for developers will be the ``@.before_first_request`` decorator. Any code that needs to be run so that the endpoints can function should be decorated by this decorator so it can run before the first request is made to the REST framework. For example, in the recording api, we have a function called ``configure_recording()`` that loads references to existing videos from disk so our endpoints will work. By wrapping it with ``@recording.before_app_first_request``, that code will fire when the first call to the REST API is made. +One of the most useful for developers will be the +``@.before_first_request`` decorator. Any code that needs to be run +so that the endpoints can function should be decorated by this decorator so it +can run before the first request is made to the REST framework. For example, in +the recording api, we have a function called ``configure_recording()`` that +loads references to existing videos from disk so our endpoints will work. By +wrapping it with ``@recording.before_app_first_request``, that code will fire +when the first call to the REST API is made. Developing Endpoints @@ -95,68 +146,108 @@ Every endpoint is wrapped with a ``@.route()`` decorator. **Decorator Parameters** - :rule: the first parameter of the ``route()`` function. The path of the endpoint with any path parameters declared. Ex. route('/users') will establish a route at to http:///users + :rule: the first parameter of the ``route()`` function. The path of the + endpoint with any path parameters declared. Ex. route('/users') will establish + a route at to http:///users - :methods: a list of all methods (GET, POST, etc.) this route accepts. Example: ``route('users/', methods=['GET'])`` means this function will only fire if a GET request is sent to the corresponding path. + :methods: a list of all methods (GET, POST, etc.) this route accepts. Example: + ``route('users/', methods=['GET'])`` means this function will only fire + if a GET request is sent to the corresponding path. -More information about route registration can be found in the `Flask documentation `_ +More information about route registration can be found in the `Flask +documentation `_ **Path parameters** -Any path parameters are specified with angular brackets. Ex. ``route('/users/')`` means any text entered after ``/users/`` will be saved as a string under the variable ``username``. +Any path parameters are specified with angular brackets. Ex. +``route('/users/')`` means any text entered after ``/users/`` will be +saved as a string under the variable ``username``. -If you want your parameter to be coerced to a certain type, you use the format +If you want your parameter to be coerced to a certain type, you use the format + Available types include int, and float. **Request Body** -In Flask, for an endpoint to accept a parameters from a request body, we don't need to explicitly declare body parameters in our function definition or route decorator. A function can examine the body of data sent by some client by reading the data found in 'request.form' where our body would be contained. +In Flask, for an endpoint to accept a parameters from a request body, we don't +need to explicitly declare body parameters in our function definition or route +decorator. A function can examine the body of data sent by some client by +reading the data found in 'request.form' where our body would be contained. **Request validation** -Obviously we want some way to ensure our endpoint gets the right kind of data (in our case, ``JSON`` formatted), and gets the data the endpoint expects. So we have added a module called ``validate`` that ensures the body data is the correct format, and contains the data the endpoint needs to function. +Obviously we want some way to ensure our endpoint gets the right kind of data +(in our case, ``JSON`` formatted), and gets the data the endpoint expects. So we +have added a module called ``validate`` that ensures the body data is the +correct format, and contains the data the endpoint needs to function. -The validate module validates request data through ``validate_form(to_validate, schema)`` +The validate module validates request data through +``validate_form(to_validate, schema)`` **Function Parameters:** - :to_validate: the body data of our request. In most cases this will be 'request.form'. - :schema: a `jsonschema `_ formatted schema to describe what our request data should look like. + :to_validate: the body data of our request. In most cases this will be + 'request.form'. + :schema: a `jsonschema `_ formatted schema to describe + what our request data should look like. -If the validation fails, ``validate_form()`` throws an ``HTTPError`` which will be sent to the client as a response. +If the validation fails, ``validate_form()`` throws an ``HTTPError`` which will +be sent to the client as a response. **Validation Schemas** -Depending on the nature of your API, your validation schema may be automatically generated. +Depending on the nature of your API, your validation schema may be +automatically generated. -.. todo:: Fill in information about how validation schemas are automatically generated +.. todo:: Fill in information about how validation schemas are automatically +generated -If your schema is not auto-generated, you may have to include any relevant schemas in the Blueprint object. (In the case of Recording API, we store the schemas in a dictionary called form_schema.) +If your schema is not auto-generated, you may have to include any relevant +schemas in the Blueprint object. (In the case of Recording API, we store the +schemas in a dictionary called form_schema.) -We use the library jsonschema to validate our json objects. The json-schema `documentation `_ will have any information you need for creating json schemas to validate data against. +We use the library jsonschema to validate our json objects. The json-schema +`documentation `_ will have any information you need for +creating json schemas to validate data against. **Returning a response** -For your function to return information back to the client, the endpoint function needs to return a ``dict`` which represents the JSON object that will be the body of the response returned by the server. +For your function to return information back to the client, the endpoint +function needs to return a ``dict`` which represents the JSON object that will +be the body of the response returned by the server. -By wrapping our endpoint function with ``@http_response(status_code)`` (status_code being the HTTP status code that indicates success), the ``dict`` and ``status_code`` become the basis for our response to the client. The decorator should go between the route decorator and the endpoint function. +By wrapping our endpoint function with ``@http_response(status_code)`` +(status_code being the HTTP status code that indicates success), the ``dict`` +and ``status_code`` become the basis for our response to the client. The +decorator should go between the route decorator and the endpoint function. **Error handling** -Our endpoints needs some way of handling requests that would cause our endpoint functions to fail, and alert the client that their request was faulty. We do this by catching the error as it happens, or pre-empting it via some validation, and sending a response back to the client that includes error information for why the request failed. For example, an endpoint receiving a request for a non-existent resources like a non-existing user. +Our endpoints needs some way of handling requests that would cause our endpoint +functions to fail, and alert the client that their request was faulty. We do +this by catching the error as it happens, or pre-empting it via some validation, +and sending a response back to the client that includes error information for +why the request failed. For example, an endpoint receiving a request for a +non-existent resources like a non-existing user. -When we do run into one of these errors, we need to send a response with an appropriate status code, and error information in our responses body. In the case of a non-existent recording, we alert the user with a 404 status code, and our response body will be a JSON object that includes a useful message such as 'No recording with id was found.' +When we do run into one of these errors, we need to send a response with an +appropriate status code, and error information in our responses body. In the +case of a non-existent recording, we alert the user with a 404 status code, and +our response body will be a JSON object that includes a useful message such as +'No recording with id was found.' **HTTPError** -If we encounter some error, we always raise an ``HTTPError`` in our endpoint function if that error is to be returned to the client. +If we encounter some error, we always raise an ``HTTPError`` in our endpoint +function if that error is to be returned to the client. **HTTPError Parameters** :status_code: - the HTTP Error code that corresponds to our error. The error codes supported at present are below (more can be added as needed) + the HTTP Error code that corresponds to our error. The error codes supported + at present are below (more can be added as needed) :: 400: 'Bad Request: Request could not be understood due to malformed syntax.' @@ -167,12 +258,20 @@ If we encounter some error, we always raise an ``HTTPError`` in our endpoint fun :description: - a string containing human readable information that a client user would find informative, and rectify the issue. If we don't supply a description method, the user will only read a generic message corresponding to the status code. + a string containing human readable information that a client user would find + informative, and rectify the issue. If we don't supply a description method, + the user will only read a generic message corresponding to the status code. **Errors Handled by the Framework** -In some situations the framework or another module already handles these errors for us, so we do not need to worry about them. (The following list may not be exhaustive, feel free to add more) +In some situations the framework or another module already handles these errors +for us, so we do not need to worry about them. (The following list may not be +exhaustive, feel free to add more) - **Faulty Path Parameters:** If path parameters cannot be coerced to the type specified by the route's rule parameter, it will send the client a response with error information. + **Faulty Path Parameters:** If path parameters cannot be coerced to the type + specified by the route's rule parameter, it will send the client a response + with error information. - **Validation Errors:** when we call the validate_form method from the validate module, the validate module will always raise an HTTPError and supply appropriate information. + **Validation Errors:** when we call the validate_form method from the validate + module, the validate module will always raise an HTTPError and supply + appropriate information.