def test_query(self): all_result = graphql_query_resources(self.client) assert not R.has('errors', all_result), R.dump_json(R.prop('errors', all_result)) results = graphql_query_resources(self.client, dict(name='String'), variable_values=dict(name='Minerals')) # Check against errors assert not R.has('errors', results), R.dump_json(R.prop('errors', results)) assert 1 == R.length(R.item_path(['data', 'resources'], results))
def quiz_model_query(client, model_query_function, result_name, variables, expect_length=1): """ Tests a query for a model with variables that produce exactly one result :param client: Apollo client :param model_query_function: Query function expecting the client and variables :param result_name: The name of the result object in the data object :param variables: key value variables for the query :param expect_length: Default 1. Optional number items to expect :return: returns the result for further assertions """ all_result = model_query_function(client) assert not R.has('errors', all_result), R.dump_json( R.map(lambda e: format_error(e), R.prop('errors', all_result))) result = model_query_function(client, variables=variables) # Check against errors assert not R.has('errors', result), R.dump_json( R.map(lambda e: format_error(e), R.prop('errors', result))) # Simple assertion that the query looks good assert expect_length == R.length(R.item_path(['data', result_name], result)) return result
def test_query_foo_with_null_geojson(self): # Query using for foos based on the related User foo_results = graphql_query_foos(self.client, variables=dict(key='fookit') ) assert not R.prop('errors', foo_results), R.dump_json(R.map(lambda e: format_error(e), R.prop('errors', foo_results))) assert 1 == R.length(R.map(R.omit_deep(omit_props), R.item_path(['data', 'foos'], foo_results)))
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 resolve(self, next, root, info, **args): result = next(root, info, **args) if is_thenable(result): result.catch(self.on_error) # Top level only if R.length(info.path) == 1: result.then(lambda response: log_request_body(info, response)) return result
def test_query(self): user_results = graphql_query_users(self.client) format_error(R.prop('errors', user_results)[0]) assert not R.prop('errors', user_results), R.dump_json(R.map(lambda e: format_error(e), R.prop('errors', user_results))) assert 2 == R.length(R.map(R.omit_deep(omit_props), R.item_path(['data', 'users'], user_results))) # Query using for foos based on the related User foo_results = graphql_query_foos( self.client, variables=dict( user=R.pick(['id'], self.lion.__dict__), # Test filters name_contains='oo', name_contains_not='jaberwaki' ) ) assert not R.prop('errors', foo_results), R.dump_json(R.map(lambda e: format_error(e), R.prop('errors', foo_results))) assert 1 == R.length(R.map(R.omit_deep(omit_props), R.item_path(['data', 'foos'], foo_results))) # Make sure the Django instance in the json blob was resolved assert self.cat.id == R.item_path(['data', 'foos', 0, 'data', 'friend', 'id'], foo_results)
def fetch_and_merge(modified_user_state_data, props): existing = UserState.objects.filter(**props) # If the user doesn't have a user state yet if not R.length(existing): return modified_user_state_data return merge_data_fields_on_update( ['data'], R.head(existing), # Merge existing's id in case it wasn't in user_state_data R.merge(modified_user_state_data, R.pick(['id'], existing)) )
def create_sample_projects(cls, users, regions): """ Create sample projects :param cls: The Project class :param users: Users to own the projects :param regions: Assign a region to each project :return: """ delete_sample_projects(cls) # Convert all sample project dicts to persisted Project instances # Give each reach an owner return R.map( lambda kv: create_sample_project(cls, regions[R.modulo(kv[0], R.length(regions))], kv[1]), enumerate(sample_projects(users)) )
def find_scope_instances(user_state_scope, new_data): """ Retrieve the scope instances to verify the Ids. Scope instances must have ids unless they are allowed to be created/updated during the userState mutation (such as searchLocations) :param new_data: The data to search :param user_state_scope Dict with 'pick' in the shape of the instances we are looking for in new_data, e.g. dict(userRegions={region: True}) to search new_data.userRegions[] for all occurrences of {region:...} and 'key' which indicates the actually key of the instance (e.g. 'region' for regions) :return: dict( instances=Instances actually in the database, ) """ def until(key, value): return key != R.prop('key', user_state_scope) return R.compose( lambda scope_dict: dict( # See which instances with ids are actually in the database # If any are missing we have an invalid update or need to create those instances if permitted instances=list( find_scope_instances_by_id(R.prop('model', user_state_scope), scope_dict['scope_ids'])), # The path from userRegions or userProjects to the scope instances, used to replace # a null update value with the existing values user_scope_path=list(R.keys(R.flatten_dct(user_state_scope, '.')))[ 0], **scope_dict), lambda scope_objs: dict( # Unique by id or accept if there is no id, this loses data, but it's just for validation scope_objs=R.unique_by( lambda obj: R.prop_or(str(now()), 'id', obj['value']), scope_objs), scope_ids=R.unique_by( R.identity, compact( R.map( lambda scope_obj: R.prop_or(None, 'id', scope_obj[ 'value']), scope_objs)))), # Use the pick key property to find the scope instances in the data # If we don't match anything we can get null or an empty item. Filter/compact these out R.filter(lambda obj: obj['value'] and (not isinstance( obj['value'], list) or R.length(obj['value']) != 0)), R.map(lambda pair: dict(key=pair[0], value=pair[1])), lambda flattened_data: R.to_pairs(flattened_data), lambda data: R.flatten_dct_until( R.pick_deep_all_array_items(R.prop('pick', user_state_scope), data ), until, '.'))(new_data)
def process_stage(stage, i): # Get the current stage as the source if there are any in nodes_by_stage sources = R.prop_or(None, R.prop('key', stage), nodes_by_stages) if not sources: return [] # Iterate through the stages until one with nodes is found target_stage = None try: target_stage = R.find( # Try to find nodes matching this potential target stage. There might not be any lambda stage: nodes_by_stages[R.prop('key', stage)] if R.has(R.prop('key', stage), nodes_by_stages) else None, stages[i + 1:R.length(stages)]) except ValueError: # It's coo, find errors if none is found. We really need R.first pass # If no more stages contain nodes, we're done if not target_stage: return [] targets = nodes_by_stages[R.prop('key', target_stage)] def prop_lookup(node, prop): return R.prop( prop, dict(zip(node['properties'], node['property_values']))) # Create the link with the source_node and target_node. Later we'll add # in source and target that points to the nodes overall index in the graph, # but we don't want to compute the overall indices yet return R.chain( lambda source: R.map( lambda target: dict(source_node=source, target_node=target, value=string_to_float( prop_lookup(source, value_key))), targets), sources)
def handle_can_mutate_related(model, related_model_scope_config, data, validated_scope_objs_instances_and_ids): """ Mutates the given related models of an instance if permitted See rescape-region's UserState for a working usage :param model: The related model :param related_model_scope_config: Configuration of the related model relative to the referencing instance :param data: The data containing thphee related models dicts to possibly mutate with :param validated_scope_objs_instances_and_ids: Config of the related objects that have been validated as existing in the database for objects not being created :return: Possibly mutates instances, returns data with newly created ids set """ def make_fields_unique_if_needed(scope_obj): # If a field needs to be unique, like a key, call it's unique_with method return R.map_with_obj( lambda key, value: R.item_str_path_or( R.identity, f'field_config.{key}.unique_with', related_model_scope_config)(scope_obj), scope_obj) def convert_foreign_key_to_id(scope_obj): # Find ForeignKey attributes and map the class field name to the foreign key id field # E.g. region to region_id, user to user_id, etc converters = R.compose( R.from_pairs, R.map(lambda field: [field.name, field.attname]), R.filter(lambda field: R.isinstance(ForeignKey, field)))( model._meta.fields) # Convert scopo_obj[related_field] = {id: x} to scope_obj[related_field_id] = x return R.from_pairs( R.map_with_obj_to_values( lambda key, value: [converters[key], R.prop('id', value)] if R.has(key, converters) else [key, value], scope_obj)) def omit_to_many(scope_obj): return R.omit(R.map(R.prop('attname'), model._meta.many_to_many), scope_obj) # This indicates that scope_objs were submitted that didn't have ids # This is allowed if those scope_objs can be created/updated when the userState is mutated if R.prop_or(False, 'can_mutate_related', related_model_scope_config): for scope_obj_key_value in validated_scope_objs_instances_and_ids[ 'scope_objs']: scope_obj = scope_obj_key_value['value'] scope_obj_path = scope_obj_key_value['key'] if R.length(R.keys(R.omit(['id'], scope_obj))): modified_scope_obj = R.compose( convert_foreign_key_to_id, omit_to_many, make_fields_unique_if_needed)(scope_obj) if R.prop_or(False, 'id', scope_obj): # Update, we don't need the result since it's already in user_state.data instance, created = model.objects.update_or_create( defaults=R.omit(['id'], modified_scope_obj), **R.pick(['id'], scope_obj)) else: # Create instance = model(**modified_scope_obj) instance.save() # We need to replace the object # passed in with an object containing the id of the instance data = R.fake_lens_path_set(scope_obj_path.split('.'), R.pick(['id'], instance), data) for to_many in model._meta.many_to_many: if to_many.attname in R.keys(scope_obj): # Set existing related values to the created/updated instances getattr(instance, to_many.attname).set( R.map(R.prop('id'), scope_obj[to_many.attname])) return data