Termipankki
  1. A
    1. API Blueprint
    2. Addressability
    3. Ajax
    4. Anonymous Function
    5. App Context
  2. B
    1. Blueprint
    2. Business Logic
  3. C
    1. CORS
    2. Callback
    3. Client
    4. Column
    5. Column Attribute
    6. Connectedness
  4. D
    1. DOM
    2. Database Schema
  5. E
    1. Element
  6. F
    1. Fixture
    2. Flask App
    3. Foreign Key
  7. G
    1. Generic Client
  8. H
    1. HTTP Method
    2. HTTP Request
    3. Header
    4. Host Part
    5. Hypermedia
  9. I
    1. Instance Folder
  10. J
    1. JSON
    2. JSON Schema
  11. L
    1. Link Relation
  12. M
    1. MIME Type
    2. Migration
    3. Model Class
  13. O
    1. ORM
  14. P
    1. Primary Key
    2. Profile
  15. Q
    1. Query
    2. Query Parameter
  16. R
    1. Regular Expression
    2. Request
    3. Request Body
    4. Request Object
    5. Resource
    6. Resource Class
    7. Response
    8. Response Body
    9. Response Object
    10. Rollback
    11. Routing
    12. Route
      Routing
    13. Row
  17. S
    1. SQL
    2. Static Content
  18. T
    1. Table
    2. Test Setup
    3. Test Teardown
  19. U
    1. URI
    2. URL Template
    3. Unique Constraint
  20. V
    1. View Function
  21. W
    1. Web API
Ratkaistu: / tehtävää

Learning outcomes and material

During this exercise students will learn how to implement a RESTful API utilizing Flask Web Framework. Students will learn also how to test the API by reading the testing tutorial We expect that you follow the same process to complete the Deliverable 4.
The slides presenting the content of the lecture can be downloaded from the following link:
Exercise3_slides.pdf

Implementing REST APIs with Flask

This exercise material covers how to implement REST APIs using Flask-RESTful, a Flask extensions that exists specifically for this purpose (in case you didn't figure that out from the name). We will also discuss how to handle hypermedia in your implementation in a manageable way. The material has examples for both single file applications, and applications that use the the project layout we proposed. For exercise tasks you need to submit single file applications. However for your course project we recommend following the more elaborate project structure.

Introduction to Flask-Restful

In the first part of the exercise we'll cover how to use the RESTful extension. In the examples we are going back to the sensorhub example from the first material. The code is a bit more straightforward then the one in the API server we designed in exercise 2 so this makes for less complex examples. As a very brief recap, we had four key concepts: measurements, sensors, sensor locations and deployment configurations. We'll implement some of these into resources as this example goes on.
Learning goals: Learn the basics of Flask-RESTful: how to define resource classes and implement the
HTTP methods
of
resources
. Learn how to define routes for resources, and about reverse routing for building
URIs
.

Installing

Some new modules are needed for this exercise. Fire up your virtual environment and cast the following spells (the latter is not needed for this section but it will be for the next one):
pip install flask-restful
pip install Flask-SQLAlchemy
pip install jsonschema

A Resourceful Class

Flask-RESTful defines a class called Resource. Much like Model was the base class for all models in our database, Resource is the base class for all our resources. A
resource class
should have a method for each
HTTP method
it supports. These methods must be named the same as the corresponding HTTP method, in lowercase. For instance a collection type resource will usually have two methods: get and post. These methods are very similar in implementation to
view functions
. However they do not have a route decorator - their
route
is based on the resource's route instead. Let's say we want to have two resource classes for sensors: the list of sensors, and then individual sensor where we can also see its measurements. The resource class skeletons would look like this:
from flask_restful import Resource

class SensorCollection(Resource):

    def get(self):
        pass

    def post(self):
        pass


class SensorItem(Resource):

    def get(self, sensor):
        pass

    def put(self, sensor):
        pass

    def delete(self, sensor):
        pass
We're using SensorItem for individual sensors instead of just Sensor, mostly because we already used Sensor for the
model
and this would cause conflicts if everything was placed in a single file. If you want to pursue that path, simply place these classes inside your application module that has the models. However, if you followed the project layout material, these classes should be placed in a new module (e.g. sensor.py) inside the resources subfolder (also make sure there's a file called __init__.py in the folder - it can be empty, but must exist for Python to recognize the folder as a package).
The methods themselves are just like views. For example, here's a post method for SensorCollection that looks very similar to the last version of the add_measurement view in exercise 1.
    def post(self):
        if not request.json:
            abort(415)
            
        try:
            sensor = Sensor(
                name=request.json["name"],
                model=request.json["model"],
            )
            db.session.add(sensor)
            db.session.commit()
        except KeyError:
            abort(400)
        except IntegrityError:
            abort(409)
        
        return "", 201
Do note that all methods must have the same parameters because they all are served from the same resource
URI
! You can, however, have different query parameters between these methods. For example, this would be typical for resources that have some filtering or sorting support in their get method using query parameters.

Resourceful Routing

In order for anything to work in Flask-RESTful we need to initialize an API object. This object will handle things like
routing
for us. To proceed with our example, we'll show you how to create this object, and how to use it to register routes to the two
resource classes
. In a single file app the process is very simple: import Api from flask_restul, and create an instance of it:
from flask import Flask
from flask_restful import Api

app = Flask(__name__)
api = Api(app)
Assuming your resource classes are in the same file, you can now add routes to them by dropping these two lines at the end of the file.
api.add_resource(SensorCollection, "/api/sensors/")
api.add_resource(SensorItem, "/api/sensors/<sensor>/")
Now you could send GET and POST to /api/sensors/, and likewise GET, PUT and DELETE to e.g. /api/sensors/uo-donkeysensor-4451/. Not that they'd do much (except for POST to sensors collection which we just implemented).

Resourceful Inventory Engineer

This taks continues the inventory engineer saga where we develop a very small inventory management service. Previously we chose to do it in a rather unorganized manner. This time we're going to update part of it to the RESTful age. We're not bothering with hypermedia just yet, so the responses will be the same.
Learning goals: Implement a simple collection
resource
with two methods using
Flask
-RESTful.
Before you begin:
You may want to retrieve your application code from this task's predecessor. In particular the POST method will be identical to the
view function
for the route "/products/add".
Resource: ProductCollection
  • Route: "/api/products/"
  • Methods:
    • GET - get list of all products (return JSON array with objects as items)
    • POST - creates a new product
GET: this method retrieves all products from the database and forms a list of them, where each product is a dictionary with the same keys as the database column names. It is effectively a simpler version of "/storage/" route's view function. Example response (formatted for reader's sanity):
[
    {
        "handle": "donkey plushie",
        "weight": 1.20,
        "price": 20.0
    }
]
Note that Flask-RESTful automatically converts Python data structures to
JSON
if a method returns them. So, unlike last time, do not use json.dumps when returning the data structure!
POST: creates a product and returns 201 if successful, various error codes if not. Literally same function as previously. Simply drop the contents of your "/products/add" view function to the
resource class'
post method and you're golden.
In summary, your code should do: Flask-RESTful initialization, one resource class with two methods, and route registration for the resource with api.add_resource.

Sallitut tiedostojen nimet

Varoitus: Et ole kirjautunut sisään. Et voi vastata.
Extra note: When using a more elaborate project structure, resources should be routed in the api.py file which in turn imports the resources from their individual files. Here's the sample api.py file, which assumes the resource classes were saved to sensor.py in the resources folder.
from flask import Blueprint
from flask_restful import Resource, Api

api_bp = Blueprint("api", __name__, url_prefix="/api")
api = Api(api_bp)

# this import must be placed after we create api to avoid issues with
# circular imports
from sensorhub.resources.sensor import SensorCollection, SensorItem

api.add_resource(SensorCollection, "/sensors/")
api.add_resource(SensorItem, "/sensors/<sensor>/")

@api_bp.route("/"):
def index():
    return ""

Even More Resourceful Routing

When it comes to
addressability
, it only states that each resource must be uniquely identifiable by its address. It doesn't say it can't have more than one address. Sometimes it makes sense that the same resource can be found in multiple locations in the URI hierarchy. Consider video games for example that usually have separate developer and publisher. In that case both of these
URI templates
would make equal sense:
Both are different ways to identify the same resource. Luckily Flask-RESTful allows the definition of multiple routes for each resource. These would be routed as follows:
api.add_resource(GameItem, 
    "/api/publishers/<publisher>/games/<game>/",
    "/api/developers/<developer>/games/<game>/"
)
Do note that if you route like this, the resource's methods must now take into account the fact that they do not always receive the same keyword arguments: they will receive either publisher or developer. In this scenario, the GameItem resource could have a get method that starts like this:
class GameItem(Resource):

    def get(game, publisher=None, developer=None):
        if publisher is not None:
            game_obj = Game.query.join(Publisher).filter(
                Game.title == game, Publisher.name == publisher
            ).first()
        elif developer is not None:
            game_obj = Game.query.join(Developer).filter(
                Game.title == game, Developer.name == developer
            ).first()
You can also utilize multiple routes to implement several similar resources using the same
resource class
. The MusicMeta API did this, using the same album and track resource classes for both single artist and VA albums. Actually there was no separate VA route, we just used "VA" as a special value for artist in e.g. the following routing:
api.add_resource(AlbumItem, "/api/artists/<artist>/albums/<album>/")

Reverse Routing

One more feature that we will soon be using a lot is the ability to generate a
URI
from the
routing
. In
hypermedia
resource URIs will be repeated a lot and hardcoding them is asking for trouble. It's much better to do reverse routing with api.url_for. Going back to our sensorhub example, this is how you should always retrieve the URI of a) sensors collection and b) specific sensor item:
collection_uri = api.url_for(SensorCollection)
sensor_uri = api.url_for(Sensor, sensor="uo-donkeysensor-4451")
The function finds the first route that matches the
resource class
and given variables, or raises BuildError if no matching route is found. If found, the URI is returned as a string. Our two examples would generate:
/api/sensors/
/api/sensors/uo-donkeysensor-4451/

Resource Locator

In this task we're bringing our inventory manager one step closer to being a well-designed API. When new resources are created with POST, the response should always contain a Location header. This header's value tells the client where it can find the resource that was just created.
Learning goals: Learn about returning
response objects
and setting
custom headers
.
Before You Begin:
Grab the code from the previous exercise. You will now also need to define at least a dummy for the Product resource, and a
route
for it. The following resource class is sufficient for this task:
class ProductItem(Resource):
    
    def get(self, handle):
        return Response(status=501)
We'll leave the
routing
to you. The route should be "/api/products/<handle>/.
Response Objects:
In order to implement the required change, you need to import one more thing from Flask: Reponse. After this your import from Flask should be:
from flask import Flask, Response, request
A response object can be returned from a view (or HTTP method in
resources
). You can find its documentation here. Its most important keyword arguments are:
  • status - status code
  • mimetype - content type of the response body
  • headers - a dictionary of HTTP headers
When creating a response object, its first argument is the response body, if any. Alternatively you can use the data keyword. In this exercise we're going to need the headers argument, which takes a dictionary of header-value pairs.
Modified Resource: Product Collection
In order to complete this task, modify the POST method from the previous exercise to include a location header for the newly created product. Using api.url_for is adviced.
Remember to test your solution by
running your Flask app
.

Sallitut tiedostojen nimet

Varoitus: Et ole kirjautunut sisään. Et voi vastata.

Generating Hypermedia

Moving from
view functions
to
resource classes
with methods for handling actions as
HTTP methods
was the more straightforward part of this exercise. Creating a
hypermedia
API is a somewhat bigger effort. The key to not breaking your hypermedia responses with changes in code is to avoid hardcoding hypermedia in your resource methods. In this section we will show you some ways how to do that through examples. If you'd rather achieve the same goal in some other way, we're not gonna hold it against you - as long you as achieve it.
Learning goals: How to use dictionary subclasses for creating hypermedia controls. How to include
JSON schema
in hypermedia responses.

Subclass Solution

In Mason the root type of a hypermedia response is
JSON
object which - as we have learned - is in most ways the equivalent of a Python dictionary. However if you go and define the entire response as a dictionary in each resource method separately, the likelihood of introducing inconsistencies is quite high. Furthermore the code becomes cumbersome to maintain. For any applications that produce JSON, a good development pattern is to create a dictionary subclass that includes a number of convenience methods that automatically manage integrity of the selected JSON format.
As stated before, our chosen hypermedia type for examples in this course is Mason. There are three special attributes in Mason JSON documents that we use commonly: "@controls", "@namespaces" and "@error". Just to give you an idea of what we're trying to avoid, here's how we would need to make a Mason document with one namespace and control with normal dictionaries (this example could be part of our Sensor resource's get method):
body = {}
body["@namespaces"] = {
    "senhub": {
        "name": "/sensorhub/link-relations/#"
    }
}
body["@controls"] = {
    "senhub:measurements": {
        "href": api.url_for(MeasurementCollection, sensor=sensor_name)
    }
}
# add some data about the sensor etc
Putting stuff like this - and usually in bigger numbers - is incredibly messy. What we want to achieve is, instead, something like this:
body = MasonBuilder()
body.add_namespace("senhub", "/sensorhub/link-relations/#")
body.add_control("senhub:measurements", api.url_for(MeasurementCollection, sensor=sensor_name))
# add some data about the sensor
Without doubt this looks much cleaner. The MasonBuilder class would take care of details about how exactly to add the namespace and contrl into the resulting document. If something about that changed, making the change in the class would also apply the change to all resource methods. So, how does this look on the class itself? Something like this:
class MasonBuilder(dict):

    def add_namespace(self, ns, uri):
        if "@namespaces" not in self:
            self["@namespaces"] = {}

        self["@namespaces"][ns] = {
            "name": uri
        }
    
    def add_control(self, ctrl_name, href, **kwargs):
        if "@controls" not in self:
            self["@controls"] = {}

        self["@controls"][ctrl_name] = kwargs
        self["@controls"][ctrl_name]["href"] = href
Observe how MasonBuilder extends the dict Python class, so the way of creating a MasonBuilder is exactly the same to create a dictionary using the dict class.
Implementation detail: if you have not seen **kwargs used before, this is a Python feature called packing/unpacking. It's a wildcard catch for keyword arguments given to the function/method when it's called: all such arguments will be packed into the kwargs dictionary. So when we call this method with method="POST" the kwargs will be end up like this: {"method": "POST"}. This feature is also used in the dict __init__ method (which we inherit), you can give it keyword arguments to initialize it with a bunch of keys.
Because each object should have only one "@controls" and "@namespaces" attributes, it makes sense to automatically create this when the first namespace/control is added. We can add a similar method for the "@error" attribute:
    def add_error(self, title, details):
       self["@error"] = {
            "@message": title,
            "@messages": [details],
        }
You can download the entire class with docstrings added from below. If you're using the more elaborate project structure, this class is something that definitely belongs into the utils.py file and should be imported to other modules with from sensorhub.utils import MasonBuilder.
masonbuilder.py
Since items in a colletion type resource can have their controls, you should serialize them as MasonBuilder instances instead of dictionaries. This way you can add controls to them just as effortlessly as you can to the root object. Let's take an example of how to add the very important "self" relation to each sensor in the sensors collection resource representation.
body = MasonBuilder(items=[])
for sensor in Sensor.query.all():
    item = MasonBuilder(
        name=sensor.name,
        model=sensor.model
    )
    item.add_control("self", api.url_for(Sensor, sensor=sensor.name))
    body["items"].append(item)

API Specific Subclasses

A generic
JSON
builder like the one we just implemented is nice, but doesn't solve the whole equation. Especially the add_control method is only truly adequate for GET methods. If we want to add a DELETE method, we would need to include more stuff into the method call - stuff that we want to avoid having to repeat:
body.add_control("senhub:delete", api.url_for(Sensor, sensor=sensor_name), method="DELETE")
Granted it's not a lot of extra typing involved here, but the fact that the correct
HTTP method
for deleting is DELETE is already incorporated in the link relation definition for "senhub:delete", so in a way we're typing the same information twice here. What's more desirable is probably something like this:
body.add_control_delete_sensor(sensor_name)
Because all controls that delete a sensor look alike, we have now hidden all the boring details inside the method, and parametrized the only difference between each individual control: the identifier of the sensor this control should delete. Even building the URI should be hidden because it's always done in the same way. For implementing this method and many more alike, we should define a new class that subclasses MasonBuilder - this way our generic builder stays intact. This new class, with this one method:
class SensorhubBuilder(MasonBuilder):

    def add_control_delete_sensor(self, sensor):
        self.add_control(
            "senhub:delete",
            href=api.url_for(Sensor, sensor=sensor),
            method="DELETE",
            "title"="Delete this sensor"
        )
With this we have now ensured that every control to delete a sensor will always be the same. We were even able to add the optional title attribute without adding more noise to resource methods. Or if you don't want to have a method to delete each different type of resource you could make something like this for a more universal delete control:
class SensorhubBuilder(MasonBuilder):

    def add_control_delete(self, href):
        self.add_control(
            "senhub:delete",
            href=href,
            method="DELETE",
            title="Delete this resource"
        )
Now the URI needs to be built on the calling end, and our title attribute is slightly less descriptive. Still, this solution has similar ease of maintenance as the more accurate one above. You can do a similar treatment to any number of controls in your API, and you probably should do so for the majority of them.

Dynamic Schemas, Static Methods

In exercise 2 we sung the praises of adding
JSON schemas
to our
hypermedia
controls. Schemas do have a nasty drawback: they are awfully verbose. If a control with like three attributes was already deemed something we'd rather not repeat in our code unnecessarily, a schema that's easily over ten lines of code is definitely something that must be written in only one place. It's also worth recalling that we have two uses for them: serialize them as parts of controls, and also to use them for validating request bodies sent by the client.
Schemas are generally needed for POST and PUT controls, and also any methods that use
query parameters
. However, the same schema is often referenced at least twice (POST and PUT) so it should not be hardcoded into any single add_control method. In fact it should not be put into a normal method at all, because sometimes we may want to retrieve the schema without having an instance of SensorhubBuilder available. Instead it should be returned by a static method, or built into a class attribute. We'll prefer static method just in the off-chance that one day some of these might need parametrization. Let's add a static method that builds sensor schema and returns it:
    @staticmethod
    def sensor_schema():
        schema = {
            "type": "object",
            "required": ["name", "model"]
        }
        props = schema["properties"] = {}
        props["name"] = {
            "description": "Sensor's unique name",
            "type": "string"
        }
        props["model"] = {
            "description": "Name of the sensor's model",
            "type": "string"
        }
        return schema
Implementation detail: a static method is a method that can be called without an instance of the class and it also doesn't usually refer to any of the class attributes (that's what class methods are for). In other words it's actually a function that's just been slapped on a class to keep things more organized. It can be called as self.sensor_schema() from normal methods within the same class. From the outside it's called as SensorhubBuilder.sensor_schema().
This static method could now be used in a control for creating new sensors (and similarly for modifying sensors):
    def add_control_add_sensor():
        self.add_control(
            "senhub:add-sensor",
            "/api/sensors/",
            method="POST",
            encoding="json",
            title="Add a new sensor",
            schema=self.sensor_schema()
        )

Inventory Builder

In this task you're going to get a bit more familiar with dictionary subclasses by implementing one yourself. Picking up this habit will make future work on our inventory management API much leaner.
Learning goals: How to implement a dictionary subclass that maintains a Mason
hypermedia
document using convenience methods for adding controls.
Before you begin:
If you haven't already done so, scroll up and download the mason builder class that we showed you. You should use this class as the base for this task. And by this we mean you should make your own class that uses MasonBuilder as its parent.
It's best to do this task by extending the code file from the previous task - these pieces must be in place for api.url_for to work. We also recommend that you change the model name of StorageItem to StorageEntry to avoid mixups later. Remember also that the MasonBuilder code should be included in the file you send.
Class: InventoryBuilder
For this task we want you to create a class that can be used to add hypermedia controls associated with the inventory manager's product resources. Your class should have a method the following methods for various
link relations
:
  • add_control_all_products
    • paramaters: -
    • rel: "storage:products-all"
    • Leads to the list of all products (GET /api/products/)
  • add_control_delete_product
    • parameters: product handle
    • rel: "storage:delete"
    • Deletes this product (DELETE /api/products/{handle}/)
  • add_control_add_product
    • paramaters: -
    • rel: "storage:add-product"
    • Creates a new product (POST /api/products/)
    • schema required
      • handle: string
      • weight: number
      • price: number
  • add_control_edit_product
    • parameters: product handle
    • rel: "edit"
    • Edits a product (PUT /api/products/{handle}/)
    • schema required (same as above)
About the Checker
The checker is dynamic. It constructs a JSON document using your InventoryBuilder and then attempts to generate requests based on the controls in that JSON document against a reference implementation of the inventory management API.

Sallitut tiedostojen nimet

Varoitus: Et ole kirjautunut sisään. Et voi vastata.
Another possible location that makes sense for static methods that return a schema would be the corresponding
model class
. If you get really into it, you can even generate the schema from the model. Here's a starting point if you want to look into it, or you could just write your own.

Responses and Errors

We briefly discussed Flask's
response object
in the Resource Locator task where we used it to set custom
headers
. We have now arrived at a stage where we should actually be using it for all responses. This is largely because we need to announce the content type of our
responses
, and this is done by using the mimetype keyword argument. Because we're using Mason, we need to set it to "application/vnd.mason+json". Since this will be repeated in every GET method, it'd be wise to make a constant of it (i.e. MASON = "application/vnd.mason+json"). From now on a typical 200 response would look like:
return Response(json.dumps(body), 200, mimetype=MASON)
We went back to using json.dumps because Response takes the response body as a string. We can also change all 201 and 204 responses accordingly. We already learned how to do the 201 response with Location header, and a 204 response is even simpler:
return Response(status=204)
We have now resolved issues regarding responses in the 200 range (i.e. successful operations). What about errors in the 400 range? Mason also defines what errors should look like. In fact, we actually already implemented the add_error method into our MasonBuilder dictionary subclass. However, even with that, returning an error becomes a multiline effort, and we don't really want that because most of it is boilerplate and resource methods typically return errors at multiple points of their execution. Let's make a convenience function for generating errors:
def create_error_response(status_code, title, message=None):
    resource_url = request.path
    body = MasonBuilder(resource_url=resource_url)
    body.add_error(title, message)
    body.add_control("profile", href=ERROR_PROFILE)
    return Response(json.dumps(body), status_code, mimetype=MASON)
This generates a Mason error messages with a title, and one message with more description about the problem. It also puts the resource URL into the response body, just in case the client forgot what it was trying to do (sometimes actually relevant, e.g. asynchronous use cases). Now instead of writing all that whenever an error is encountered in a resource method, we can just write:
return create_error_response(404, "Not found", "No sensor was found with the given name")

Static Parts of Hypermedia

In addition to generating
hypermedia
representations for
resources
, a fully functional hypermedia API should also serve some static content. Namely: link relations and resource
profiles
. Also if you have particularly large schemas and would rather serve them separately from resource representations, these should also be served as static content. For profiles and link relations you have two options: you can send them out as static files, or you can redirect to apiary.

As Static Files

If your project doesn't have a static folder yet, now's the time to create one. It's also recommended to create some subfolders to keep things organized, e.g.
static
├── profiles
└── schema
In order to use a static folder, it must be registered with Flask. This is done when initializing the app:
app = Flask(__name__, static_folder="static")
Static views are routed with @app.route. If you are storing profiles and such locally as html files, you can implement these views quite easily by using Flask's send_from_directory function which sends the contents of a file as the response body - you should add it to your growing from flask import line. With profiles you can use one route definition and view function for all the profiles, like this:
@app.route("/profiles/<resource>/")
def send_profile_html(resource):
    return send_from_directory(app.static_folder, "{}.html".format(resource))
The send_from_directory function is convenient enough that it will send a 404 response if the file is not found. Because link relation descriptions are not particularly lengthy they can be gathered into a single file, served in a similar manner:
@app.route("/sensorhub/link-relations/")
def send_link_relations_html():
    return send_from_directory(app.static_folder, "links-relations.html")
If you are using schema files, you can send them out in a similar manner. You will also need these URLs often in your code since almost all responses will include the namespace which requires the link relation URL, and likewise all resource representations have at least one profile link. Therefore you should probably at least introduce them as constants in your code, e.g.
SENSOR_PROFILE = "/profiles/sensor/"
MEASUREMENT_PROFILE = "/profiles/measurement/"
LINK_RELATIONS_URL = "/sensorhub/link-relations/"
If you are using the more elaborate project structure, consider putting these into their own file, e.g. constants.py.

As Redirects

You can also link to your apiary documentation by using redirect. Like send_from_directory, redirect is also imported from Flask. Likewise, these should be routed as normal routes. For example, to link to link relations in Apiary:
APIARY_URL = "https://yourproject.docs.apiary.io/#reference/"

@app.route("/sensorhub/link-relations/")
def redirect_to_apiary_link_rels():
    return redirect(APIARY_URL + "link-relations")
This should take the viewer to the Apiary documentation, and to the Link Relations heading. A caveat of this approach is that if your profiles do not have any methods, they will not have anchorable headings. You can use the same constants as we did with static files for these URLs.

Did You GET It?

We have spent quite a while doing various utilities but how does all of this actually look like in
resource class
methods? Let's look at what the GET method of a single sensor would look like in this brand new world.
class SensorItem(Resource):

    def get(self, sensor):
        db_sensor = Sensor.query.filter_by(name=sensor).first()
        if db_sensor is None:
            return create_error_response(404, "Not found", 
                "No sensor was found with the name {}".format(sensor)
            )
        
        body = SensorhubBuilder(
            name=db_sensor.name,
            model=db_sensor.model,
            location=db_sensor.location and db_sensor.location.name
        )
        body.add_namespace("senhub", LINK_RELATIONS_URL)
        body.add_control("self", api.url_for(SensorItem, sensor=sensor))
        body.add_control("profile", SENSOR_PROFILE)
        body.add_control_delete_sensor(sensor)
        body.add_control_modify_sensor(sensor)
        body.add_control_add_measurement(sensor)
        body.add_control("senhub:measurements",
            api.url_for(MeasurementCollection, sensor=sensor)
        )
        if db_sensor.location is not None:
            body.add_control("senhub:location", 
                api.url_for(LocationItem, location=db_sensor.location.sensor)
            )
        
        return Response(json.dumps(body), 200, mimetype=MASON)
Implementation note: the use of and in getting the location's name circumvents the need for conditional statement to check whether the sensor has a location or not. The evaluation of this statement ends immediately if the left hand operand is equivalent to False, which avoids the AttributeError we would otherwise get from the right hand side.
This looks rather pleasant. Just this one time, let's take a look at what we have avoided doing (deliberately avoiding every single nicety we've introduced so far):
class SensorItem(Resource):

    def get(self, sensor):
        db_sensor = Sensor.query.filter_by(name=sensor).first()
        if db_sensor is None:
            error = {
                "resource_url": request.path,
                "@error": {
                    "@message": "Not found",
                    "@messages": ["No sensor was found with name {}".format(sensor)]
                }
            }
            return Response(json.dumps(error), 404, mimetype="application/vnd.mason+json")
        
        body = {
            "name": db_sensor.name,
            "model": db_sensor.model,
            "location": db_sensor.location and db_sensor.location.name
        }
        body["@namespaces"] = {
            "senhub": {"name": "/sensorhub/link-relations/#"}
        }
        body["@controls"] = {
            "self": {"href": "/api/sensors/{}/".format(sensor)},
            "profile": {"href": "/profiles/sensor/"},
            "senhub:delete": {
                "href": "/api/sensors/{}/".format(sensor),
                "method": "DELETE",
                "title": "Delete this sensor."
            },
            "edit": {
                "href": "/api/sensors/{}/".format(sensor),
                "method": "PUT",
                "encoding": "json",
                "title": "Delete this sensor.",
                "schema": {
                    "type": "object",
                    "required": ["name", "model"]
                    "properties": {
                        "name": {
                            "description": "Sensor's unique name",
                            "type": "string"
                        },
                        "model": {
                            "description": "Name of the sensor's model",
                            "type": "string"
                        }
                    }
                }
            },
            "senhub:add-measurement": {
                "href": "/api/sensors/{}/measurements/".format(sensor),
                "method": "POST",
                "encoding": "json",
                "title": "Add a new measurement for this sensor",
                "schema": {
                    "type" "object",
                    "required": ["value"],
                    "properties": {
                        "value": {
                            "description": "Measured value.",
                            "type": "number"
                        },
                        "time": {
                            "description": "Measurement timestamp",
                            "type": "string",
                            "pattern": "^[0-9]{4}-[01][0-9]-[0-3][0-9]T[0-9]{2}:[0-5][0-9]:[0-5][0-9]Z$"
                        }
                    }
                }
            },
            "senhub:measurements": {
                "href": "/api/sensors/{}/measurements/".format(sensor),
                "title": "Measurements from this sensor"
            }
        }
        
        if db_sensor.location is not None:
            body["@controls"]["senhub:location"] = {
                "href": "/api/locations/{}/".format(db_sensor.location.name),
                "title": "This sensor's location."
            }
        
        return Response(json.dumps(body), 200, mimetype="application/vnd.mason+json")
Imagine a program full of methods like this one. Just don't do it late at night, might lose your sleep.
If you want to make an even leaner GET method, you can look into generating the data part of resource representation automatically from your models. One library that does this is Marshmallow. You can also go for a simpler option and write a serialize mehod for your
model classes
. The upside of this approach is that it allows you to maintain all data related code in the same place (i.e. the model). If you need to change your underlying models, there will be much less looking around to find places that need to be modified to conform with the change.

POST-it

Just to also show you the other side of the coin, here's the POST method for creating new sensors from the SensorCollection
resource
. Note the use of
JSON Schema
for validation of the
request body
. This requires an additional import as well: from jsonschema import validate, ValidationError. One nice bonus is that jsonschema gives rather detailed validation errors, and we can conveniently return them to the client with just str(e).
class SensorCollection(Resource):

    def post(self):
        if not request.json:
            return create_error_response(415, "Unsupported media type",
                "Requests must be JSON"
            )

        try:
            validate(request.json, Sensor.get_schema())
        except ValidationError as e:
            return create_error_response(400, "Invalid JSON document", str(e))

        sensor = Sensor(
            name=request.json["name"],
            model=request.json["model"],
        )

        try:
            db.session.add(sensor)
            db.session.commit()
        except IntegrityError:
            return create_error_response(409, "Already exists", 
                "Sensor with name '{}' already exists.".format(request.json["name"])
            )
        
        return Response(status=201, headers={
            "Location": api.url_for(SensorItem, sensor=request.json["name"])
        })

Inventory Engineer Finale

We're almost one with our very minimal inventory management API that has served us well for majority of the server side implementation tasks. As your final exam on the topic, we want you to implement one of each of the basic HTTP method types. Well, GET is covered twice - once for a collection and once for a single item. We will stick to managing products, inventory will be Someone Else's Problem.
Learning goals: how to implement basic
HTTP methods
for
resources classes
and return
hypermedia
from them.
Before you begin:
Everything we've done so far is useful for this task, so grab your previous submission as a basis.
Running your API
and testing it with the Restlet client is highly recommended in this task, don't rely only on the checker's output. Working on this task will be easier if you have something in the database. You can use the code below in the console (after creating your database) to create some products:
In [3]: for i in range(1, 4): 
   ...:     p = Product( 
   ...:         handle="test-product-{}".format(i), 
   ...:         price=10.5 * i, 
   ...:         weight=2.1 * i 
   ...:     ) 
   ...:     api.db.session.add(p) 
Entry Point
  • Route: "/api/"
You need to create an entry point that has one control: "storage:products-all" which should point to the products collection. Remember namespace. This is best done as a normal
view function
.
Resource: ProductCollection
  • Route: "/api/products/"
  • Methods:
    • GET - get list of all products (returns a Mason document)
    • POST - creates a new product
GET: this method retrieves all products from the database and forms a list of them, where each product is a dictionary with the same keys as the database column names (i.e. this part remains unchanged). This list will be included in the response Mason document as "items". Each item must also have the following controls:
  • "self" - points to the item
  • "profile" - points to the product profile ("/profiles/product/")
The rest of the document should have the following controls:
  • "self" - points to the collection itself
  • "storage:add-product" - a control for adding products, see previous task
Don't forget to add the namespace. The document is returned as JSON, with the status code 200 and using "application/vnd.mason+json" as the content type.
POST: creates a new product and returns 201 along with the Location header if successful. If not successful, will return either 415 if the request didn't have JSON as the content type, 400 if the JSON wasn't valid against the product schema and 409 if the product handle is already taken. All errors must be returned as Mason error documents, and must include "resource_url" as an attribute.
Resource: ProductItem
  • Route: "/api/products/<handle>/"
  • Methods:
    • GET - get information about the addressed product
    • PUT - modify product information
    • DELETE - delete the addressed product
GET: this method returns information about a product and controls related to it (or 404 error if the product doesn't exist). The information is put into the root document as attributes (same as database columns). In addition the following controls must be present:
  • "self" - points to the item
  • "profile" - points to the product profile ("/profiles/product/")
  • "collection" - points to the product collection
  • "edit" - a control for editing product information, see previous task
  • "storage:delete" - a control to delete the addressed product, see previous task
PUT: this method replaces the product's information with values from the
request body
. It returns 204 if the operation is successful. 415 if the request didn't have JSON as the content type, 404 if the product doesn't exist, 400 if the JSON wasn't valid against the product schema and 409 if trying to change the product handle to something that's already taken. All errors must be returned as Mason error documents, and must include "resource_url" as an attribute.
DELETE: this method deletes the product. Returns 204 if successful, 404 if the the product didn't exist to begin with. All errors must be returned as Mason error documents, and must include "resource_url" as an attribute.
Finishing Touches
Finally you need to provide views for
link relations
and
profiles
. These do not need to contain anything in particular as long as they exist and return plain text or HTML as strings. For this task only don't use static files or redirect.
About the Checker
The checker initializes your database with some test data. After this it will run a script that acts like a hypermedia client, traversing your API from the entry point onward using link relations to find resources and actions. The testing is sequential: if your API fails to return a resource representation, all future cases will probably fail as well because the client script cannot find the hypermedia controls it's looking for.

Sallitut tiedostojen nimet

Varoitus: Et ole kirjautunut sisään. Et voi vastata.
?
API Blueprint is a description language for REST APIs. Its primary categories are resources and their related actions (i.e. HTTP methods). It uses a relatively simple syntax. The advantage of using API Blueprint is the wide array of tools available. For example Apiary has a lot of features (interactive documentation, mockup server, test generation etc.) that can be utilized if the API is described in API Blueprint.
Another widely used alteranative for API Blueprint is OpenAPI.
Addressability is one of the key REST principles. It means that in an API everything should be presented as resources with URIs so that every possible action can be given an address. On the flipside this also means that every single address should always result in the same resource being accessed, with the same parameters. From the perspective of addressability, query parameters are part of the address.
Ajax is a common web technique. It used to be known as AJAX, an acronym for Asynchronous Javascript And XML but with JSON largely replacing XML, it become just Ajax. Ajax is used in web pages to make requests to the server without a page reload being triggered. These requests are asynchronous - the page script doesn't stop to wait for the response. Instead a callback is set to handle the response when it is received. Ajax can be used to make a request with any HTTP method.
  1. Kuvaus
  2. Examples
Anonymous functions are usually used as in-place functions to define a callback. They are named such because they are defined just like functions, but don't have a name. In JavaScript function definition returns the function as an object so that it can e.g. passed as an argument to another function. Generally they are used as one-off callbacks when it makes the code more readable to have the function defined where the callback is needed rather than somewhere else. A typical example is the forEach method of arrays. It takes a callback as its arguments and calls that function for each of its members. One downside of anonymous functions is that they function is defined anew every time, and this can cause significant overhead if performed constantly.
  1. Kuvaus
  2. Example
In Flask application context (app context for short) is an object that keeps tracks of application level data, e.g. configuration. You always need to have it when trying to manipulate the database etc. View functions will automatically have app context included, but if you want to manipulate the database or test functions from the interactive Python console, you need to obtain app context using a with statement.
Blueprint is a Flask feature, a way of grouping different parts of the web application in such a way that each part is registered as a blueprint with its own root URI. Typical example could be an admin blueprint for admin-related features, using the root URI /admin/. Inside a blueprint, are routes are defined relatively to this root, i.e. the route /users/ inside the admin blueprint would have the full route of /admin/users/.
Defines how data is processed in the application
Cross Origin Resource Sharing (CORS) is a relaxation mechanism for Same Origin Policy (SOP). Through CORS headers, servers can allow requests from external origins, what can be requested, and what headers can be included in those requests. If a server doesn't provide CORS headers, browsers will browsers will apply the SOP and refuse to make requests unless the origin is the same. Note that the primary purpose of CORS is to allow only certain trusted origins. Example scenario: a site with dubious script cannot just steal a user's API credentials from another site's cookies and make requests using them because the APIs CORS configuration doesn't allow requests from the site's origin. NOTE: this is not a mechanism to protect your API, it's to protect browser users from accessing your API unintentionally.
Callback is a function that is passed to another part of the program, usually as an argument, to be called when certain conditions are met. For instance in making Ajax requests, it's typical to register a callback for at least success and error situations. A typical feature of callbacks is that the function cannot decide its own parameters, and must instead make do with the arguments given by the part of the program that calls it. Callbacks are also called handlers. One-off callbacks are often defined as anonymous functions.
Piece of software that consumes or utilizes the functionality of a Web API. Some clients are controlled by humans, while others (e.g. crawlers, monitors, scripts, agents) have different degree of autonomy.
In databases, columns define the attributes of objects stored in a table. A column has a type, and can have additional properties such as being unique. If a row doesn't conform with the column types and other restrictions, it cannot be inserted into the table.
  1. Kuvaus
  2. Common keywords
In object relational mapping, column attributes are attributes in model classes that have been initialized as columns (e.g. in SQLAlchemy their initial value is obtained by initializing a Column). Each of these attributes corresponds to a column in the database table (that corresponds with the model class). A column attribute defines the column's type as well as additional properties (e.g. primary key).
Connectedness is a REST principle particularly related to hypermedia APIs. It states that there for each resource in the API, there must exist a path from every other resource to get there by following hypermedia links. Connectedness is easiest to analyze by creating an API state diagram.
Document Object Model (DOM) is an interface through which Javascript code can interact with the HTML document. It's a tree structure that follows the HTML's hierarchy, and each HTML tag has its own node. Through DOM manipulation, Javascript code can insert new HTML into anywhere, modify its contents or remove it. Any modifications to the DOM are updated into the web page in real time. Do note that since this is a rendering operation, it's very likely one of the most costly operations your code can do. Therefore changing the entire contents of an element at once is better than changing it e.g. one line at a time.
Database schema is the "blueprint" of the database. It defines what tables are contained in the database, and what columns are in each table, and what additional attributes they have. A database's schema can be dumped into an SQL file, and a database can also be created from a schema file. When using object relational mapping (ORM), the schema is constructed from model classes.
In HTML element refers to a single tag - most of the time including a closing tag and everything in between. The element's properties are defined by the tag, and any of the properties can be used to select that element from the document object model (DOM). Elements can contain other elements, which forms the HTML document's hierarchy.
In software testing, a fixture is a component that satisfies the preconditions required by tests. In web application testing the most common role for fixtures is to initialize the database into a state that makes testing possible. This generally involves creating a fresh database, and possibly populating it with some data. In this course fixtures are implemented using pytest's fixture architecture.
  1. Kuvaus
  2. Creating DB
  3. Starting the App
This term contains basic instructions about setting up and running Flask applications. See the term tabs "Creating DB" and "Starting the App". For all instructions to work you need to be in the folder that contains your app.
In database terminology, foreign key means a column that has its value range determined by the values of a column in another table. They are used to create relationships between tables. The foreign key column in the target table must be unique.
For most hypermedia types, there exists a generic client. This is a client program that constructs a navigatable user interface based on hypermedia controls in the API, and can usually also generate data input forms. The ability to use such clients for testing and prototyping is one of the big advantages of hypermedia.
HTTP method is the "type" of an HTTP request, indicating what kind of an action the sender is intending to do. In web applications by far the most common method is GET which is used for retrieving data (i.e. HTML pages) from the server. The other method used in web applications is POST, used in submitting forms. However, in REST API use cases, PUT and DELETE methods are also commonly used to modify and delete data.
HTTP request is the entirety of the requets made by a client to a server using the HTTP protocol. It includes the request URL, request method (GET, POST etc.), headers and request body. In Python web frameworks the HTTP request is typically turned into a request object.
Headers are additional information fields included in HTTP requests and responses. Typical examples of headers are content-type and content-length which inform the receiver how the content should be interpreted, and how long it should be. In Flask headers are contained in the request.headers attribute that works like a dictionary.
Host part is the part of URL that indicates the server's address. For example, lovelace.oulu.fi is the host part. This part determines where (i.e. which IP address) in the world wide web the request is sent.
In API terminology hypermedia means additional information that is added on top of raw data in resource representations. It's derived from hypertext - the stuff that makes the world wide web tick. The purpose of the added hypermedia is to inform the client about actions that are available in relation to the resource they requested. When this information is conveyed in the representations sent by the API, the client doesn't need to know how to perform these actions beforehand - it only needs to parse them from the response.
Instance folder is a Flask feature. It is intended for storing files that are needed when running the Flask application, but should not be in the project's code repository. Primary example of this is the prodcution configuration file which differs from installation to installation, and generally should remain unchanged when the application code is updated from the repository. The instance path can be found from the application context: app.instance_path. Flask has a reasonable default for it, but it can also be set manually when calling Flask constuctor by adding the instance_path keyword argument. The path should be written as absolute in this case.
  1. Kuvaus
  2. Serializing / Parsing
JavaScript Object Notation (JSON) is a popular document format in web development. It's a serialized representation of a data structure. Although the representation syntax originates from JavaScript, It's almost identical to Python dictionaries and lists in formatting and structure. A JSON document conists of key-value pairs (similar to Python dictionaries) and arrays (similar to Python lists). It's often used in APIs, and also in AJAX calls on web sites.
JSON schema is a JSON document that defines the validity criteria for JSON documents that fall under the schema. It defines the type of the root object, and types as well as additional constraints for attributes, and which attributes are required. JSON schemas serve two purposes in this course: clients can use them to generate requests to create/modify resources, and they can also be used on the API end to validate incoming requests.
  1. Kuvaus
  2. Common MIME types
MIME type is a standard used for indicating the type of a document.In web development context it is placed in the Content-Type header. Browsers and servers the MIME type to determine how to process the request/response content. On this course the MIME type is in most cases application/json.
Database migration is a process where an existing database is updated with a new database schema. This is done in a way that does not lose data. Some changes can be migrated automatically. These include creation of new tables, removal of columns and adding nullable columns. Other changes often require a migration script that does the change in multiple steps so that old data can be transformed to fit the new schema. E.g. adding a non-nullable column usually involves adding it first as nullable, then using a piece of code to determine values for each row, and finally setting the column to non-nullable.
  1. Kuvaus
  2. Example
In ORM terminology, a model class is a program level class that represents a database table. Instances of the class represent rows in the table. Creation and modification operations are performed using the class and instances. Model classes typically share a common parent (e.g. db.Model) and table columns are defined as class attributes with special constuctors (e.g. db.Column).
Object relational mapping is a way of abstracting database use. Database tables are mapped to programming language classes. These are usually called models. A model class declaration defines the table's structure. When rows from the database table are fetched, they are represented as instances of the model class with columns as attributes. Likewise new rows are created by making new instances of the model class and committing them to the database. This course uses SQLAlchemy's ORM engine.
In database terminology primary key refers to the column in a table that's intended to be the primary way of identifying rows. Each table must have exactly one, and it needs to be unique. This is usually some kind of a unique identifier associated with objects presented by the table, or if such an identifier doesn't exist simply a running ID number (which is incremented automatically).
Profile is metadata about a resource. It's a document intended for client developers. A profile gives meaning to each word used in the resource representation be it link relation or data attribute (also known as semantic descriptors). With the help of profiles, client developers can teach machine clients to understand resource representations sent by the API. Note that profiles are not part of the API and are usually served as static HTML documents. Resource representations should always contain a link to their profile.
In database terminology, query is a command sent to the database that can fetch or alter data in the database. Queries use written with a script-like language. Most common is the structured query language (SQL). In object relational mapping, queries are abstracted behind Python method calls.
  1. Kuvaus
  2. Example
Query parameters are additional parameters that are included in a URL. You can often see these in web searches. They are the primary mechanism of passing arbitrary parameters with an HTTP request. They are separated from the actual address by ?. Each parameter is written as a key=value pair, and they are separated from each other by &. In Flask applications they can be found from request.args which works like a dictionary.
  1. Kuvaus
  2. Examples
Regular expressions are used in computing to define matching patterns for strings. In this course they are primarily used in validation of route variables, and in JSON schemas. Typical features of regular expressions are that they look like a string of garbage letters and get easily out of hand if you need to match something complex. They are also widely used in Lovelace text field exercises to match correct (and incorrect) answers.
In this course request referes to HTTP request. It's a request sent by a client to an HTTP server. It consists of the requested URL which identifies the resource the client wants to access, a method describing what it wants to do with the resource. Requests also include headers which provide further context information, and possihby a request body that can contain e.g. a file to upload.
  1. Kuvaus
  2. Accessing
In an HTTP request, the request body is the actual content of the request. For example when uploading a file, the file's contents would be contained within the request body. When working with APIs, request body usually contains a JSON document. Request body is mostly used with POST, PUT and PATCH requests.
  1. Kuvaus
  2. Getting data
Request object is related to web development frameworks. It's a programming language object representation of the HTTP request made to the server. It has attributes that contain all the information contained within the request, e.g. method, url, headers, request body. In Flask the object can be imported from Flask to make it globally available.
in RESTful API terminology, a resource is anything that is interesting enough that a client might want to access it. A resource is a representation of data that is stored in the API. While they usually represent data from the database tables it is important to understand that they do not have a one-to-one mapping to database tables. A resource can combine data from multiple tables, and there can be multiple representations of a single table. Also things like searches are seen as resources (it does, after all, return a filtered representation of data).
Resource classes are introduced in Flask-RESTful for implementing resources. They are inherited from flask_restful.Resource. A resource class has a view-like method for each HTTP method supported by the resource (method names are written in lowercase). Resources are routed through api.add_resource which routes all of the methods to the same URI (in accordance to REST principles). As a consequence, all methods must also have the same parameters.
In this course response refers to HTTP response, the response given by an HTTP server when a request is made to it. Reponses are made of a status code, headers and (optionally) response body. Status code describes the result of the transaction (success, error, something else). Headers provide context information, and response body contains the document (e.g. HTML document) returned by the server.
Response body is the part of HTTP response that contains the actual data sent by the server. The body will be either text or binary, and this information with additional type instructions (e.g. JSON) are defined by the response's Content-type header. Only GET requests are expected to return a response body on a successful request.
Response object is the client side counterpart of request object. It is mainly used in testing: the Flask test client returns a response object when it makes a "request" to the server. The response object has various attributes that represent different parts of an actual HTTP response. Most important are usually status_code and data.
In database terminology, rollback is the cancellation of a database transaction by returning the database to a previous (stable) state. Rollbacks are generally needed if a transaction puts the database in an error state. On this course rollbacks are generally used in testing after deliberately causing errors.
  1. Kuvaus
  2. Routing in Flask
  3. Reverse routing
  4. Flask-RESTful routing
URL routing in web frameworks is the process in which the framework transforms the URL from an HTTP request into a Python function call. When routing, a URL is matched against a sequence of URL templates defined by the web application. The request is routed to the function registered for the first matching URL template. Any variables defined in the template are passed to the function as parameters.
In relational database terminology, row refers to a single member of table, i.e. one object with properties that are defined by the table's columns. Rows must be uniquely identifiable by at least one column (the table's primary key).
SQL (structured query language) is a family of languages that are used for interacting with databases. Queries typically involve selecting a range of data from one or more tables, and defining an operation to perform to it (such as retrieve the contents).
In web applications static content refers to content that is served from static files in the web server's hard drive (or in bigger installations from a separate media server). This includes images as well as javascript files. Also HTML files that are not generated from templates are static content.
In database terminology, a table is a collection of similar items. The attributes of those items are defined by the table's columns that are declared when the table is created. Each item in a table is contained in a row.
In software testing, test setup is a procedure that is undertaken before each test case. It prepares preconditions for the test. On this course this is done with pytest's fixtures.
In software testing, test teardown is a process that is undertaken after each test case. Generally this involves clearing up the database (e.g. dropping all tables) and closing file descriptors, socket connections etc. On this course pytest fixtures are used for this purpose.
Universal resource identifier (URI) is basically what the name says: it's a string that unambiguously identifies a resource, thereby making it addressable. In APIs everything that is interesting enough is given its own URI. URLs are URIs that specify the exact location where to find the resource which means including protocol (http) and server part (e.g. lovelace.oulu.fi) in addition to the part that identifies the resource within the server (e.g. /ohjelmoitava-web/programmable-web-project-spring-2019).
  1. Kuvaus
  2. Type converters
  3. Custom converters
URL template defines a range of possible URLs that all lead to the same view function by defining variables. While it's possible for these variables to take arbitrary values, they are more commonly used to select one object from a group of similar objects, i.e. one user's profile from all the user profiles in the web service (in Flask: /profile/<username>. If a matching object doesn't exist, the default response would be 404 Not Found. When using a web framework, variables in the URL template are usually passed to the corresponding view function as arguments.
In database terminology, unique constraint is a what ensures the uniqueness of each row in a table. Primary key automatically creates a unique constraint, as do unique columns. A unique constraint can also be a combination of columns so that each combination of values between these columns is unique. For example, page numbers by themselves are hardly unique as each book has a first page, but a combination of book and page number is unique - you can only have one first page in a book.
  1. Kuvaus
  2. Registering
View functions are Python functions (or methods) that are used for serving HTTP requests. In web applications that often means rendering a view (i.e. a web page). View functions are invoked from URLs by routing. A view function always has application context.
Interface, implemented using web technologies, that exposes a functionality in a remote machine (server). By extension Web API is the exposed functionality itself.