Replace graphene-django with Strawberry #6722

Closed
opened 2025-12-29 19:44:29 +01:00 by adam · 7 comments
Owner

Originally created by @jeremystretch on GitHub (Jul 27, 2022).

Originally assigned to: @arthanson on GitHub.

NetBox version

v3.2.7

Feature type

Change to existing functionality

Proposed functionality

Replace NetBox's existing GraphQL implementation, which was built using graphene-django, with Strawberry. graphene-django unfortunately is no longer being maintained, and is no longer compatible with Django 4.0 and later releases (something which we currently work around via a monkey patch).

Use case

The goal here will be to keep the new GraphQL API as backward-compatible as possible, and to leverage any new features of Strawberry where they might be beneficial. Further research is needed to determine the migration strategy.

Database changes

No changes to the database schema are anticipated

External dependencies

Replace graphene-django with strawberry

Originally created by @jeremystretch on GitHub (Jul 27, 2022). Originally assigned to: @arthanson on GitHub. ### NetBox version v3.2.7 ### Feature type Change to existing functionality ### Proposed functionality Replace NetBox's existing GraphQL implementation, which was built using [graphene-django](https://github.com/graphql-python/graphene-django), with [Strawberry](https://strawberry.rocks/docs/integrations/django). graphene-django unfortunately is [no longer being maintained](https://github.com/graphql-python/graphene-django/issues/1324), and is no longer compatible with Django 4.0 and later releases (something which we currently work around via a monkey patch). ### Use case The goal here will be to keep the new GraphQL API as backward-compatible as possible, and to leverage any new features of Strawberry where they might be beneficial. Further research is needed to determine the migration strategy. ### Database changes No changes to the database schema are anticipated ### External dependencies Replace graphene-django with strawberry
adam added the status: acceptedtype: featurebreaking changetopic: GraphQL labels 2025-12-29 19:44:29 +01:00
adam closed this issue 2025-12-29 19:44:29 +01:00
Author
Owner

@jeremystretch commented on GitHub (Sep 27, 2022):

Given the recent release of graphene-django 3.0, we've decided to put on hold the exploration of alternative GraphQL API implementations and proceed with upgrading. (#10472 has been opened to track this work.) I'm going to close this issue with the assumption that moving to graphene-django 3.0 is tenable. If we discover otherwise, this will need to be reopened.

@jeremystretch commented on GitHub (Sep 27, 2022): Given the recent [release of graphene-django 3.0](https://github.com/graphql-python/graphene-django/releases/tag/v3.0.0), we've decided to put on hold the exploration of alternative GraphQL API implementations and proceed with upgrading. (#10472 has been opened to track this work.) I'm going to close this issue with the assumption that moving to graphene-django 3.0 is tenable. If we discover otherwise, this will need to be reopened.
Author
Owner

@arthanson commented on GitHub (Mar 30, 2023):

Reopening issue - Graphene Django is still pretty much dead. strawberry-django meanwhile has quite a bit of activity, they have also released https://github.com/blb-ventures/strawberry-django-plus which makes it potentially more viable.

@arthanson commented on GitHub (Mar 30, 2023): Reopening issue - Graphene Django is still pretty much dead. strawberry-django meanwhile has quite a bit of activity, they have also released https://github.com/blb-ventures/strawberry-django-plus which makes it potentially more viable.
Author
Owner

@arthanson commented on GitHub (Aug 26, 2023):

Investigating this, one issue is that the filtering syntax is different between the two libraries.

Graphene

user_list(is_superuser: true) {
    id
}

Strawberry

user_list(filters: {is_superuser: true}) {
   id
}

The filtering lookup syntax (greater than, less than, case-insensitive compare) is also different:
Graphene

{
  user_list(username__ic: "bob") {
    username
  }
}

Strawberry

{
  user_list(filters: {username: {i_contains: "bob"}}) {
    username
  }
}

For stawberry, allowing lookups on fields forces the lookup to be present, so it forces this somewhat awkward syntax:
Graphene

{
  user_list(id: "3") {
    id
  }
}

Strawberry

{
  user_list(filters: {id: {exact: 3}}) {
    id
  }
}
@arthanson commented on GitHub (Aug 26, 2023): Investigating this, one issue is that the filtering syntax is different between the two libraries. **Graphene** ``` user_list(is_superuser: true) { id } ``` **Strawberry** ``` user_list(filters: {is_superuser: true}) { id } ``` The filtering lookup syntax (greater than, less than, case-insensitive compare) is also different: **Graphene** ``` { user_list(username__ic: "bob") { username } } ``` **Strawberry** ``` { user_list(filters: {username: {i_contains: "bob"}}) { username } } ``` For stawberry, allowing lookups on fields forces the lookup to be present, so it forces this somewhat awkward syntax: **Graphene** ``` { user_list(id: "3") { id } } ``` **Strawberry** ``` { user_list(filters: {id: {exact: 3}}) { id } } ```
Author
Owner

@ryanmerolle commented on GitHub (Aug 26, 2023):

I think it's a reality of switch and frankly it's not that crazy.

@ryanmerolle commented on GitHub (Aug 26, 2023): I think it's a reality of switch and frankly it's not that crazy.
Author
Owner

@arthanson commented on GitHub (Jan 17, 2024):

After further research some issues came up with strawberry. Here are some pros, cons and more detailed explanation for discussion:

Note: If you do actively use GraphQL then please take a look at this and make comments as it will effect your code...

Pros (of using strawberry):

  • it is actively maintained
  • It has a queryset optimizer built-in so reduces n+1 queries when referencing foreign-key fields
  • GraphiQL is up-to-date

Cons:

  • Graphene works currently with bugs but nothing major
  • strawberry doesn't support django-filters (see below)
  • Not stable, pre-1.0 they make a LOT of releases and still have breaking changes fairly often

The major downside of strawberry is they don't support django-filters. They used to, but ripped it out without almost any warning on a prior release when they refactored to use data-classes. This has a couple side-effects:

  1. All filter fields have to be re-defined in the GraphQL code
  2. custom filters that we define have to have a custom wrapper in the GraphQL code to call into the filterset
  3. Lookup expressions will be different

More detail / examples

  1. Each field has to be defined on each filter, inheritance isn't supported for field definitions, so a filter example would be:
@strawberry_django.filter(models.Circuit, lookups=True)
class CircuitFilter(filtersets.CircuitFilterSet, filters.NetBoxModelFilter):
    # NetBoxModelFilterSet
    q: str | None
    # tag:
    # ChangeLoggedModelFilterSet
    created: auto
    last_updated: auto
    created_by_request: str | None
    updated_by_request: str | None
    modified_by_request: str | None

    id: auto
    cid: auto
    description: auto
    install_date: auto
    termination_date: auto
    commit_rate: auto
    provider_id: auto
    provider: auto
    ...

So the top part with the comment #NetBoxModelFilterSet would need to be on each filter definition. Type definitions (CircuitType) do support field definition inheritance so the duplication of field definitions is only needed on the filters. Note: The inheriting from the filtersets is a bit of a hack to support calling of the custom filter functions (see below).

  1. The custom filters have to have a custom wrapper, but the functional implementation for the filters do support inheritance, so for example the custom filters for created_by_request is defined in a common subclass that calls into the filterset methods:
class ChangeLoggedModelFilter:

    def created_by_request(self, queryset):
        return self.filter_by_request(queryset, "created_by_request", self.created_by_request)

    def updated_by_request(self, queryset):
        return self.filter_by_request(queryset, "updated_by_request", self.updated_by_request)

    def modified_by_request(self, queryset):
        return self.filter_by_request(queryset, "modified_by_request", self.modified_by_request)
  1. Lookups (greater-than, less-than, etc... ) are different, on the old they are added by the custom routines in base FilterSet classes, for strawberry they have their own implementation which is a slightly different set. Old:
id__n: [String]
id__lte: [String]
id__lt: [String]
id__gte: [String]
id__gt: [String]
id__empty: Boolean
cid__n: [String]
cid__ic: [String]
cid__nic: [String]
cid__iew: [String]
cid__niew: [String]
cid__isw: [String]
cid__nisw: [String]
cid__ie: [String]
cid__nie: [String]
cid__empty: Boolean

strawberry:

exact: ID
i_exact: ID
contains: ID
i_contains: ID
in_list: [ID!]
gt: ID
gte: ID
lt: ID
lte: ID
starts_with: ID
i_starts_with: ID
ends_with: ID
i_ends_with: ID
range: [ID!]
is_null: Boolean
regex: String
i_regex: String

There is no not operators (cid_n) as not is a graphql operator so you could query with {not {cid: ... which should get the same result.

@arthanson commented on GitHub (Jan 17, 2024): After further research some issues came up with strawberry. Here are some pros, cons and more detailed explanation for discussion: **Note:** If you do actively use GraphQL then please take a look at this and make comments as it will effect your code... **Pros** (of using strawberry): * it is actively maintained * It has a queryset optimizer built-in so reduces n+1 queries when referencing foreign-key fields * GraphiQL is up-to-date **Cons**: * Graphene works currently with bugs but nothing major * strawberry doesn't support django-filters (see below) * Not stable, pre-1.0 they make a LOT of releases and still have breaking changes fairly often The major downside of strawberry is they don't support django-filters. They used to, but ripped it out without almost any warning on a prior release when they refactored to use data-classes. This has a couple side-effects: 1. All filter fields have to be re-defined in the GraphQL code 2. custom filters that we define have to have a custom wrapper in the GraphQL code to call into the filterset 3. Lookup expressions will be different ## More detail / examples 1. Each field has to be defined on each filter, inheritance isn't supported for field definitions, so a filter example would be: ``` @strawberry_django.filter(models.Circuit, lookups=True) class CircuitFilter(filtersets.CircuitFilterSet, filters.NetBoxModelFilter): # NetBoxModelFilterSet q: str | None # tag: # ChangeLoggedModelFilterSet created: auto last_updated: auto created_by_request: str | None updated_by_request: str | None modified_by_request: str | None id: auto cid: auto description: auto install_date: auto termination_date: auto commit_rate: auto provider_id: auto provider: auto ... ``` So the top part with the comment #NetBoxModelFilterSet would need to be on each filter definition. Type definitions (CircuitType) do support field definition inheritance so the duplication of field definitions is only needed on the filters. **Note:** The inheriting from the filtersets is a bit of a hack to support calling of the custom filter functions (see below). 2. The custom filters have to have a custom wrapper, but the functional implementation for the filters do support inheritance, so for example the custom filters for created_by_request is defined in a common subclass that calls into the filterset methods: ``` class ChangeLoggedModelFilter: def created_by_request(self, queryset): return self.filter_by_request(queryset, "created_by_request", self.created_by_request) def updated_by_request(self, queryset): return self.filter_by_request(queryset, "updated_by_request", self.updated_by_request) def modified_by_request(self, queryset): return self.filter_by_request(queryset, "modified_by_request", self.modified_by_request) ``` 3. Lookups (greater-than, less-than, etc... ) are different, on the old they are added by the custom routines in base FilterSet classes, for strawberry they have their own implementation which is a slightly different set. Old: ``` id__n: [String] id__lte: [String] id__lt: [String] id__gte: [String] id__gt: [String] id__empty: Boolean cid__n: [String] cid__ic: [String] cid__nic: [String] cid__iew: [String] cid__niew: [String] cid__isw: [String] cid__nisw: [String] cid__ie: [String] cid__nie: [String] cid__empty: Boolean ``` strawberry: ``` exact: ID i_exact: ID contains: ID i_contains: ID in_list: [ID!] gt: ID gte: ID lt: ID lte: ID starts_with: ID i_starts_with: ID ends_with: ID i_ends_with: ID range: [ID!] is_null: Boolean regex: String i_regex: String ``` There is no not operators (cid_n) as not is a graphql operator so you could query with {not {cid: ... which should get the same result.
Author
Owner

@DanSheps commented on GitHub (Jan 25, 2024):

strawberry-graphql-django - Issue for adding support for django_filters: https://github.com/strawberry-graphql/strawberry-graphql-django/issues/448
strawberry-graphql-django - Issue for filtering overhaul: https://github.com/strawberry-graphql/strawberry-graphql-django/issues/399

@DanSheps commented on GitHub (Jan 25, 2024): strawberry-graphql-django - Issue for adding support for django_filters: https://github.com/strawberry-graphql/strawberry-graphql-django/issues/448 strawberry-graphql-django - Issue for filtering overhaul: https://github.com/strawberry-graphql/strawberry-graphql-django/issues/399
Author
Owner

@jeremystretch commented on GitHub (Mar 22, 2024):

Very happy to announce this has been completed for the upcoming v4.0 release! Thank you @arthanson!

@jeremystretch commented on GitHub (Mar 22, 2024): Very happy to announce this has been completed for the upcoming v4.0 release! Thank you @arthanson!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#6722