def test_none_json_value_input_in_dsl_argument(): ds = DSLSchema(schema) with pytest.raises(GraphQLError) as exc_info: ds.Mutation.addPlayer(player=None) assert "Received Null value for a Non-Null type JSON." in str( exc_info.value)
def mutation_domain(self, domain: str, lang: str, phrases: list): ds = DSLSchema(self.client.schema) mutation = dsl_gql( DSLMutation( ds.Mutation.register_domain.args( domain=domain, lang=lang, phrases=phrases).select(ds.MatcherResponse.success, ds.MatcherResponse.errors, ds.MatcherResponse.domain, ds.MatcherResponse.errors, ds.MatcherResponse.lang))) return mutation
def __init__(self, **kwargs): schemes = {"http": "ws", "https": "wss"} self.headers: Dict[str, str] = {} endpoint = os.getenv(URL_VAR, "http://localhost:8080/api") url = kwargs.pop("url", endpoint) split_url = urlsplit(url) ws_url = urlunsplit( split_url._replace(scheme=schemes[split_url.scheme])) transport = RequestsHTTPTransport(url=url, headers=self.headers) client = Client(transport=transport, schema=schema) self.ds = DSLSchema(client) ws_transport = WebsocketsTransport(url=ws_url) self.ws_client = Client(transport=ws_transport, schema=schema) self.token = kwargs.pop("token", os.getenv(TOKEN_VAR)) username = kwargs.pop("username", os.getenv(USERNAME_VAR)) password = kwargs.pop("password", os.getenv(PASSWORD_VAR)) if self.token is not None: self.headers = {"Authorization": f"Bearer {self.token}"} elif all((username, password)): self.login(username, password) # Mapping for nested fields self.field_map = { "Job": { "outputMetadata": self.ds.Metadata, "parameters": self.ds.Parameter, "user": self.ds.User, "worker": self.ds.Worker, }, "Service": { "parameters": self.ds.ServiceParameter, "fileType": self.ds.FileType, }, }
async def refresh_token(self) -> str: """Refresh an expired access token Returns: str: New access token """ q = self.ds.Mutation.refresh.args(token=self.refresh) async with Client(transport=self.transport, schema=schema) as sess: ds = DSLSchema(sess) resp = await ds.mutate(q) if resp["refresh"] is None: raise ValueError("Token refresh failed") token = resp["refresh"]["token"] self.token = token self.headers["Authorization"] = f"Bearer {self.token}" return token
async def main(): transport = AIOHTTPTransport( url="https://countries.trevorblades.com/graphql") client = Client(transport=transport, fetch_schema_from_transport=True) # Using `async with` on the client will start a connection on the transport # and provide a `session` variable to execute queries on this connection. # Because we requested to fetch the schema from the transport, # GQL will fetch the schema just after the establishment of the first session async with client as session: # Instanciate the root of the DSL Schema as ds ds = DSLSchema(client.schema) # Create the query using dynamically generated attributes from ds query = dsl_gql( DSLQuery( ds.Query.continents(filter={ "code": { "eq": "EU" } }).select(ds.Continent.code, ds.Continent.name))) result = await session.execute(query) print(result) # This can also be written as: # I want to query the continents query_continents = ds.Query.continents # I want to get only the continents with code equal to "EU" query_continents(filter={"code": {"eq": "EU"}}) # I want this query to return the code and name fields query_continents.select(ds.Continent.code) query_continents.select(ds.Continent.name) # I generate a document from my query to be able to execute it query = dsl_gql(DSLQuery(query_continents)) # Execute the query result = await session.execute(query) print(result)
def test_json_value_input_with_none_list_in_dsl_argument(): ds = DSLSchema(schema) new_player = { "name": "Bob", "level": 9001, "is_connected": True, "score": 666.66, "friends": None, } query = ds.Mutation.addPlayer(player=new_player) print(str(query)) assert (str(query) == """addPlayer( player: {name: "Bob", level: 9001, is_connected: true, score: 666.66, friends: null} )""")
async def submit(self) -> str: """Submit a job request to the service endpoint Returns: str: The unique job identifier """ q = self.client.ds.Mutation.submit_job.args( request=self.req_args).select( self.client.ds.JobRequestResponse.job.select( self.client.ds.Job.job_id, self.client.ds.Job.status)) async with Client(transport=self.client.transport, schema=self.client.schema) as sess: ds = DSLSchema(sess) resp = await ds.mutate(q) if "errors" in resp: raise ValueError("Error submitting job request: " + resp["errors"]) self.job_id = resp["submitJob"]["job"]["jobId"] self.status = resp["submitJob"]["job"]["status"] return self.job_id
async def login( self, username: Optional[str] = None, password: Optional[str] = None) -> Tuple[str, Optional[str]]: """Retrieve a web token from MOTHR Args: username (str, optional): Username used to login, the library will look for ``MOTHR_USERNAME`` in the environment as a fallback. password (str, optional): Password used to login, the library will look for ``MOTHR_PASSWORD`` in the environment as a fallback. Returns: str: An access token to pass with future requests str: A refresh token for receiving a new access token after the current token expires Raises: ValueError: If a username or password are not provided and are not found in the current environment """ username = username if username is not None else os.getenv( "MOTHR_USERNAME") password = password if password is not None else os.getenv( "MOTHR_PASSWORD") if username is None: raise ValueError("Username not provided") if password is None: raise ValueError("Password not provided") credentials = {"username": username, "password": password} q = self.ds.Mutation.login.args(**credentials).select( self.ds.LoginResponse.token, self.ds.LoginResponse.refresh) async with Client(transport=self.transport, schema=schema) as sess: ds = DSLSchema(sess) resp = await ds.mutate(q) tokens = resp["login"] if tokens is None: raise ValueError("Login failed") self.token = tokens["token"] self.refresh = tokens["refresh"] self.headers["Authorization"] = f"Bearer {self.token}" return self.token, self.refresh
def test_json_value_input_in_dsl_argument(): ds = DSLSchema(schema) new_player = { "name": "Tim", "level": 0, "is_connected": False, "score": 5, "friends": ["Lea"], } query = ds.Mutation.addPlayer(player=new_player) print(str(query)) assert (str(query) == """addPlayer( player: {name: "Tim", level: 0, is_connected: false, score: 5, friends: ["Lea"]} )""")
async def services(self, fields: Optional[List[str]] = None) -> List[Dict]: """Retrieve all services registered with MOTHR Args: fields (list<str>, optional): Fields to return in the query response, default is `name` and `version` Returns: list<dict>: All services registered with MOTHR """ fields = fields if fields is not None else ["name", "version"] fields = [ self.resolve_field(self.ds.Service, field) for field in fields ] q = self.ds.Query.services.select(*fields) async with Client(transport=self.transport, schema=schema) as sess: ds = DSLSchema(sess) resp = await ds.query(q) return resp["services"]
def mutation_skill(self, domain: str, call_back: str, lang: str, matchers: list): ''' Args: ---- call_back: the callback method registered as <domain>/<method> lang: The language iso code matchers: A list of Matcher rules on the form [{'ORTH': "vær", 'OP': "?"}, {'ORTH': "været", 'OP': "?"}] ''' ds = DSLSchema(self.client.schema) mutation = dsl_gql( DSLMutation( ds.Mutation.register_skill.args( domain=domain, call_back=call_back, lang=lang, matcher=matchers).select(ds.MatcherResponse.success, ds.MatcherResponse.errors, ds.MatcherResponse.domain, ds.MatcherResponse.errors, ds.MatcherResponse.lang, ds.MatcherResponse.call_back))) return mutation
def ds(): return DSLSchema(NestedInputSchema)
def ds(): client = Client(schema=NestedInputSchema) ds = DSLSchema(client) return ds
def ds(): return DSLSchema(StarWarsSchema)
def test_DSLSchema_requires_a_schema(client): with pytest.raises(TypeError, match="DSLSchema needs a schema as parameter"): DSLSchema(client)
def get_introspection_query_ast( descriptions: bool = True, specified_by_url: bool = False, directive_is_repeatable: bool = False, schema_description: bool = False, type_recursion_level: int = 7, ) -> DocumentNode: """Get a query for introspection as a document using the DSL module. Equivalent to the get_introspection_query function from graphql-core but using the DSL module and allowing to select the recursion level. Optionally, you can exclude descriptions, include specification URLs, include repeatability of directives, and specify whether to include the schema description as well. """ ds = DSLSchema(GraphQLSchema()) fragment_FullType = DSLFragment("FullType").on(ds.__Type) fragment_InputValue = DSLFragment("InputValue").on(ds.__InputValue) fragment_TypeRef = DSLFragment("TypeRef").on(ds.__Type) schema = DSLMetaField("__schema") if descriptions and schema_description: schema.select(ds.__Schema.description) schema.select( ds.__Schema.queryType.select(ds.__Type.name), ds.__Schema.mutationType.select(ds.__Type.name), ds.__Schema.subscriptionType.select(ds.__Type.name), ) schema.select(ds.__Schema.types.select(fragment_FullType)) directives = ds.__Schema.directives.select(ds.__Directive.name) if descriptions: directives.select(ds.__Directive.description) if directive_is_repeatable: directives.select(ds.__Directive.isRepeatable) directives.select( ds.__Directive.locations, ds.__Directive.args.select(fragment_InputValue), ) schema.select(directives) fragment_FullType.select( ds.__Type.kind, ds.__Type.name, ) if descriptions: fragment_FullType.select(ds.__Type.description) if specified_by_url: fragment_FullType.select(ds.__Type.specifiedByURL) fields = ds.__Type.fields(includeDeprecated=True).select(ds.__Field.name) if descriptions: fields.select(ds.__Field.description) fields.select( ds.__Field.args.select(fragment_InputValue), ds.__Field.type.select(fragment_TypeRef), ds.__Field.isDeprecated, ds.__Field.deprecationReason, ) enum_values = ds.__Type.enumValues(includeDeprecated=True).select( ds.__EnumValue.name ) if descriptions: enum_values.select(ds.__EnumValue.description) enum_values.select( ds.__EnumValue.isDeprecated, ds.__EnumValue.deprecationReason, ) fragment_FullType.select( fields, ds.__Type.inputFields.select(fragment_InputValue), ds.__Type.interfaces.select(fragment_TypeRef), enum_values, ds.__Type.possibleTypes.select(fragment_TypeRef), ) fragment_InputValue.select(ds.__InputValue.name) if descriptions: fragment_InputValue.select(ds.__InputValue.description) fragment_InputValue.select( ds.__InputValue.type.select(fragment_TypeRef), ds.__InputValue.defaultValue, ) fragment_TypeRef.select( ds.__Type.kind, ds.__Type.name, ) if type_recursion_level >= 1: current_field = ds.__Type.ofType.select(ds.__Type.kind, ds.__Type.name) fragment_TypeRef.select(current_field) for _ in repeat(None, type_recursion_level - 1): new_oftype = ds.__Type.ofType.select(ds.__Type.kind, ds.__Type.name) current_field.select(new_oftype) current_field = new_oftype query = DSLQuery(schema) query.name = "IntrospectionQuery" dsl_query = dsl_gql(query, fragment_FullType, fragment_InputValue, fragment_TypeRef) return dsl_query
class MothrClient: """Client for connecting to MOTHR Args: url (str, optional): Endpoint to send the job request, checks for ``MOTHR_ENDPOINT`` in environment variables otherwise defaults to ``http://localhost:8080/query`` token (str, optional): Access token to use for authentication, the library also looks for ``MOTHR_ACCESS_TOKEN`` in the environment as a fallback username (str, optional): Username for logging in, if not given the library will attempt to use ``MOTHR_USERNAME`` environment variable. If neither are found the request will be made without authentication. password (str, optional): Password for logging in, if not given the library will attempt to use the ``MOTHR_PASSWORD`` environment variable. If neither are found the request will be made without authentication. """ def __init__(self, **kwargs): schemes = {"http": "ws", "https": "wss"} self.headers: Dict[str, str] = {} endpoint = os.getenv(URL_VAR, "http://localhost:8080/api") url = kwargs.pop("url", endpoint) split_url = urlsplit(url) ws_url = urlunsplit( split_url._replace(scheme=schemes[split_url.scheme])) transport = RequestsHTTPTransport(url=url, headers=self.headers) client = Client(transport=transport, schema=schema) self.ds = DSLSchema(client) ws_transport = WebsocketsTransport(url=ws_url) self.ws_client = Client(transport=ws_transport, schema=schema) self.token = kwargs.pop("token", os.getenv(TOKEN_VAR)) username = kwargs.pop("username", os.getenv(USERNAME_VAR)) password = kwargs.pop("password", os.getenv(PASSWORD_VAR)) if self.token is not None: self.headers = {"Authorization": f"Bearer {self.token}"} elif all((username, password)): self.login(username, password) # Mapping for nested fields self.field_map = { "Job": { "outputMetadata": self.ds.Metadata, "parameters": self.ds.Parameter, "user": self.ds.User, "worker": self.ds.Worker, }, "Service": { "parameters": self.ds.ServiceParameter, "fileType": self.ds.FileType, }, } def login(self, username: Optional[str] = None, password: Optional[str] = None) -> Tuple[str, str]: """Retrieve a web token from MOTHR Args: username (str, optional): Username used to login, the library will look for ``MOTHR_USERNAME`` in the environment as a fallback. password (str, optional): Password used to login, the library will look for ``MOTHR_PASSWORD`` in the environment as a fallback. Returns: str: An access token to pass with future requests str: A refresh token for receiving a new access token after the current token expires Raises: ValueError: If a username or password are not provided and are not found in the current environment """ username = username if username is not None else os.getenv( "MOTHR_USERNAME") password = password if password is not None else os.getenv( "MOTHR_PASSWORD") if username is None: raise ValueError("Username not provided") if password is None: raise ValueError("Password not provided") credentials = {"username": username, "password": password} q = self.ds.Mutation.login.args(**credentials).select( self.ds.LoginResponse.token, self.ds.LoginResponse.refresh) resp = self.ds.mutate(q) tokens = resp["login"] if tokens is None: raise ValueError("Login failed") self.access = tokens["token"] self.refresh = tokens["refresh"] self.headers["Authorization"] = f"Bearer {self.token}" return self.access, self.refresh def refresh_token(self) -> str: """Refresh an expired access token Returns: str: New access token """ q = self.ds.Mutation.refresh.args(token=self.refresh).select( self.ds.RefreshResponse.token) resp = self.ds.mutate(q) if resp["refresh"] is None: raise ValueError("Token refresh failed") token = resp["refresh"]["token"] self.token = token self.headers["Authorization"] = f"Bearer {self.token}" return token def service( self, name: str, version: Optional[str] = "*", fields: Optional[List[str]] = None, ) -> List[Dict]: """Query a service by name Args: name (str): Name of the service version (str, optional): Version to retrieve. If no version is given all versions of the service will be returned. Wildcards are also accepted. fields (list<str>, optional): Fields to return in the query response, default is `name` and `version` Returns: list<dict>: Service records matching the query """ fields = fields if fields is not None else ["name", "version"] fields = [ self.resolve_field(self.ds.Service, field) for field in fields ] q = self.ds.Query.service.args(name=name, version=version).select(*fields) resp = self.ds.query(q) return resp["service"] def services(self, fields: Optional[List[str]] = None) -> List[Dict]: """Retrieve all services registered with MOTHR Args: fields (list<str>, optional): Fields to return in the query response, default is `name` and `version` Returns: list<dict>: All services registered with MOTHR """ fields = fields if fields is not None else ["name", "version"] fields = [ self.resolve_field(self.ds.Service, field) for field in fields ] q = self.ds.Query.services.select(*fields) resp = self.ds.query(q) return resp["services"] def resolve_field(self, obj: DSLType, field: str) -> DSLField: """Resolve paths to nested fields Args: obj (`gql.dsl.DSLType`): Root type belonging to the field field (str): Field to resolve, nested fields are specified using dot notation Returns: `gql.dsl.DSLField` """ if "." in field: nested_fields = self.field_map[str(obj._type)] f_split = field.split(".") return self.select_field(nested_fields, getattr(obj, f_split[0]), f_split[1:]) return getattr(obj, field) def select_field(self, field_map: Dict, field_obj: DSLField, field: List[str]) -> DSLField: """Select nested fields Args: field_map (dict<str, `gql.dsl.DSLType`>): Dictionary mapping field names to their associated DSLType. See `self.field_map` field_obj (`gql.dsl.DSLField`): Field object to select subfields from field (list<str>): Field name split into individual components Returns: `gql.dsl.DSLField` """ field_key = str(field_obj) for char in ["{", "}", "\n"]: field_key = field_key.replace(char, "") field_key = field_key.split(" ")[-1] field_name = field.pop(0) if len(field) > 0: return field_obj.select( self.select_field(field_map, getattr(field_map[field_key], field_name), field)) return field_obj.select(getattr(field_map[field_key], field_name))
from gql.dsl import DSLQuery, DSLSchema, dsl_gql from gql.transport.requests import RequestsHTTPTransport transport = RequestsHTTPTransport( url="https://countries.trevorblades.com/", verify=True, retries=3, ) client = Client(transport=transport, fetch_schema_from_transport=True) # Using `with` on the sync client will start a connection on the transport # and provide a `session` variable to execute queries on this connection. # Because we requested to fetch the schema from the transport, # GQL will fetch the schema just after the establishment of the first session with client as session: # We should have received the schema now that the session is established assert client.schema is not None # Instantiate the root of the DSL Schema as ds ds = DSLSchema(client.schema) # Create the query using dynamically generated attributes from ds query = dsl_gql( DSLQuery( ds.Query.continents.select(ds.Continent.code, ds.Continent.name))) result = session.execute(query) print(result)
def ds(): client = Client(schema=StarWarsSchema) ds = DSLSchema(client) return ds
def _get_schema(self): """ Gets GQL schema """ return DSLSchema(self._gql_client.schema)