def _fetch_schema(self): schema = self.session.get(self._schema_url).json( cls=PotionJSONSchemaDecoder, referrer=self._schema_url, client=self) # NOTE these should perhaps be definitions in Flask-Potion for name, resource_schema in schema["properties"].items(): resource = self.resource_factory(name, resource_schema) setattr(self, upper_camel_case(name), resource)
def _fetch_schema(self): schema = self.session \ .get(self._schema_url) \ .json(cls=PotionJSONSchemaDecoder, referrer=self._schema_url, client=self) # NOTE these should perhaps be definitions in Flask-Potion for name, resource_schema in schema['properties'].items(): resource = self.resource_factory(name, resource_schema) setattr(self, upper_camel_case(name), resource)
def resource_factory(self, name, schema, resource_cls=None): """ Registers a new resource with a given schema. The schema must not have any unresolved references (such as `{"$ref": "#"}` for self-references, or otherwise). A subclass of :class:`Resource` may be provided to add specific functionality to the resulting :class:`Resource`. :param str name: :param dict schema: :param Resource resource_cls: a subclass of :class:`Resource` or None :return: The new :class:`Resource`. """ cls = type( str(upper_camel_case(name)), (resource_cls or Resource, collections.abc.MutableMapping), {"__doc__": schema.get("description", "")}, ) cls._schema = schema cls._client = self cls._links = links = {} for link_schema in schema["links"]: link = Link( self, rel=link_schema["rel"], href=link_schema["href"], method=link_schema["method"], schema=link_schema.get("schema", None), target_schema=link_schema.get("targetSchema", None), ) # Set Resource._self, etc. for the special methods as they are managed by the Resource class if link.rel in ("self", "instances", "create", "update", "destroy"): setattr(cls, "_{}".format(link.rel), link) links[link.rel] = link if link.rel != "update": # 'update' is a special case because of MutableMapping.update() setattr(cls, snake_case(link.rel), link) # TODO routes (instance & non-instance) for property_name, property_schema in schema.get("properties", {}).items(): # skip $uri and $id as these are already implemented in Resource and overriding them causes unnecessary # fetches. if property_name.startswith("$"): continue if property_schema.get("readOnly", False): # TODO better error message. Raises AttributeError("can't set attribute") setattr( cls, property_name, property( fget=partial((lambda name, obj: getitem(obj, name)), property_name), doc=property_schema.get("description", None), ), ) else: setattr( cls, property_name, property( fget=partial((lambda name, obj: getitem(obj, name)), property_name), fset=partial((lambda name, obj, value: setitem( obj, name, value)), property_name), fdel=partial((lambda name, obj: delitem(obj, name)), property_name), doc=property_schema.get("description", None), ), ) root = None if "instances" in links: root = cls._instances.href elif "self" in links: root = cls._self.href[:cls._self.href.rfind("/")] else: root = self._root_path + "/" + name.replace("_", "-") self._resources[root] = cls return cls
def resource_factory(self, name, schema, resource_cls=None): """ Registers a new resource with a given schema. The schema must not have any unresolved references (such as `{"$ref": "#"}` for self-references, or otherwise). A subclass of :class:`Resource` may be provided to add specific functionality to the resulting :class:`Resource`. :param str name: :param dict schema: :param Resource resource_cls: a subclass of :class:`Resource` or None :return: The new :class:`Resource`. """ cls = type(str(upper_camel_case(name)), (resource_cls or Resource, collections.MutableMapping), {'__doc__': schema.get('description', '')}) cls._schema = schema cls._client = self cls._links = links = {} for link_schema in schema['links']: link = Link(self, rel=link_schema['rel'], href=link_schema['href'], method=link_schema['method'], schema=link_schema.get('schema', None), target_schema=link_schema.get('targetSchema', None)) # Set Resource._self, etc. for the special methods as they are managed by the Resource class if link.rel in ('self', 'instances', 'create', 'update', 'destroy'): setattr(cls, '_{}'.format(link.rel), link) links[link.rel] = link if link.rel != 'update': # 'update' is a special case because of MutableMapping.update() setattr(cls, snake_case(link.rel), link) # TODO routes (instance & non-instance) for property_name, property_schema in schema['properties'].items(): # skip $uri and $id as these are already implemented in Resource and overriding them causes unnecessary # fetches. if property_name.startswith('$'): continue if property_schema.get('readOnly', False): # TODO better error message. Raises AttributeError("can't set attribute") setattr( cls, property_name, property(fget=partial( (lambda name, obj: getitem(obj, name)), property_name), doc=property_schema.get('description', None))) else: setattr( cls, property_name, property(fget=partial( (lambda name, obj: getitem(obj, name)), property_name), fset=partial((lambda name, obj, value: setitem( obj, name, value)), property_name), fdel=partial( (lambda name, obj: delitem(obj, name)), property_name), doc=property_schema.get('description', None))) root = None if 'instances' in links: root = cls._instances.href elif 'self' in links: root = cls._self.href[:cls._self.href.rfind('/')] else: root = self._root_path + '/' + name.replace('_', '-') self._resources[root] = cls return cls
def resource_factory(self, name, schema, resource_cls=None): """ Registers a new resource with a given schema. The schema must not have any unresolved references (such as `{"$ref": "#"}` for self-references, or otherwise). A subclass of :class:`Resource` may be provided to add specific functionality to the resulting :class:`Resource`. :param str name: :param dict schema: :param Resource resource_cls: a subclass of :class:`Resource` or None :return: The new :class:`Resource`. """ cls = type(str(upper_camel_case(name)), (resource_cls or Resource, collections.MutableMapping), { '__doc__': schema.get('description', '') }) cls._schema = schema cls._client = self cls._links = links = {} for link_schema in schema['links']: link = Link(self, rel=link_schema['rel'], href=link_schema['href'], method=link_schema['method'], schema=link_schema.get('schema', None), target_schema=link_schema.get('targetSchema', None)) # Set Resource._self, etc. for the special methods as they are managed by the Resource class if link.rel in ('self', 'instances', 'create', 'update', 'destroy'): setattr(cls, '_{}'.format(link.rel), link) links[link.rel] = link if link.rel != 'update': # 'update' is a special case because of MutableMapping.update() setattr(cls, snake_case(link.rel), link) # TODO routes (instance & non-instance) for property_name, property_schema in schema.get('properties', {}).items(): # skip $uri and $id as these are already implemented in Resource and overriding them causes unnecessary # fetches. if property_name.startswith('$'): continue if property_schema.get('readOnly', False): # TODO better error message. Raises AttributeError("can't set attribute") setattr(cls, property_name, property(fget=partial((lambda name, obj: getitem(obj, name)), property_name), doc=property_schema.get('description', None))) else: setattr(cls, property_name, property(fget=partial((lambda name, obj: getitem(obj, name)), property_name), fset=partial((lambda name, obj, value: setitem(obj, name, value)), property_name), fdel=partial((lambda name, obj: delitem(obj, name)), property_name), doc=property_schema.get('description', None))) root = None if 'instances' in links: root = cls._instances.href elif 'self' in links: root = cls._self.href[:cls._self.href.rfind('/')] else: root = self._root_path + '/' + name.replace('_', '-') self._resources[root] = cls return cls
def _fetch_schema(self, cache_schema=False, creds_file=None): self._cached_schema = {} creds_fp = os.path.expanduser( '~/.onecodex') if creds_file is None else creds_file if os.path.exists(creds_fp): creds = json.load(open(creds_fp, 'r')) else: creds = {} schema = None serialized_schema = None if cache_schema: # Determine if we need to update schema_update_needed = True last_update = creds.get('schema_saved_at') if last_update is not None: last_update = datetime.strptime(last_update, self.DATE_FORMAT) time_diff = datetime.now() - last_update schema_update_needed = time_diff.days > self.SCHEMA_SAVE_DURATION if not schema_update_needed: # get the schema from the credentials file (as a string) serialized_schema = creds.get('schema') if serialized_schema is not None: # Catch schema caching issues and fall back to remote URL try: base_schema = serialized_schema.pop(self._schema_url) schema = json.loads(base_schema, cls=PotionJSONSchemaDecoder, referrer=self._schema_url, client=self) for route, route_schema in serialized_schema.items(): object_schema = json.loads(route_schema, cls=PotionJSONSchemaDecoder, referrer=self._schema_url, client=self) self._cached_schema[route] = object_schema except KeyError: # Caches issue with schema_url not existing pass if schema is None: # if the schema wasn't cached or if it was expired, get it anew schema = self.session.get(self._schema_url).json( cls=PotionJSONSchemaDecoder, referrer=self._schema_url, client=self) if cache_schema: # serialize the schemas back out creds['schema_saved_at'] = datetime.strftime( datetime.now(), self.DATE_FORMAT) # serialize the main schema serialized_schema = {} serialized_schema[self._schema_url] = json.dumps( schema, cls=PotionJSONEncoder) # serialize the object schemas for schema_ref in schema['properties'].values(): serialized_schema[schema_ref._uri] = json.dumps( schema_ref._properties, cls=PotionJSONEncoder) creds['schema'] = serialized_schema else: if 'schema_saved_at' in creds: del creds['schema_saved_at'] if 'schema' in creds: del creds['schema'] # always resave the creds (to make sure we're removing schema if we need to be or # saving if we need to do that instead) json.dump(creds, open(creds_fp, mode='w')) for name, resource_schema in schema['properties'].items(): class_name = upper_camel_case(name) setattr(self, class_name, self.resource_factory(name, resource_schema))