Align GraphQL lookup types for booleans & numerics (replace FilterLookup) #1038

Open
opened 2026-04-05 19:57:20 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @pheus on 10/17/2025

NetBox version

v4.4.4

Feature type

Change to existing functionality

Proposed functionality

To keep GraphQL filter inputs consistent and aligned with Strawberry‑Django, I’d like to suggest:

  • Booleans:

    Option A (preferred): use a plain bool on all boolean filter fields
    This keeps input shapes simple and ergonomic for typical use.

    # v2 boolean filters – applied consistently across all fields
    is_active: bool | None = strawberry_django.filter_field()
    

    Option B (alternative): use BaseFilterLookup[bool] on all boolean filter fields
    Choose this if we want to expose exact, isNull, and inList uniformly for tri‑state scenarios.

    from strawberry_django import BaseFilterLookup
    
    # v2 boolean filters – applied consistently across all fields
    is_active: BaseFilterLookup[bool] | None = strawberry_django.filter_field()
    

    The intent is consistency: adopt Option A (recommended) or Option B across the entire v2 schema, but don’t mix them by field.

  • Numerics (e.g., counts/lengths): adopt ComparisonFilterLookup[...] so v2 clients can use exact/gt/gte/lt/lte/range.

    from strawberry_django import ComparisonFilterLookup
    
    device_bay_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
    
  • Strings: continue using FilterLookup[str] where substring/regex matching is desired.

Use case

  • With Option A (preferred), boolean queries remain compact and easy to read:

    # exact match using plain bool
    query {
      device_type_list(filters: { is_full_depth: true }) { id }
    }
    
  • If Option B is selected, nullable/tri‑state patterns remain idiomatic:

    # exact true
    query {
      device_type_list(filters: { is_full_depth: { exact: true } }) { id }
    }
    
    # explicit null check
    query {
      device_type_list(filters: { is_full_depth: { is_null: true } }) { id }
    }
    
    # exclude nulls (generic builder pattern)
    query {
      device_type_list(filters: { is_full_depth: { in_list: [true, false] } }) { id }
    }
    
  • Numeric comparisons become first‑class:

    query {
      device_list(filters: { device_bay_count: { gte: 1, lte: 4 } }) { count }
    }
    

This keeps boolean filters uniform across v2 and exposes the intended comparison operators for numeric fields, aligning with Strawberry‑Django’s lookup families.

Database changes

None.

External dependencies

None.

Additional context

*Originally created by @pheus on 10/17/2025* ### NetBox version v4.4.4 ### Feature type Change to existing functionality ### Proposed functionality To keep GraphQL filter inputs consistent and aligned with Strawberry‑Django, I’d like to suggest: - **Booleans:** **Option A (preferred): use a plain `bool` on *all* boolean filter fields** This keeps input shapes simple and ergonomic for typical use. ```py # v2 boolean filters – applied consistently across all fields is_active: bool | None = strawberry_django.filter_field() ``` **Option B (alternative): use `BaseFilterLookup[bool]` on *all* boolean filter fields** Choose this if we want to expose `exact`, `isNull`, and `inList` uniformly for tri‑state scenarios. ```py from strawberry_django import BaseFilterLookup # v2 boolean filters – applied consistently across all fields is_active: BaseFilterLookup[bool] | None = strawberry_django.filter_field() ``` > The intent is **consistency**: adopt **Option A** (recommended) *or* **Option B** across the entire v2 schema, but don’t mix them by field. - **Numerics (e.g., counts/lengths):** adopt `ComparisonFilterLookup[...]` so v2 clients can use `exact/gt/gte/lt/lte/range`. ```py from strawberry_django import ComparisonFilterLookup device_bay_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field() ``` - **Strings:** continue using `FilterLookup[str]` where substring/regex matching is desired. ### Use case - With **Option A (preferred)**, boolean queries remain compact and easy to read: ```graphql # exact match using plain bool query { device_type_list(filters: { is_full_depth: true }) { id } } ``` - If **Option B** is selected, nullable/tri‑state patterns remain idiomatic: ```graphql # exact true query { device_type_list(filters: { is_full_depth: { exact: true } }) { id } } # explicit null check query { device_type_list(filters: { is_full_depth: { is_null: true } }) { id } } # exclude nulls (generic builder pattern) query { device_type_list(filters: { is_full_depth: { in_list: [true, false] } }) { id } } ``` - Numeric comparisons become first‑class: ```graphql query { device_list(filters: { device_bay_count: { gte: 1, lte: 4 } }) { count } } ``` This keeps boolean filters **uniform** across v2 and exposes the intended comparison operators for numeric fields, aligning with Strawberry‑Django’s lookup families. ### Database changes None. ### External dependencies None. ### Additional context - Related: #20603
MrUnknownDE added the complexity: mediumtype: featuretopic: GraphQLstatus: blockedstatus: backlogcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumtopic: GraphQLtype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featurestatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: backlogstatus: blockedstatus: blockedstatus: blockedstatus: blocked labels 2026-04-05 19:57:49 +02:00
Sign in to join this conversation.
No Label complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: backlog status: blocked status: blocked status: blocked status: blocked status: blocked topic: GraphQL topic: GraphQL type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/netbox#1038