Sådan oprettes en JSON API med Python

JSON API-specifikationen er en effektiv måde at muliggøre kommunikation mellem klient og server på. Det specificerer strukturen på anmodninger og svar sendt mellem de to ved hjælp af JSON-formatet.

Som dataformat har JSON fordelene ved at være let og læsbar. Dette gør det meget nemt at arbejde med hurtigt og produktivt. Specifikationen er designet til at minimere antallet af anmodninger og mængden af ​​data, der skal sendes mellem klient og server.

Her kan du lære at oprette en grundlæggende JSON API ved hjælp af Python og Flask. Derefter viser resten af ​​artiklen dig, hvordan du afprøver nogle af de funktioner, som JSON API-specifikationen har at tilbyde.

Flask er et Python-bibliotek, der giver en 'mikroramme' til webudvikling. Det er fantastisk til hurtig udvikling, da det kommer med en enkel, men alligevel udvidelig kernefunktionalitet.

Et virkelig grundlæggende eksempel på, hvordan man sender et JSON-lignende svar ved hjælp af Flask, vises nedenfor:

from flask import Flask app = Flask(__name__) @app.route('/') def example(): return '{"name":"Bob"}' if __name__ == '__main__': app.run()

Denne artikel bruger to tilføjelser til Flask:

  • Flask-REST-JSONAPI hjælper med at udvikle en API, der følger JSON API-specifikationen nøje.
  • Flask-SQLAlchemy bruger SQLAlchemy til at gøre oprettelse og interaktion med en simpel database meget ligetil.

Det store billede

Det endelige mål er at oprette en API, der muliggør interaktion på klientsiden med en underliggende database. Der vil være et par lag mellem databasen og klienten - et dataabstraktionslag og et ressourcehåndteringslag.

Her er en oversigt over de involverede trin:

  1. Definer en database ved hjælp af Flask-SQLAlchemy
  2. Opret en dataabstraktion med Marshmallow-JSONAPI
  3. Opret ressourceforvaltere med Flask-REST-JSONAPI
  4. Opret URL-slutpunkter, og start serveren med Flask

Dette eksempel bruger et simpelt skema, der beskriver moderne kunstnere og deres forhold til forskellige kunstværker.

Installer alt

Inden du kommer i gang, skal du konfigurere projektet. Dette indebærer oprettelse af et arbejdsområde og virtuelt miljø, installation af de krævede moduler og oprettelse af de vigtigste Python- og databasefiler til projektet.

Fra kommandolinjen skal du oprette en ny mappe og navigere inde.

$ mkdir flask-jsonapi-demo $ cd flask-jsonapi-demo/

Det er god praksis at oprette virtuelle miljøer til hvert af dine Python-projekter. Du kan springe dette trin over, men det anbefales stærkt.

$ python -m venv .venv $ source .venv/bin/activate 

Når dit virtuelle miljø er oprettet og aktiveret, kan du installere de moduler, der er nødvendige for dette projekt.

$ pip install flask-rest-jsonapi flask-sqlalchemy

Alt hvad du har brug for installeres som kravene til disse to udvidelser. Dette inkluderer selve Flask og SQLAlchemy.

Det næste trin er at oprette en Python-fil og database til projektet.

$ touch application.py artists.db

Opret databaseskemaet

Her begynder du at ændre for application.pyat definere og oprette databaseskemaet til projektet.

Åbn application.pyi din foretrukne teksteditor. Start med at importere nogle moduler. For at gøre klarheden importeres moduler med det samme.

Opret derefter et objekt kaldet appsom en forekomst af Flask-klassen.

Brug derefter SQLAlchemy til at oprette forbindelse til den databasefil, du oprettede. Det sidste trin er at definere og oprette en kaldet tabel artists.

from flask import Flask from flask_sqlalchemy import SQLAlchemy # Create a new Flask application app = Flask(__name__) # Set up SQLAlchemy app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////artists.db' db = SQLAlchemy(app) # Define a class for the Artist table class Artist(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String) birth_year = db.Column(db.Integer) genre = db.Column(db.String) # Create the table db.create_all()

Oprettelse af et abstraktionslag

Det næste trin bruger Marshmallow-JSONAPI-modulet til at skabe en logisk dataabstraktion over de netop definerede tabeller.

Årsagen til at skabe dette abstraktionslag er enkel. Det giver dig mere kontrol over, hvordan dine underliggende data eksponeres via API'en. Tænk på dette lag som en linse, hvorigennem API-klienten kan se de underliggende data tydeligt og kun de bits, de har brug for at se.

I nedenstående kode defineres dataabstraktionslaget som en klasse, der arver fra Marshmallow-JSONAPIs Schemaklasse. Det giver adgang via API'en til både enkeltoptegnelser og flere poster fra kunstnertabellen.

Inde i denne blok Metadefinerer klassen nogle metadata. Specifikt vil navnet på URL-slutpunktet til interaktion med enkelte poster være artist_one, hvor hver kunstner identificeres med en URL-parameter . Navnet på slutpunktet til interaktion med mange poster vil være artist_many.

De resterende definerede attributter vedrører kolonnerne i kunstnertabellen. Her kan du kontrollere yderligere, hvordan hver eksponeres via API'en.

For eksempel, når du sender POST-anmodninger om at tilføje nye kunstnere til databasen, kan du sikre dig, at namefeltet er obligatorisk ved at indstille required=True.

Og hvis du af en eller anden grund ikke ønskede, at birth_yearfeltet skulle returneres, når du foretager GET-anmodninger, kan du angive det ved at indstille load_only=True.

from marshmallow_jsonapi.flask import Schema from marshmallow_jsonapi import fields # Create data abstraction layer class ArtistSchema(Schema): class Meta: type_ = 'artist' self_view = 'artist_one' self_view_kwargs = {'id': ''} self_view_many = 'artist_many' id = fields.Integer() name = fields.Str(required=True) birth_year = fields.Integer(load_only=True) genre = fields.Str() 

Opret ressourceadministratorer og URL-slutpunkter

Det sidste stykke af puslespillet er at oprette en ressourcemanager og et tilsvarende slutpunkt for hver af ruterne / kunstnerne og / kunstnerne / id'et.

Hver ressourcemanager er defineret som en klasse, der arver fra klassen Flask-REST-JSONAPI ResourceListog ResourceDetail.

Her tager de to attributter. schemabruges til at indikere det dataabstraheringslag, som ressourcemanageren bruger, og data_layerangiver den session og datamodel, der skal bruges til datalaget.

Dernæst definer apisom en forekomst af Flask-REST-JSONAPI's Apiklasse, og opret ruterne til API'en med api.route(). Denne metode tager tre argumenter - dataabstraktionslagsklassen, slutpunktsnavnet og URL-stien.

The last step is to write a main loop to launch the app in debug mode when the script is run directly. Debug mode is great for development, but it is not suitable for running in production.

# Create resource managers and endpoints from flask_rest_jsonapi import Api, ResourceDetail, ResourceList class ArtistMany(ResourceList): schema = ArtistSchema data_layer = {'session': db.session, 'model': Artist} class ArtistOne(ResourceDetail): schema = ArtistSchema data_layer = {'session': db.session, 'model': Artist} api = Api(app) api.route(ArtistMany, 'artist_many', '/artists') api.route(ArtistOne, 'artist_one', '/artists/') # main loop to run app in debug mode if __name__ == '__main__': app.run(debug=True)

Make GET and POST requests

Now you can start using the API to make HTTP requests. This could be from a web browser, or from a command line tool like curl, or from within another program (e.g., a Python script using the Requests library).

To launch the server, run the application.py script with:

$ python application.py

In your browser, navigate to //localhost:5000/artists.  You will see a JSON output of all the records in the database so far. Except, there are none.

To start adding records to the database, you can make a POST request. One way of doing this is from the command line using curl. Alternatively, you could use a tool like Insomnia, or perhaps code up a simple HTML user interface that posts data using a form.

With curl, from the command line:

curl -i -X POST -H 'Content-Type: application/json' -d '{"data":{"type":"artist", "attributes":{"name":"Salvador Dali", "birth_year":1904, "genre":"Surrealism"}}}' //localhost:5000/artists

Now if you navigate to //localhost:5000/artists, you will see the record you just added. If you were to add more records, they would all show here as well, as this URL path calls the artists_many endpoint.

To view just a single artist by their id number, you can navigate to the relevant URL. For example, to see the first artist, try //localhost:5000/artists/1.

Filtering and sorting

One of the neat features of the JSON API specification is the ability to return the response in more useful ways by defining some parameters in the URL. For instance, you can sort the results according to a chosen field, or filter based on some criteria.

Flask-REST-JSONAPI comes with this built in.

To sort artists in order of birth year, just navigate to //localhost:5000/artists?sort=birth_year. In a web application, this would save you from needing to sort results on the client side, which could be costly in terms of performance and therefore impact the user experience.

Filtering is also easy. You append to the URL the criteria you wish to filter on, contained in square brackets. There are three pieces of information to include:

  • "name" - the field you are filtering by (e.g., birth_year)
  • "op" - the filter operation ("equal to", "greater than", "less than" etc.)
  • "val" - the value to filter against (e.g., 1900)

For example, the URL below retrieves artists whose birth year is greater than 1900:

//localhost:5000/artists?filter=[{"name":"birth_year","op":"gt","val":1900}]

This functionality makes it much easier to retrieve only relevant information when calling the API. This is valuable for improving performance, especially when retrieving potentially large volumes of data over a slow connection.

Pagination

Another feature of the JSON API specification that aids performance is pagination. This is when large responses are sent over several "pages", rather than all in one go. You can control the page size and the number of the page you request in the URL.

So, for example, you could receive 100 results over 10 pages instead of loading all 100 in one go. The first page would contain results 1-10, the second page would contain results 11-20, and so on.

To specify the number of results you want to receive per page, you can add the parameter ?page[size]=X to the URL, where X is the number of results. Flask-REST-JSONAPI uses 30 as the default page size.

To request a given page number, you can add the parameter ?page[number]=X, where is the page number. You can combine both parameters as shown below:

//localhost:5000/artists?page[size]=2&page[number]=2

This URL sets the page size to two results per page, and asks for the second page of results. This would return the third and fourth results from the overall response.

Relationships

Almost always, data in one table will be related to data stored in another. For instance, if you have a table of artists, chances are you might also want a table of artworks. Each artwork is related to the artist who created it.

The JSON API specification allows you to work with relational data easily, and the Flask-REST-JSONAPI lets you take advantage of this. Here, this will be demonstrated by adding an artworks table to the database, and including relationships between artist and artwork.

To implement the artworks example, it will be necessary to make a few changes to the code in application.py.

First, make a couple of extra imports, then create a new table which relates each artwork to an artist:

from marshmallow_jsonapi.flask import Relationship from flask_rest_jsonapi import ResourceRelationship # Define the Artwork table class Artwork(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String) artist_id = db.Column(db.Integer, db.ForeignKey('artist.id')) artist = db.relationship('Artist', backref=db.backref('artworks'))

Next, rewrite the abstraction layer:

# Create data abstraction class ArtistSchema(Schema): class Meta: type_ = 'artist' self_view = 'artist_one' self_view_kwargs = {'id': ''} self_view_many = 'artist_many' id = fields.Integer() name = fields.Str(required=True) birth_year = fields.Integer(load_only=True) genre = fields.Str() artworks = Relationship(self_view = 'artist_artworks', self_view_kwargs = {'id': ''}, related_view = 'artwork_many', many = True, schema = 'ArtworkSchema', type_ = 'artwork') class ArtworkSchema(Schema): class Meta: type_ = 'artwork' self_view = 'artwork_one' self_view_kwargs = {'id': ''} self_view_many = 'artwork_many' id = fields.Integer() title = fields.Str(required=True) artist_id = fields.Integer(required=True) 

This defines an abstraction layer for the artwork table, and adds a relationship between artist and artwork to the ArtistSchema class.

Next, define new resource managers for accessing artworks many at once and one at a time, and also for accessing the relationships between artist and artwork.

class ArtworkMany(ResourceList): schema = ArtworkSchema data_layer = {'session': db.session, 'model': Artwork} class ArtworkOne(ResourceDetail): schema = ArtworkSchema data_layer = {'session': db.session, 'model': Artwork} class ArtistArtwork(ResourceRelationship): schema = ArtistSchema data_layer = {'session': db.session, 'model': Artist}

Finally, add some new endpoints:

api.route(ArtworkOne, 'artwork_one', '/artworks/') api.route(ArtworkMany, 'artwork_many', '/artworks') api.route(ArtistArtwork, 'artist_artworks', '/artists//relationships/artworks')

Run application.py and trying posting some data from the command line via curl:

curl -i -X POST -H 'Content-Type: application/json' -d '{"data":{"type":"artwork", "attributes":{"title":"The Persistance of Memory", "artist_id":1}}}' //localhost:5000/artworks

This will create an artwork related to the artist with id=1.

In the browser, navigate to //localhost:5000/artists/1/relationships/artworks. This should show the artworks related to the artist with id=1. This saves you from writing a more complex URL with parameters to filter artworks by their artist_id field. You can quickly list all the relationships between a given artist and their artworks.

Another feature is the ability to include related results in the response to calling the artists_one endpoint:

//localhost:5000/artists/1?include=artworks

This will return the usual response for the artists endpoint, and also results for each of that artist's artworks.

Sparse Fields

One last feature worth mentioning - sparse fields. When working with large data resources with many complex relationships, the response sizes can blow up real fast. It is helpful to only retrieve the fields you are interested in.

The JSON API specification lets you do this by adding a fields parameter to the URL. For example URL below gets the response for a given artist and their related artworks. However, instead of returning all the fields for the given artwork, it returns only the title.

//localhost:5000/artists/1?include=artworks&fields[artwork]=title

This is again very helpful for improving performance, especially over slow connections. As a general rule, you should only make requests to and from the server with the minimal amount of data required.

Final remarks

JSON API-specifikationen er en meget nyttig ramme til afsendelse af data mellem server og klient i et rent, fleksibelt format. Denne artikel har givet et overblik over, hvad du kan gøre med det, med et fungeret eksempel i Python ved hjælp af Flask-REST-JSONAPI-biblioteket.

Så hvad vil du gøre nu? Der er mange muligheder. Eksemplet i denne artikel har været et simpelt proof-of-concept med kun to tabeller og et enkelt forhold mellem dem. Du kan udvikle en applikation så sofistikeret som du vil og oprette en kraftfuld API til at interagere med den ved hjælp af alle de værktøjer, der findes her.

Tak for læsningen, og fortsæt kodningen i Python!