def dump_graphql_data_object(dct): """ Stringify a dict to a graphql input parameter key values in the form Also camelizes keys if the are slugs {"key1": "string value1", "key2": "number2", ...} :param dct: :return: """ if dct == None: return 'null' elif isinstance(dct, dict): return '{%s}' % R.join( ', ', R.map( lambda key_value: R.join( ': ', [ camelize(quote(key_value[0]), False), dump_graphql_data_object(key_value[1]) ] ), dct.items() ) ) elif isinstance(dct, list): return f"[{R.join(', ', R.map(lambda item: dump_graphql_data_object(item), dct))}]" else: return quote(dct)
def log_request_body(info, response_or_error): body = info.context._body.decode('utf-8') try: json_body = json.loads(body) ( logger.error if isinstance(response_or_error, ErrorType) else logger.debug )(f" User: {info.context.user} \n Action: {json_body['operationName']} \n Variables: {json_body['variables']} \n Body: {json_body['query']}", ) if hasattr(response_or_error, '_meta') and isinstance( response_or_error._meta, MutationOptions): # Just log top level types if isinstance(response_or_error, (Model)): mutation_response = json.dumps(R.omit( ['_state'], response_or_error.__dict__), sort_keys=True, indent=1, cls=MyDjangoJSONEncoder) logger.debug(f'Mutation returned {mutation_response}') elif isinstance(response_or_error, (BaseType)): try: mutation_response = json.dumps( R.omit(['_state'], response_or_error.__dict__), sort_keys=True, indent=1, ) logger.debug(f'Mutation returned {mutation_response}') except: logger.debug( f'Mutation returned {response_or_error.__class__}') else: if hasattr(response_or_error, 'objects'): count = response_or_error.objects.count() # Log up to 100 ids, don't log if it's a larger set because it might be a paging query ids = R.join(' ', [ '', 'having ids:', R.join( ', ', R.map(R.prop("id"), response_or_error.objects.values('id'))) ]) if count < 100 else "" logger.debug( f'Paginated Query Page {response_or_error.page} of page size {response_or_error.page_size} out of total pages {response_or_error.pages} returned {count} results{ids}' ) elif hasattr(response_or_error, 'count'): count = response_or_error.count() # Log up to 100 ids, don't log if it's a larger set because it might be a paging query ids = R.join(' ', [ '', 'having ids:', R.join(', ', R.map(R.prop("id"), response_or_error.values('id'))) ]) if count < 100 else "" logger.debug(f'Query returned {count} results{ids}') else: id = R.prop('id', response_or_error) logger.debug(f'Query returned single result {id}') except Exception as e: logging.error(body)
def dump_graphql_keys(dct): """ Convert a dict to a graphql input parameter keys in the form Also camelizes keys if the are slugs and handles complex types. If a value has read=IGNORE it is omitted key1 key2 key3 key4 { subkey1 ... } ... :param dct: keyed by field :return: """ from rescape_graphene.graphql_helpers.schema_helpers import IGNORE, DENY return R.join('\n', R.values(R.map_with_obj( dump_graphene_type, R.filter_dict( lambda key_value: not R.compose( lambda v: R.contains(v, [IGNORE, DENY]), lambda v: R.prop_or(None, 'read', v) )(key_value[1]), dct ) )))
def validate_and_mutate_scope_instances(scope_instances_config, data): """ Inspect the data and find all scope instances within data For UserState, for instance, this includes userRegions[*].region, userProject[*].project and within userRegions and userProjects userSearch.userSearchLocations[*].search_location and whatever the implementing libraries define in addition :param scope_instances_config: See user_state_schema.user_state_scope_instances_config for an example :param data: The instance data field containing the scope instances :return: The updated data with scope instances possibly created/updated if allowed. If creates occur then the scope instance will now have an id. Otherwise no changes are visible """ validated_scope_objs_instances_and_ids_sets = R.map( lambda scope_instance_config: find_scope_instances( scope_instance_config, data), scope_instances_config) # Some scope instances can be created or modified when embedded in the data. This helps # make mutation of the instance, such as UserState, # a one step process, so that new Projects, SearchLocations, etc. can # be created without having to call mutation for them separately ahead of times, which would create # a series of mutations that weren't failure-protected as a single transaction for i, validated_scope_objs_instances_and_ids in enumerate( validated_scope_objs_instances_and_ids_sets): scope = R.merge( scope_instances_config[i], dict(model=scope_instances_config[i]['model'].__name__)) # If any scope instances with an id specified in new_data don't exist, throw an error if R.length(validated_scope_objs_instances_and_ids['scope_ids'] ) != R.length( validated_scope_objs_instances_and_ids['instances']): ids = R.join(', ', validated_scope_objs_instances_and_ids['scope_ids']) instances_string = R.join( ', ', R.map(lambda instance: str(instance), validated_scope_objs_instances_and_ids['instances'])) raise Exception( f"For scope {dumps(scope)} Some scope ids among ids:[{ids}] being saved in user state do not exist. Found the following instances in the database: {instances_string or 'None'}. UserState.data is {dumps(data)}" ) # Create/Update any scope instances that permit it model = scope_instances_config[i]['model'] data = handle_can_mutate_related( model, scope, data, validated_scope_objs_instances_and_ids) return data
def get_user_search_data_schema(): """ Uses the same technique as get_user_model() to get the current location model from settings :return: """ try: modules = settings.USER_SEARCH_DATA_SCHEMA_CONFIG.split('.') return getattr(importlib.import_module(R.join('.', R.init(modules))), R.last(modules)) except ValueError: raise ImproperlyConfigured( '''settings.USER_SEARCH_DATA_SCHEMA_CONFIG must point to the user_search schema config containing { graphene_class=UserSearchType, graphene_fields=user_search_fields, } ''') except LookupError: raise ImproperlyConfigured( "settings.USER_SEARCH_DATA_SCHEMA_CONFIG refers to model '%s' that has not been installed" % settings.USER_SEARCH_DATA_SCHEMA_CONFIG)
def get_location_for_project_schema(): """ Like get_location_schema() but without reverse relationships that cause circular dependencies :return: """ try: modules = settings.LOCATION_SCHEMA_FOR_PROJECT_CONFIG.split('.') return getattr(importlib.import_module(R.join('.', R.init(modules))), R.last(modules)) except ValueError: raise ImproperlyConfigured( '''settings.LOCATION_SCHEMA_FOR_PROJECT_CONFIG must point to the location schema config containing { model_class=Location, graphene_class=LocationType, graphene_fields=location_fields, } ''') except LookupError: raise ImproperlyConfigured( "settings.LOCATION_SCHEMA_CONFIG refers to model '%s' that has not been installed" % settings.LOCATION_SCHEMA_CONFIG)