Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Documentation-Driven Approach to Building APIs

A Documentation-Driven Approach to Building APIs

Django REST Framework (DRF) does for REST APIs what Django does for web applications more generally. That is, it provides a powerful, flexible framework to help quickly building applications. OpenAPI (formerly Swagger) provides a machine-readable schema for describing the REST APIs made possible by the likes of DRF. When combined, they allow you to do some pretty neat things, which we're going to cover in this talk:

- Automatic generation of schemas from your API
- Validation of APIs using schemas
- Documentation!
- The competition

Let's see if OpenAPI really is the one API schema to rule them all... or something like that.

Stephen Finucane

May 13, 2020
Tweet

More Decks by Stephen Finucane

Other Decks in Technology

Transcript

  1. A DOCUMENTATION-DRIVEN APPROACH TO BUILDING APIS USING DJANGO REST FRAMEWORK

    AND OPENAPI Stephen Finucane (@stephenfin) Python Ireland Remote MeetUp, May 2020
  2. ABOUT ME Senior Software Engineer at Red Hat Working on

    OpenStack since ~2015 Working on Python for even longer
  3. AGENDA Intro to Django and Django REST Framework Intro to

    OpenAPI Generating an OpenAPI schema with DRF Validating a DRF-based API with OpenAPI Documenting Your API Wrap Up
  4. Django is a high-level Python Web framework that encourages rapid

    development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development, so you can focus on writing your app without needing to reinvent the wheel. It’s free and open source.
  5. Django REST framework is a powerful and flexible toolkit for

    building Web APIs that includes a web browsable API, serialization that supports both ORM and non-ORM data sources, extensive documentation and a large community.
  6. $ cd myapp $ python manage.py startapp core $ ls

    core/ __init__.py admin.py apps.py migrations/ __init__.py models.py tests.py views.py
  7. class Post(models.Model): title = models.CharField(max_length=255) date = models.DateTimeField(auto_now_add=True) author =

    models.ForeignKey( User, on_delete=models.CASCADE, ) body = models.TextField() mysite/core/models.py
  8. class PostSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Post fields = ['url',

    'title', 'date', 'author', 'body'] class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User fields = ['url', 'username', 'email'] mysite/core/serializers.py
  9. class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer permission_classes =

    [ permissions.IsAuthenticatedOrReadOnly] class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all().order_by('-date_joined') serializer_class = UserSerializer permission_classes = [permissions.IsAuthenticated] mysite/core/views.py
  10. $ curl -s http://localhost:8000/posts/ | python -m json.tool { "count":

    0, "next": null, "previous": null, "results": [] }
  11. $ curl -s http://localhost:8000/posts/ | python -m json.tool { "count":

    1, "next": null, "previous": null, "results": [ { "url": "http://localhost:8000/posts/1/", "title": "Hello, world", "date": "2020-05-13T09:58:54.805866Z", "author": "http://localhost:8000/users/1/", "body": "This is my first post." } ] }
  12. The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to

    RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined, a consumer can understand and interact with the remote service with a minimal amount of implementation logic.
  13. openapi: 3.0.0 info: title: Sample API version: 0.1.9 servers: -

    url: http://api.example.com/v1 paths: /users: get: summary: Returns a list of users. responses: '200': # status code content: application/json: schema: type: array items: type: string
  14. $ python manage.py generateschema openapi: 3.0.2 info: title: '' version:

    '' paths: /users/: get: operationId: listUsers description: API endpoint that allows users to be viewed or edited. parameters: - name: page required: false description: A page number within the paginated result set. schema: type: integer responses: # ...
  15. # ... responses: '200': content: application/json: schema: type: object properties:

    count: type: integer example: 123 next: type: string nullable: true previous: type: string nullable: true # ...
  16. urlpatterns = [ path('', include(router.urls)), path('openapi', get_schema_view( title='Your Project', description='API

    for all things …', version='1.0.0' ), name='openapi-schema'), path('swagger-ui/', TemplateView.as_view( template_name='swagger-ui.html', extra_context={'schema_url': 'openapi-schema'} ), name='swagger-ui'), ] mysite/mysite/urls.py
  17. extensions = [ 'sphinxcontrib.redoc', ] redoc = [ { 'name':

    'Sample API', 'page': 'api/index', 'spec': 'openapi.yml', 'embed': True, }, ] mysite/docs/conf.py
  18. ROOT_DIR = os.path.join(os.path.dirname(__file__), os.pardir) SCHEMA = os.path.join(ROOT_DIR, 'docs', 'openapi.yml') class

    SchemaValidationTests(TestCase): def test_validate_schema(self): with open(SCHEMA) as fh: schema = yaml.safe_load(fh) openapi_spec_validator.validate_spec(schema) mysite/core/tests.py
  19. $ python manage.py test core Creating test database for alias

    'default'... System check identified no issues (0 silenced). . -------------------------------------------------------------------- Ran 1 test in 0.114s OK Destroying test database for alias 'default'...
  20. from django.test.client import RequestFactory from openapi_core.validation.request.validators import ( RequestValidator) from

    openapi_core.contrib.django import DjangoOpenAPIRequest request_factory = RequestFactory() django_request = request_factory.get('/posts/') openapi_request = DjangoOpenAPIRequest(django_request) validator = RequestValidator(spec) result = validator.validate(openapi_request) assert not result.errors, result.errors
  21. WRAP UP Use Django + Django REST Framework for fast

    APIs Generate or handwrite OpenAPI schemas Use these schemas for documentation Use these schemas for validation (server and client)
  22. A DOCUMENTATION-DRIVEN APPROACH TO BUILDING APIS USING DJANGO REST FRAMEWORK

    AND OPENAPI Stephen Finucane (@stephenfin) Python Ireland Remote MeetUp, May 2020