Skip to main content

Dynamic GraphQL query builder

Project description

About this project

This project emerged from the frustrating experience when trying to write gql queries with dynamic filters. In some cases, think of a UI to beautifully display database contents, one desires to be able to set filters dynamically if some value is provided, yet just ignore it if not.

This behavior was guaranteed by Hasura versions 1.3.3v and below - but disabled by default in later versions, as described here.

The env var HASURA_GRAPHQL_V1_BOOLEAN_NULL_COLLAPSE preserves this functionality globally. However, this as well is not always desired, when a more fine-grained control over when a value is strictly necessary for where conditions, and when not, is of importance.

This project therefore provides a lightweight query builder that takes a simple gql query string as input and inserts the respective clauses if a value is presented, and does nothing, if not. This avoids nasty, error-prone, string concatenations while avoiding heavy-weight ASTs and the necessity of schema declarations as with gql-DSL.

Usage

All a user should be interacting with is located in the src.api package. As of now it only contains the GQLDynamicQueryBuilder which is the heart of the project and provides all the necessary functionality, while api.dsl provides a more user-friendly and convenient wrapper around the builder, as described here. To extend a query by an optional where clause one can do the following:

    query = """
        query TestQuery {
            product {
                name
                brand
            }
        }
    """
    
    builder = GQLDynamicQueryBuilder(query)
    builder = builder.with_where_clause(
                table_name='product', 
                field_name='name', 
                value='tomato', 
                operation='_ilike',
                skip_if_none=True
              )
    result = builder.build() # returns the transformed query as a string

This also works for queries with existing where clauses and other query parameters (e.g. limit). To access nested fields, simply use '.' as the delimiter:

    builder.with_where_clause(
        'product', 
        'brand.name', # will build brand { name: ...
        'ABC', 
        '_eq',
        skip_if_none=True
    )
    result = builder.build()

Furthermore, it is also possible to provide the following to with_where_clause:

  • values: list and operation: str which allows for set-operations like _in
  • values: list and operation: list[str] which allows for multiple operations for the same field (e.g. timestamp {_gte: <ts1> _lt: <ts2>})

As a fallback also explicit where clauses are supported via table_name: clause dictionaries:

    builder.with_where_clauses(
        {'product': 'name: {_ilike : "tomato"}'}
    )
    result = builder.build()

DSL

Since version 1.1.0 a more user-friendly DSL is provided under api.dsl. The DSL is a wrapper around the builder pattern that enables a continuous flow of information, such as the table name for which a filter is to be set. E.g. look at this builder-based query:

    result = (GQLDynamicQueryBuilder(query)
                  .with_where_clause('product', 'name', ['a', 'b', 'c'], '_in', skip_if_none=True)
                  .with_offset('product', 15)
                  .with_limit('product', 25)
                  .with_where_clause('product', 'price', 5, '_gte')
                  ).build()

All function calls in the above examples return a GQLDynamicQueryBuilder object, whose state is modified along the way. Thereby, the table name is repeated 4 times and the code is only marginally readable. Using the DSL on the other hand the same functionality can be achieved through the following:

    result = (dynamic_query(query).table('subquery_to_test')
                  .where('test', is_optional=True).
                  _in(['a', 'b', 'c'])
                  .offset(15)
                  .limit(25)
                  .where('test2')
                  ._gte(5)
                  .build()
                  )

This way the query construction becomes much more readable and concise. Furthermore, the method chaining is guarded by the type system, i.e. there is no need to worry to e.g. chain build() to a where() as the dsl is designed to only allow cohesive queries.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gql_dynamic_query_builder-1.1.0.tar.gz (10.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gql_dynamic_query_builder-1.1.0-py3-none-any.whl (11.5 kB view details)

Uploaded Python 3

File details

Details for the file gql_dynamic_query_builder-1.1.0.tar.gz.

File metadata

File hashes

Hashes for gql_dynamic_query_builder-1.1.0.tar.gz
Algorithm Hash digest
SHA256 1f2ba3886d0ee3d4930ae80c8de57988900353478e7a6fa8fc2bad22e4dbb168
MD5 2b33d28934ed82d557ea56a4beb5fc81
BLAKE2b-256 681d8eac5639d665c6c935931fa729edc663e6cff4835709b325c2f725a4894f

See more details on using hashes here.

File details

Details for the file gql_dynamic_query_builder-1.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for gql_dynamic_query_builder-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fd364515c44ac56e333190c8a25cdf3c1a5a154f19a930bdea016d6b764ff319
MD5 7212a3b863f1d4f77c8d894de42351af
BLAKE2b-256 11374acf889dda2ba2926f3cd51905bda69e49507daadeb018cd3d8cdf08c9b4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page