When Python meets GraphQL Managing contributor identities in your - - PowerPoint PPT Presentation

when python meets graphql
SMART_READER_LITE
LIVE PREVIEW

When Python meets GraphQL Managing contributor identities in your - - PowerPoint PPT Presentation

When Python meets GraphQL Managing contributor identities in your Open-source project FOSDEM 2020 Python DevRoom share this slide! @mghfdez About me My name is Miguel-ngel Fernndez Working at Bitergia, part of the Engineering team


slide-1
SLIDE 1

When Python meets GraphQL

FOSDEM 2020 Python DevRoom

share this slide! @mghfdez

Managing contributor identities in your Open-source project

slide-2
SLIDE 2

About me

share this slide! @mghfdez

My name is Miguel-Ángel Fernández Working at Bitergia, part of the Engineering team Software developer... … also involved in stuff related with data and metrics

slide-3
SLIDE 3

share this slide! @mghfdez

slide-4
SLIDE 4

How can I measure my project?

share this slide! @mghfdez

How many contributors do we have ? How many companies are contributing to my project?

slide-5
SLIDE 5

share this slide! @mghfdez

Photo credit: juliooliveiraa

Tom Riddle Affiliated to Slytherin, Hogwarts

It’s all about identities

slide-6
SLIDE 6

share this slide! @mghfdez

Photo credit: James Seattle

Lord Voldemort Working as a freelance (dark) wizard

It’s all about identities

slide-7
SLIDE 7

Wait… they are the same person!

share this slide! @mghfdez

Photo credit: juliooliveiraa Photo credit: James Seattle

slide-8
SLIDE 8

share this slide! @mghfdez

Manrique López <jsmanrique@bitergia.com> Jose Manrique López de la Fuente <jsmanrique@gmail.com> Manrique López <jsmanrique@gmail.com> jsmanrique jsmanrique@gmail.com jsmanrique@bitergia.com correo@jsmanrique.es jsmanrique jsmanrique@bitergia.com 02/2005 - 12/2010 CTIC 01/2010 - 12/2012 Andago 01/2013 - 06/2013 TapQuo 07/2013 - 12/2015 freelance (ASOLIF, CENATIC) 07/2013 - now Bitergia

A little bit more complex

slide-9
SLIDE 9

share this slide! @mghfdez

Who is who? Project manager

slide-10
SLIDE 10

share this slide! @mghfdez

“For I'm the famous Sorting Hat. (...) So put me on and you will know Which house you should be in... ”

SortingHat: Wizardry on Software Project Members

slide-11
SLIDE 11

share this slide! @mghfdez

Photo credit: James Seattle

Merge identities! Affiliate this person! Complete the profile!

Name: Tom Gender: Male Email: tom@dark.wiz

Lord Voldemort Tom Riddle

slide-12
SLIDE 12

Boosting SH integration

share this slide! @mghfdez

Main idea: building a robust API Easy to integrate with external apps Flexible, easy to adapt Ensure consistency

Hatstall

Python module

slide-13
SLIDE 13

GraphQL is...

share this slide! @mghfdez … A query language, transport-agnostic but typically served over HTTP . … A specification for client-server communication: It doesn’t dictate which language to use, how the data should be stored or which clients to support. … Based on graph theory: nodes, edges and connections.

slide-14
SLIDE 14

REST vs GraphQL

share this slide! @mghfdez

/unique_identities/<uuid>/identities /unique_identities/<uuid>/profile /unique_identities/<uuid>/enrollments /organizations/<org_name>/domains query { unique_identities(uuid:“<uuid>”) { identities { uid } profile { email gender } enrollments {

  • rganization

end_date } domains { domain_name } } }

slide-15
SLIDE 15

Comparing approaches: REST

Convention between server and client Overfetching / Underfetching API Documentation is not tied to development Multiple requests per view

share this slide! @mghfdez

slide-16
SLIDE 16

Comparing approaches: GraphQL

Strongly typed language The client defines what it receives The server only sends what is needed One single request per view

share this slide! @mghfdez

slide-17
SLIDE 17

Summarizing ...

share this slide! @mghfdez

slide-18
SLIDE 18

Implementing process

share this slide! @mghfdez

Define data model & schema Up next... Support paginated results Authentication Implement basic queries & mutations

slide-19
SLIDE 19

Implementation: Graphene-Django

share this slide! @mghfdez

Picture credit: Snippedia

Graphene-Django is built on top of Graphene. It provides some additional abstractions that help to add GraphQL functionality to your Django project.

slide-20
SLIDE 20

Schema

share this slide! @mghfdez

M u t a t i

  • n

s Types Queries

GraphQL Schema

slide-21
SLIDE 21

Schema.py

share this slide! @mghfdez

C R U D

  • p

e r a t i

  • n

s Models Resolvers

GraphQL Schema: Graphene-Django

slide-22
SLIDE 22

It is already a graph

share this slide! @mghfdez Lord Voldemort Profile Identities Affiliations

Name: Tom Gender: Male Email: tom@dark.wiz

Tom Riddle slytherin.edu

UUID

slide-23
SLIDE 23

(Basic) Recipe for building queries

share this slide! @mghfdez class Organization(EntityBase): name = CharField(max_length=MAX_SIZE) class Meta: db_table = 'organizations' unique_together = ('name',) def __str__(self): return self.name class OrganizationType(DjangoObjectType): class Meta: model = Organization class SortingHatQuery:

  • rganizations = graphene.List(OrganizationType)

def resolve_organizations(self, info, **kwargs): return Organization.objects.order_by('name')

models.py schema.py

slide-24
SLIDE 24

Documentation is already updated!

share this slide! @mghfdez

slide-25
SLIDE 25

(Basic) Recipe for building mutations

share this slide! @mghfdez class AddOrganization(graphene.Mutation): class Arguments: name = graphene.String()

  • rganization = graphene.Field(lambda: OrganizationType)

def mutate(self, info, name):

  • rg = add_organization(name)

return AddOrganization(

  • rganization=org

) class SortingHatMutation(graphene.ObjectType): add_organization = AddOrganization.Field()

schema.py

slide-26
SLIDE 26

(Basic) Recipe for building mutations

share this slide! @mghfdez def add_organization(name): validate_field('name', name)

  • rganization = Organization(name=name)

try:

  • rganization.save()

except django.db.utils.IntegrityError as exc: _handle_integrity_error(Organization, exc) return organization

db.py

@django.db.transaction.atomic def add_organization(name): try:

  • rg = add_organization_db(name=name)

except ValueError as e: raise InvalidValueError(msg=str(e)) except AlreadyExistsError as exc: raise exc return org

api.py

slide-27
SLIDE 27

Documentation is already updated… again!

share this slide! @mghfdez

slide-28
SLIDE 28

share this slide! @mghfdez

About pagination

identities(first:2 offset:2) identities(first:2 after:$uuid) identities(first:2 after:$uuidCursor)

How are we getting the cursor? It is a property of the connection, not of the object.

slide-29
SLIDE 29

share this slide! @mghfdez

Edges and connections

Information that is specific to the edge, rather than to one of the objects. There are specifications like Relay

Friend A Friend B Friendship time

slide-30
SLIDE 30

share this slide! @mghfdez

Implementing pagination

We are taking our own approach without reinventing the wheel It is a hybrid approach based on offsets and limits, using Paginator Django objects Also benefiting from edges & connections

slide-31
SLIDE 31

share this slide! @mghfdez

Query Result

slide-32
SLIDE 32

share this slide! @mghfdez

slide-33
SLIDE 33

share this slide! @mghfdez class AbstractPaginatedType(graphene.ObjectType): @classmethod def create_paginated_result(cls, query, page=1, page_size=DEFAULT_SIZE): paginator = Paginator(query, page_size) result = paginator.page(page) entities = result.object_list page_info = PaginationType( page=result.number, page_size=page_size, num_pages=paginator.num_pages, has_next=result.has_next(), has_prev=result.has_previous(), start_index=result.start_index(), end_index=result.end_index(), total_results=len(query) ) return cls(entities=entities, page_info=page_info)

Django objects Query results Pagination info

slide-34
SLIDE 34

Returning paginated results

share this slide! @mghfdez class OrganizationPaginatedType(AbstractPaginatedType): entities = graphene.List(OrganizationType) page_info = graphene.Field(PaginationType) class SortingHatQuery: def resolve_organizations(...) (...) return OrganizationPaginatedType.create_paginated_result(query, page, page_size=page_size)

slide-35
SLIDE 35

Authenticated queries

share this slide! @mghfdez

It is based on JSON Web Tokens (JWT) An existing user must generate a token which has to be included in the Authorization header with the HTTP request This token is generated using a mutation which comes defined by the graphene-jwt module

slide-36
SLIDE 36

Testing authentication

share this slide! @mghfdez

Use an application capable of setting up headers to the HTTP requests Heads-up! Configuring the Django CSRF token properly was not trivial

Insomnia app

slide-37
SLIDE 37

Testing authentication

share this slide! @mghfdez from django.test import RequestFactory def setUp(self): self.user = get_user_model().objects.create(username='test') self.context_value = RequestFactory().get(GRAPHQL_ENDPOINT) self.context_value.user = self.user def test_add_organization(self): client = graphene.test.Client(schema) executed = client.execute(self.SH_ADD_ORG, context_value=self.context_value)

slide-38
SLIDE 38

Bonus: filtering

share this slide! @mghfdez class OrganizationFilterType(graphene.InputObjectType): name = graphene.String(required=False) class SortingHatQuery:

  • rganizations = graphene.Field(

OrganizationPaginatedType, page_size=graphene.Int(), page=graphene.Int(), filters=OrganizationFilterType(required=False) ) def resolve_organizations(...): # Modified resolver

slide-39
SLIDE 39

(some) Future work

share this slide! @mghfdez

Implementing a command line & web Client Limiting nested queries Feedback is welcome!

slide-40
SLIDE 40

share this slide! @mghfdez GrimoireLab architecture

slide-41
SLIDE 41

share this slide! @mghfdez

Let’s go for some questions

Twitter @mghfdez Email mafesan@bitergia.com GitHub mafesan speaker pic

FLOSS enthusiast & Data nerd Software Developer @ Bitergia Contributing to CHAOSS-GrimoireLab project