def get(self): """ @api {GET} /express/ps/single/pricing 配送系列 - 拿计价 @apiName ps_express_single_get_price @apiGroup app_ps @apiParam (query param) {float} weight 运单重量 @apiParam (query param) {int} [volume_a] 运单体积-长 @apiParam (query param) {int} [volume_b] 运单体积-宽 @apiParam (query param) {int} [volume_c] 运单体积-高 :return: """ try: params = Schema( Or({'weight': schema_float_2}, { "volume_a": schema_int, "volume_b": schema_int, "volume_c": schema_int })).validate(self.get_query_args()) except SchemaError as e: logging.warn(e) self.resp_args_error() return fh_extra, msg, weight_max = pricing(volume=params.get('volume_a', 0) * params.get('volume_b', 0) * params.get('volume_c', 0), weight=params.get('weight', 0.0)) self.resp( dict(msg=msg, weight=weight_max, fh_extra=fh_extra, fh=15.0 + fh_extra))
def test_schema_simple_get(self): schema_plain = {'a': {'type': int, 'get': public}} A = Schema(schema_plain) value = A.get({'a': 3}) self.assertEqual(value, {'a': 3})
def test_schema_simple_get_forbidden(self): schema_plain = {'a': {'type': int, 'get': private}} A = Schema(schema_plain) value = A.get({'a': 3}) self.assertEqual(value, {})
def test_schema_path_b_is_private(self): schema_plain = {'b': {'type': str, 'get': private}} B = Schema(schema_plain) schema_plain = {'a': {'type': B, 'set': public}} A = Schema(schema_plain) value = A.get({'a': {'b': 'hello'}}) self.assertEqual(value, {'a': {}})
def test_schema_path_b_is_current_user_miguel(self): schema_plain = {'b': {'type': str, 'get': current_user_is('user')}} B = Schema(schema_plain) schema_plain = {'a': {'type': B, 'set': public}, 'user': {'type': str}} A = Schema(schema_plain) with patch('schema.current_user') as mock: mock.return_value = 'miguel' value = A.get({'user': '******', 'a': {'b': 'hello'}}) self.assertEqual(value, {'user': '******', 'a': {'b': 'hello'}})
def test_schema_path_b_is_owner(self): schema_plain = {'b': {'type': str, 'get': is_owner}} B = Schema(schema_plain) schema_plain = {'a': {'type': B, 'set': public}} A = Schema(schema_plain) with patch('schema.current_user') as mock: mock.return_value = 'miguel' value = A.get({'__owners': ['miguel'], 'a': {'b': 'hello'}}) self.assertEqual(value, {'a': {'b': 'hello'}})
def _test_multiple_schema(my): # add a second schema new_schema = SearchType.create("sthpw/schema") new_schema.set_value("project_code", "unittest") new_schema.set_value("code", "second_schema") new_schema.set_value("schema", ''' <schema> <search_type name="test/node1"/> <search_type name="test/node2"/> <search_type name="test/node3"/> <search_type name="test/node4"/> <connect from="test/node1" to="test/node2"/> <connect from="test/node2" to="test/node3"/> <connect from="test/node3" to="test/node4"/> </schema>''') new_schema.commit() schema = Schema.get(reset_cache=True) print schema.get_value("schema")
def _test_multiple_schema(self): # add a second schema new_schema = SearchType.create("sthpw/schema") new_schema.set_value("project_code", "unittest") new_schema.set_value("code", "second_schema") new_schema.set_value("schema", ''' <schema> <search_type name="test/node1"/> <search_type name="test/node2"/> <search_type name="test/node3"/> <search_type name="test/node4"/> <connect from="test/node1" to="test/node2"/> <connect from="test/node2" to="test/node3"/> <connect from="test/node3" to="test/node4"/> </schema>''') new_schema.commit() schema = Schema.get(reset_cache=True) print schema.get_value("schema")
class Feature(object): """ An object composed of a set of named attributes with values. A feature is constructed from a ``dict`` of name value pairs and an optional identifier. >>> f = Feature({ 'name': 'anvil', 'price': 100.0 }, 'widgets.1') >>> str(f.get('name')) 'anvil' A feature can also be constructed optionally with a :class:`Schema`. >>> from schema import Schema >>> s = Schema('widgets', [('name', str), ('price', float)]) >>> f = Feature({'name': 'anvil'}, '1', s) >>> f widgets.1 {name: anvil, price: None} When *schema* is specified feature values can be passed a ``list``. >>> s = Schema('widgets', [('name', str), ('price', float)]) >>> f = Feature(['anvil', 100.0], '1', s) >>> f widgets.1 {name: anvil, price: 100.0} """ def __init__(self, atts=None, id=None, schema=None, f=None): from schema import Schema if atts: # attributes specified directly # if list specified assume values in order of schema if isinstance(atts, list): if not schema: raise Exception('Values may be specified as list only when schema is supplied') natts = {} for i in range(len(atts)): natts[schema.fields[i].name] = atts[i] atts = natts # generate feature type if necessary self.schema = schema if not self.schema: self.schema = Schema('feature', [(att, type(val)) for att,val in atts.iteritems()]) # generate feature b = SimpleFeatureBuilder(self.schema._type) for att, val in atts.iteritems(): b.set(att, val) self._feature = b.buildFeature(str(id) if id else None) elif f: # feature specififed directly self._feature = f self.schema = schema if schema else Schema(ft=f.type) else: raise Exception('No attributes specified for feature') def getid(self): return self._feature.identifier.toString() id = property(getid, None) """ Identifier of the feature as a ``str`` >>> f = Feature({'name': 'anvil'}, 'widgets.1') >>> f.id 'widgets.1' """ def getgeom(self): return core.map(self._feature.defaultGeometry) def setgeom(self, g): self._feature.defaultGeometry = g geom = property(getgeom, setgeom) """ The geometry of the feature. >>> import geom >>> f = Feature({'geom': geom.Point(1,1)}) >>> f.geom POINT (1 1) >>> f.geom = geom.Point(2,2) >>> f.geom POINT (2 2) """ def getbounds(self): if self.geom: return geom.Bounds(prj=self.schema.proj, env=self.geom.getEnvelopeInternal()) bounds = property(getbounds) """ The :class:`Bounds <geoscript.geom.bounds.Bounds>` of the feature geometry. Will return ``None`` if the feature does not contain any geometric attributes. """ def get(self, name): """ Returns a feature attribute value by name. ``KeyError`` is thrown if the attribute does not exist. *name* is the name of the attribute whose value to return. >>> f = Feature({'name': 'anvil', 'price': 100.0}) >>> str(f.get('name')) 'anvil' """ self.schema.get(name) return self._feature.getAttribute(name) def set(self, name, value): """ Sets a feature attribute value by name. ``KeyError`` is thrown is the attribute does not exist. *name* is the name of the attribute whose value to set. *value* is the new attribute value. >>> f = Feature({'name': 'anvil', 'price': 100.0}) >>> str(f.get('name')) 'anvil' >>> f.set('name', 'mallet') >>> str(f.get('name')) 'mallet' """ self.schema.get(name) self._feature.setAttribute(name, value) def getattributes(self): atts = {} for fld in self.schema.fields: atts[fld.name] = core.map(self._feature.getAttribute(fld.name)) return atts attributes = property(getattributes, None) """ A ``dict`` of name, value for the attributes of the feature. >>> f = Feature({'name': 'anvil', 'price': 100.0}) >>> atts = f.attributes >>> str(atts['name']) 'anvil' >>> atts['price'] 100.0 """ def __getitem__(self, key): return self.get(key) def __setitem__(self, key, value): self.set(key, value) def __iter__(self): return self.schema.__iter__() def iterkeys(self): return self.__iter__() def iteritems(self): return self.attributes.iteritems() def keys(self): return [f.name for f in self.schema.fields] def values(self): return [core.map(val) for val in self._feature.getAttributes()] def __repr__(self): atts = ['%s: %s' % (fld.name, self.get(fld.name)) for fld in self.schema.fields] id = self.id if self.id.startswith(self.schema.name) else '%s.%s' % (self.schema.name, self.id) return '%s {%s}' % (id, string.join(atts,', ')) def __eq__(self, other): return other and self._feature == other._feature
class Config(object): # pylint: disable=too-many-instance-attributes """Class that manages configuration files for a dvc repo. Args: dvc_dir (str): optional path to `.dvc` directory, that is used to access repo-specific configs like .dvc/config and .dvc/config.local. validate (bool): optional flag to tell dvc if it should validate the config or just load it as is. 'True' by default. Raises: ConfigError: thrown when config has an invalid format. """ APPNAME = "dvc" APPAUTHOR = "iterative" # NOTE: used internally in RemoteLOCAL to know config # location, that url should resolved relative to. PRIVATE_CWD = "_cwd" CONFIG = "config" CONFIG_LOCAL = "config.local" BOOL_SCHEMA = And(str, is_bool, Use(to_bool)) SECTION_CORE = "core" SECTION_CORE_LOGLEVEL = "loglevel" SECTION_CORE_LOGLEVEL_SCHEMA = And(Use(str.lower), supported_loglevel) SECTION_CORE_REMOTE = "remote" SECTION_CORE_INTERACTIVE_SCHEMA = BOOL_SCHEMA SECTION_CORE_INTERACTIVE = "interactive" SECTION_CORE_ANALYTICS = "analytics" SECTION_CORE_ANALYTICS_SCHEMA = BOOL_SCHEMA SECTION_CACHE = "cache" SECTION_CACHE_DIR = "dir" SECTION_CACHE_TYPE = "type" SECTION_CACHE_TYPE_SCHEMA = supported_cache_type SECTION_CACHE_PROTECTED = "protected" SECTION_CACHE_LOCAL = "local" SECTION_CACHE_S3 = "s3" SECTION_CACHE_GS = "gs" SECTION_CACHE_SSH = "ssh" SECTION_CACHE_HDFS = "hdfs" SECTION_CACHE_AZURE = "azure" SECTION_CACHE_SLOW_LINK_WARNING = "slow_link_warning" SECTION_CACHE_SCHEMA = { Optional(SECTION_CACHE_LOCAL): str, Optional(SECTION_CACHE_S3): str, Optional(SECTION_CACHE_GS): str, Optional(SECTION_CACHE_HDFS): str, Optional(SECTION_CACHE_SSH): str, Optional(SECTION_CACHE_AZURE): str, Optional(SECTION_CACHE_DIR): str, Optional(SECTION_CACHE_TYPE, default=None): SECTION_CACHE_TYPE_SCHEMA, Optional(SECTION_CACHE_PROTECTED, default=False): BOOL_SCHEMA, Optional(PRIVATE_CWD): str, Optional(SECTION_CACHE_SLOW_LINK_WARNING, default=True): BOOL_SCHEMA, } # backward compatibility SECTION_CORE_CLOUD = "cloud" SECTION_CORE_CLOUD_SCHEMA = And(Use(str.lower), supported_cloud) SECTION_CORE_STORAGEPATH = "storagepath" SECTION_CORE_SCHEMA = { Optional(SECTION_CORE_LOGLEVEL): And(str, Use(str.lower), SECTION_CORE_LOGLEVEL_SCHEMA), Optional(SECTION_CORE_REMOTE, default=""): And(str, Use(str.lower)), Optional(SECTION_CORE_INTERACTIVE, default=False): SECTION_CORE_INTERACTIVE_SCHEMA, Optional(SECTION_CORE_ANALYTICS, default=True): SECTION_CORE_ANALYTICS_SCHEMA, # backward compatibility Optional(SECTION_CORE_CLOUD, default=""): SECTION_CORE_CLOUD_SCHEMA, Optional(SECTION_CORE_STORAGEPATH, default=""): str, } # backward compatibility SECTION_AWS = "aws" SECTION_AWS_STORAGEPATH = "storagepath" SECTION_AWS_CREDENTIALPATH = "credentialpath" SECTION_AWS_ENDPOINT_URL = "endpointurl" SECTION_AWS_LIST_OBJECTS = "listobjects" SECTION_AWS_REGION = "region" SECTION_AWS_PROFILE = "profile" SECTION_AWS_USE_SSL = "use_ssl" SECTION_AWS_SCHEMA = { SECTION_AWS_STORAGEPATH: str, Optional(SECTION_AWS_REGION): str, Optional(SECTION_AWS_PROFILE): str, Optional(SECTION_AWS_CREDENTIALPATH): str, Optional(SECTION_AWS_ENDPOINT_URL): str, Optional(SECTION_AWS_LIST_OBJECTS, default=False): BOOL_SCHEMA, Optional(SECTION_AWS_USE_SSL, default=True): BOOL_SCHEMA, } # backward compatibility SECTION_GCP = "gcp" SECTION_GCP_STORAGEPATH = SECTION_AWS_STORAGEPATH SECTION_GCP_CREDENTIALPATH = SECTION_AWS_CREDENTIALPATH SECTION_GCP_PROJECTNAME = "projectname" SECTION_GCP_SCHEMA = { SECTION_GCP_STORAGEPATH: str, Optional(SECTION_GCP_PROJECTNAME): str, } # backward compatibility SECTION_LOCAL = "local" SECTION_LOCAL_STORAGEPATH = SECTION_AWS_STORAGEPATH SECTION_LOCAL_SCHEMA = {SECTION_LOCAL_STORAGEPATH: str} SECTION_AZURE_CONNECTION_STRING = "connection_string" # Alibabacloud oss options SECTION_OSS_ACCESS_KEY_ID = "oss_key_id" SECTION_OSS_ACCESS_KEY_SECRET = "oss_key_secret" SECTION_OSS_ENDPOINT = "oss_endpoint" SECTION_REMOTE_REGEX = r'^\s*remote\s*"(?P<name>.*)"\s*$' SECTION_REMOTE_FMT = 'remote "{}"' SECTION_REMOTE_URL = "url" SECTION_REMOTE_USER = "******" SECTION_REMOTE_PORT = "port" SECTION_REMOTE_KEY_FILE = "keyfile" SECTION_REMOTE_TIMEOUT = "timeout" SECTION_REMOTE_PASSWORD = "******" SECTION_REMOTE_ASK_PASSWORD = "******" SECTION_REMOTE_SCHEMA = { SECTION_REMOTE_URL: str, Optional(SECTION_AWS_REGION): str, Optional(SECTION_AWS_PROFILE): str, Optional(SECTION_AWS_CREDENTIALPATH): str, Optional(SECTION_AWS_ENDPOINT_URL): str, Optional(SECTION_AWS_LIST_OBJECTS, default=False): BOOL_SCHEMA, Optional(SECTION_AWS_USE_SSL, default=True): BOOL_SCHEMA, Optional(SECTION_GCP_PROJECTNAME): str, Optional(SECTION_CACHE_TYPE): SECTION_CACHE_TYPE_SCHEMA, Optional(SECTION_CACHE_PROTECTED, default=False): BOOL_SCHEMA, Optional(SECTION_REMOTE_USER): str, Optional(SECTION_REMOTE_PORT): Use(int), Optional(SECTION_REMOTE_KEY_FILE): str, Optional(SECTION_REMOTE_TIMEOUT): Use(int), Optional(SECTION_REMOTE_PASSWORD): str, Optional(SECTION_REMOTE_ASK_PASSWORD): BOOL_SCHEMA, Optional(SECTION_AZURE_CONNECTION_STRING): str, Optional(SECTION_OSS_ACCESS_KEY_ID): str, Optional(SECTION_OSS_ACCESS_KEY_SECRET): str, Optional(SECTION_OSS_ENDPOINT): str, Optional(PRIVATE_CWD): str, } SECTION_STATE = "state" SECTION_STATE_ROW_LIMIT = "row_limit" SECTION_STATE_ROW_CLEANUP_QUOTA = "row_cleanup_quota" SECTION_STATE_SCHEMA = { Optional(SECTION_STATE_ROW_LIMIT): And(Use(int), is_whole), Optional(SECTION_STATE_ROW_CLEANUP_QUOTA): And(Use(int), is_percent), } SCHEMA = { Optional(SECTION_CORE, default={}): SECTION_CORE_SCHEMA, Optional(Regex(SECTION_REMOTE_REGEX)): SECTION_REMOTE_SCHEMA, Optional(SECTION_CACHE, default={}): SECTION_CACHE_SCHEMA, Optional(SECTION_STATE, default={}): SECTION_STATE_SCHEMA, # backward compatibility Optional(SECTION_AWS, default={}): SECTION_AWS_SCHEMA, Optional(SECTION_GCP, default={}): SECTION_GCP_SCHEMA, Optional(SECTION_LOCAL, default={}): SECTION_LOCAL_SCHEMA, } def __init__(self, dvc_dir=None, validate=True): self.system_config_file = os.path.join(self.get_system_config_dir(), self.CONFIG) self.global_config_file = os.path.join(self.get_global_config_dir(), self.CONFIG) if dvc_dir is not None: self.dvc_dir = os.path.abspath(os.path.realpath(dvc_dir)) self.config_file = os.path.join(dvc_dir, self.CONFIG) self.config_local_file = os.path.join(dvc_dir, self.CONFIG_LOCAL) else: self.dvc_dir = None self.config_file = None self.config_local_file = None self._system_config = None self._global_config = None self._repo_config = None self._local_config = None self.config = None self.load(validate=validate) @staticmethod def get_global_config_dir(): """Returns global config location. E.g. ~/.config/dvc/config. Returns: str: path to the global config directory. """ from appdirs import user_config_dir return user_config_dir(appname=Config.APPNAME, appauthor=Config.APPAUTHOR) @staticmethod def get_system_config_dir(): """Returns system config location. E.g. /etc/dvc.conf. Returns: str: path to the system config directory. """ from appdirs import site_config_dir return site_config_dir(appname=Config.APPNAME, appauthor=Config.APPAUTHOR) @staticmethod def init(dvc_dir): """Initializes dvc config. Args: dvc_dir (str): path to .dvc directory. Returns: dvc.config.Config: config object. """ config_file = os.path.join(dvc_dir, Config.CONFIG) open(config_file, "w+").close() return Config(dvc_dir) def _load(self): self._system_config = configobj.ConfigObj(self.system_config_file) self._global_config = configobj.ConfigObj(self.global_config_file) if self.config_file is not None: self._repo_config = configobj.ConfigObj(self.config_file) else: self._repo_config = configobj.ConfigObj() if self.config_local_file is not None: self._local_config = configobj.ConfigObj(self.config_local_file) else: self._local_config = configobj.ConfigObj() self.config = None def _load_config(self, path): config = configobj.ConfigObj(path) config = self._lower(config) self._resolve_paths(config, path) return config @staticmethod def _resolve_path(path, config_file): assert os.path.isabs(config_file) config_dir = os.path.dirname(config_file) return os.path.abspath(os.path.join(config_dir, path)) def _resolve_cache_path(self, config, fname): cache = config.get(self.SECTION_CACHE) if cache is None: return cache_dir = cache.get(self.SECTION_CACHE_DIR) if cache_dir is None: return cache[self.PRIVATE_CWD] = os.path.dirname(fname) def _resolve_paths(self, config, fname): if fname is None: return self._resolve_cache_path(config, fname) for section in config.values(): if self.SECTION_REMOTE_URL not in section.keys(): continue section[self.PRIVATE_CWD] = os.path.dirname(fname) def load(self, validate=True): """Loads config from all the config files. Args: validate (bool): optional flag to tell dvc if it should validate the config or just load it as is. 'True' by default. Raises: dvc.config.ConfigError: thrown if config has invalid format. """ self._load() try: self.config = self._load_config(self.system_config_file) user = self._load_config(self.global_config_file) config = self._load_config(self.config_file) local = self._load_config(self.config_local_file) # NOTE: schema doesn't support ConfigObj.Section validation, so we # need to convert our config to dict before passing it to for conf in [user, config, local]: self.config = self._merge(self.config, conf) if validate: self.config = Schema(self.SCHEMA).validate(self.config) # NOTE: now converting back to ConfigObj self.config = configobj.ConfigObj(self.config, write_empty_values=True) self.config.filename = self.config_file self._resolve_paths(self.config, self.config_file) except Exception as ex: raise ConfigError(ex) @staticmethod def _get_key(conf, name, add=False): for k in conf.keys(): if k.lower() == name.lower(): return k if add: conf[name] = {} return name return None def save(self, config=None): """Saves config to config files. Args: config (configobj.ConfigObj): optional config object to save. Raises: dvc.config.ConfigError: thrown if failed to write config file. """ if config is not None: clist = [config] else: clist = [ self._system_config, self._global_config, self._repo_config, self._local_config, ] for conf in clist: if conf.filename is None: continue try: logger.debug("Writing '{}'.".format(conf.filename)) dname = os.path.dirname(os.path.abspath(conf.filename)) try: os.makedirs(dname) except OSError as exc: if exc.errno != errno.EEXIST: raise conf.write() except Exception as exc: msg = "failed to write config '{}'".format(conf.filename) raise ConfigError(msg, exc) def get_remote_settings(self, name): import posixpath """ Args: name (str): The name of the remote that we want to retrieve Returns: dict: The content beneath the given remote name. Example: >>> config = {'remote "server"': {'url': 'ssh://localhost/'}} >>> get_remote_settings("server") {'url': 'ssh://localhost/'} """ settings = self.config.get(self.SECTION_REMOTE_FMT.format( name.lower())) if settings is None: raise ConfigError( "unable to find remote section '{}'".format(name)) parsed = urlparse(settings["url"]) # Support for cross referenced remotes. # This will merge the settings, giving priority to the outer reference. # For example, having: # # dvc remote add server ssh://localhost # dvc remote modify server user root # dvc remote modify server ask_password true # # dvc remote add images remote://server/tmp/pictures # dvc remote modify images user alice # dvc remote modify images ask_password false # dvc remote modify images password asdf1234 # # Results on a config dictionary like: # # { # "url": "ssh://localhost/tmp/pictures", # "user": "******", # "password": "******", # "ask_password": False, # } # if parsed.scheme == "remote": reference = self.get_remote_settings(parsed.netloc) url = posixpath.join(reference["url"], parsed.path.lstrip("/")) merged = reference.copy() merged.update(settings) merged["url"] = url return merged return settings @staticmethod def unset(config, section, opt=None): """Unsets specified option and/or section in the config. Args: config (configobj.ConfigObj): config to work on. section (str): section name. opt (str): optional option name. """ if section not in config.keys(): raise ConfigError("section '{}' doesn't exist".format(section)) if opt is None: del config[section] return if opt not in config[section].keys(): raise ConfigError("option '{}.{}' doesn't exist".format( section, opt)) del config[section][opt] if not config[section]: del config[section] @staticmethod def set(config, section, opt, value): """Sets specified option in the config. Args: config (configobj.ConfigObj): config to work on. section (str): section name. opt (str): option name. value: value to set option to. """ if section not in config.keys(): config[section] = {} config[section][opt] = value @staticmethod def show(config, section, opt): """Prints option value from the config. Args: config (configobj.ConfigObj): config to work on. section (str): section name. opt (str): option name. """ if section not in config.keys(): raise ConfigError("section '{}' doesn't exist".format(section)) if opt not in config[section].keys(): raise ConfigError("option '{}.{}' doesn't exist".format( section, opt)) logger.info(config[section][opt]) @staticmethod def _merge(first, second): res = {} sections = list(first.keys()) + list(second.keys()) for section in sections: first_copy = first.get(section, {}).copy() second_copy = second.get(section, {}).copy() first_copy.update(second_copy) res[section] = first_copy return res @staticmethod def _lower(config): new_config = {} for s_key, s_value in config.items(): new_s = {} for key, value in s_value.items(): new_s[key.lower()] = str(value) new_config[s_key.lower()] = new_s return new_config
class Feature(object): """ An object composed of a set of named attributes with values. A feature is constructed from a ``dict`` of name value pairs and an optional identifier. >>> f = Feature({ 'name': 'anvil', 'price': 100.0 }, 'widgets.1') >>> str(f.get('name')) 'anvil' A feature can also be constructed optionally with a :class:`Schema`. >>> from schema import Schema >>> s = Schema('widgets', [('name', str), ('price', float)]) >>> f = Feature({'name': 'anvil'}, '1', s) >>> f widgets.1 {name: anvil, price: None} When *schema* is specified feature values can be passed a ``list``. >>> s = Schema('widgets', [('name', str), ('price', float)]) >>> f = Feature(['anvil', 100.0], '1', s) >>> f widgets.1 {name: anvil, price: 100.0} """ def __init__(self, atts=None, id=None, schema=None, f=None): from schema import Schema if atts: # attributes specified directly # if list specified assume values in order of schema if isinstance(atts, list): if not schema: raise Exception( 'Values may be specified as list only when schema is supplied' ) natts = {} for i in range(len(atts)): natts[schema.fields[i].name] = atts[i] atts = natts # generate feature type if necessary self.schema = schema if not self.schema: self.schema = Schema('feature', [(att, type(val)) for att, val in atts.iteritems()]) # generate feature b = SimpleFeatureBuilder(self.schema._type) for att, val in atts.iteritems(): b.set(att, val) self._feature = b.buildFeature(str(id) if id else None) elif f: # feature specififed directly self._feature = f self.schema = schema if schema else Schema(ft=f.type) else: raise Exception('No attributes specified for feature') def getid(self): return self._feature.identifier.toString() id = property(getid, None) """ Identifier of the feature as a ``str`` >>> f = Feature({'name': 'anvil'}, 'widgets.1') >>> f.id 'widgets.1' """ def getgeom(self): return core.map(self._feature.defaultGeometry) def setgeom(self, g): self._feature.defaultGeometry = g geom = property(getgeom, setgeom) """ The geometry of the feature. >>> import geom >>> f = Feature({'geom': geom.Point(1,1)}) >>> f.geom POINT (1 1) >>> f.geom = geom.Point(2,2) >>> f.geom POINT (2 2) """ def getbounds(self): if self.geom: return geom.Bounds(prj=self.schema.proj, env=self.geom.getEnvelopeInternal()) bounds = property(getbounds) """ The :class:`Bounds <geoscript.geom.bounds.Bounds>` of the feature geometry. Will return ``None`` if the feature does not contain any geometric attributes. """ def get(self, name): """ Returns a feature attribute value by name. ``KeyError`` is thrown if the attribute does not exist. *name* is the name of the attribute whose value to return. >>> f = Feature({'name': 'anvil', 'price': 100.0}) >>> str(f.get('name')) 'anvil' """ self.schema.get(name) return self._feature.getAttribute(name) def set(self, name, value): """ Sets a feature attribute value by name. ``KeyError`` is thrown is the attribute does not exist. *name* is the name of the attribute whose value to set. *value* is the new attribute value. >>> f = Feature({'name': 'anvil', 'price': 100.0}) >>> str(f.get('name')) 'anvil' >>> f.set('name', 'mallet') >>> str(f.get('name')) 'mallet' """ self.schema.get(name) self._feature.setAttribute(name, value) def getattributes(self): atts = {} for fld in self.schema.fields: atts[fld.name] = core.map(self._feature.getAttribute(fld.name)) return atts attributes = property(getattributes, None) """ A ``dict`` of name, value for the attributes of the feature. >>> f = Feature({'name': 'anvil', 'price': 100.0}) >>> atts = f.attributes >>> str(atts['name']) 'anvil' >>> atts['price'] 100.0 """ def __getitem__(self, key): return self.get(key) def __setitem__(self, key, value): self.set(key, value) def __iter__(self): return self.schema.__iter__() def iterkeys(self): return self.__iter__() def iteritems(self): return self.attributes.iteritems() def keys(self): return [f.name for f in self.schema.fields] def values(self): return [core.map(val) for val in self._feature.getAttributes()] def __repr__(self): atts = [ '%s: %s' % (fld.name, self.get(fld.name)) for fld in self.schema.fields ] id = self.id if self.id.startswith( self.schema.name) else '%s.%s' % (self.schema.name, self.id) return '%s {%s}' % (id, string.join(atts, ', ')) def __eq__(self, other): return other and self._feature == other._feature
class Page: "Banana banana" meta_schema = { Optional('title'): And(str, len), Optional('symbols'): Schema([And(str, len)]), Optional('private-symbols'): Schema([And(str, len)]), Optional('short-description'): And(str, len), Optional('description'): And(str, len), Optional('render-subpages'): bool, Optional('auto-sort'): bool, Optional('full-width'): bool, Optional('see-also'): And(str, len), Optional('extra'): Schema({str: object}), Optional('thumbnail'): And(str, len) } # pylint: disable=too-many-arguments def __init__(self, source_file, ast, output_path, project_name, meta=None, raw_contents=None): "Banana banana" assert source_file basename = os.path.basename(source_file) name = os.path.splitext(basename)[0] ref = os.path.join(output_path, re.sub(r'\W+', '-', os.path.splitext(basename)[0])) pagename = '%s.html' % ref self.ast = ast self.extension_name = None self.source_file = source_file self.raw_contents = raw_contents self.comment = None self.generated = False self.pre_sorted = False self.output_attrs = None self.subpages = OrderedSet() self.symbols = [] self.private_symbols = [] self.typed_symbols = OrderedDict() self.by_parent_symbols = OrderedDict() self.is_stale = True self.formatted_contents = None self.detailed_description = None self.build_path = None self.project_name = project_name self.cached_paths = OrderedSet() meta = meta or {} self.listed_symbols = [] self.symbol_names = [] self.short_description = None self.render_subpages = True self.title = '' self.meta = Schema(Page.meta_schema).validate({}) self.__update_meta(meta) self.__discover_title(meta) self.link = Link(pagename, self.title or name, ref) def __update_meta(self, meta): for key, value in meta.items(): try: self.meta.update( Schema(Page.meta_schema).validate( {key.replace('_', '-').lower(): value})) except SchemaError as _: warn( 'invalid-page-metadata', '%s: Invalid metadata: \n%s, discarding metadata' % (self.source_file, str(_))) if not self.meta.get('extra'): self.meta['extra'] = defaultdict() self.title = meta.get('title', self.title) self.thumbnail = meta.get('thumbnail') self.listed_symbols = OrderedSet( meta.get('symbols') or self.symbol_names) self.private_symbols = OrderedSet( meta.get('private-symbols') or self.private_symbols) self.symbol_names = OrderedSet( meta.get('symbols') or self.symbol_names) self.short_description = meta.get('short-description', self.short_description) self.render_subpages = meta.get('render-subpages', self.render_subpages) def __getstate__(self): return { 'ast': None, 'build_path': None, 'title': self.title, 'raw_contents': self.raw_contents, 'short_description': self.short_description, 'extension_name': self.extension_name, 'link': self.link, 'meta': self.meta, 'source_file': self.source_file, 'comment': self.comment, 'generated': self.generated, 'is_stale': False, 'formatted_contents': None, 'detailed_description': None, 'output_attrs': None, 'symbols': [], 'private_symbols': {}, 'typed_symbols': {}, 'by_parent_symbols': {}, 'subpages': self.subpages, 'listed_symbols': self.listed_symbols, 'symbol_names': self.symbol_names, 'project_name': self.project_name, 'pre_sorted': self.pre_sorted, 'cached_paths': self.cached_paths, 'render_subpages': self.render_subpages } def __repr__(self): return "<Page %s>" % self.source_file @staticmethod def __get_empty_typed_symbols(): typed_symbols_list = namedtuple('TypedSymbolsList', ['name', 'symbols']) empty_typed_symbols = {} for subclass in all_subclasses(Symbol): empty_typed_symbols[subclass] = typed_symbols_list( subclass.get_plural_name(), []) return empty_typed_symbols def set_comment(self, comment): """ Sets @comment as main comment for @self. """ if comment: self.__update_meta(comment.meta) self.comment = comment def resolve_symbols(self, tree, database, link_resolver): """ When this method is called, the page's symbol names are queried from `database`, and added to lists of actual symbols, sorted by symbol class. """ self.typed_symbols = self.__get_empty_typed_symbols() all_syms = OrderedSet() for sym_name in self.symbol_names: sym = database.get_symbol(sym_name) self.__query_extra_symbols(sym, all_syms, tree, link_resolver, database) if tree.project.is_toplevel: page_path = self.link.ref else: page_path = self.project_name + '/' + self.link.ref if self.meta.get("auto-sort", True): all_syms = sorted(all_syms, key=lambda x: x.unique_name) for sym in all_syms: sym.update_children_comments() self.__resolve_symbol(sym, link_resolver, page_path) self.symbol_names.add(sym.unique_name) # Always put symbols with no parent at the end no_parent_syms = self.by_parent_symbols.pop(None, None) if no_parent_syms: self.by_parent_symbols[None] = no_parent_syms for sym_type in [ ClassSymbol, AliasSymbol, InterfaceSymbol, StructSymbol ]: syms = self.typed_symbols[sym_type].symbols if not syms: continue if self.title is None: self.title = syms[0].display_name if self.comment is None: self.comment = Comment(name=self.source_file) self.comment.short_description = syms[ 0].comment.short_description self.comment.title = syms[0].comment.title break # pylint: disable=no-self-use def __fetch_comment(self, sym, database): old_comment = sym.comment new_comment = database.get_comment(sym.unique_name) sym.comment = Comment(sym.unique_name) if new_comment: sym.comment = new_comment elif old_comment: if old_comment.filename not in ChangeTracker.all_stale_files: sym.comment = old_comment def __format_page_comment(self, formatter, link_resolver): if not self.comment: return if self.comment.short_description: self.short_description = formatter.format_comment( self.comment.short_description, link_resolver).strip() if self.short_description.startswith('<p>'): self.short_description = self.short_description[3:-4] if self.comment.title: self.title = formatter.format_comment(self.comment.title, link_resolver).strip() if self.title.startswith('<p>'): self.title = self.title[3:-4] if self.title: self.formatted_contents += '<h1 id="%s-page">%s</h1>' % ( id_from_text(self.title), self.title) self.formatted_contents += formatter.format_comment( self.comment, link_resolver) def format(self, formatter, link_resolver, output): """ Banana banana """ if not self.title and self.source_file: title = os.path.splitext(self.source_file)[0] self.title = os.path.basename(title).replace('-', ' ') self.formatted_contents = u'' self.build_path = os.path.join(formatter.get_output_folder(self), self.link.ref) if self.ast: out, diags = cmark.ast_to_html(self.ast, link_resolver) for diag in diags: warn(diag.code, message=diag.message, filename=self.source_file) self.formatted_contents += out if not self.formatted_contents: self.__format_page_comment(formatter, link_resolver) self.output_attrs = defaultdict(lambda: defaultdict(dict)) formatter.prepare_page_attributes(self) self.__format_symbols(formatter, link_resolver) self.detailed_description =\ formatter.format_page(self)[0] if output: formatter.cache_page(self) # pylint: disable=no-self-use def get_title(self): """ Banana banana """ return self.title or 'unnamed' def __discover_title(self, meta): if meta is not None and 'title' in meta: self.title = meta['title'] elif self.ast: self.title = cmark.title_from_ast(self.ast) def __format_symbols(self, formatter, link_resolver): for symbol in self.symbols: if symbol is None: continue debug( 'Formatting symbol %s in page %s' % (symbol.unique_name, self.source_file), 'formatting') symbol.detailed_description = formatter.format_symbol( symbol, link_resolver) def __query_extra_symbols(self, sym, all_syms, tree, link_resolver, database): if sym: self.__fetch_comment(sym, database) new_symbols = sum(tree.resolving_symbol_signal(self, sym), []) all_syms.add(sym) for symbol in new_symbols: self.__query_extra_symbols(symbol, all_syms, tree, link_resolver, database) def __resolve_symbol(self, symbol, link_resolver, page_path): symbol.resolve_links(link_resolver) symbol.link.ref = "%s#%s" % (page_path, symbol.unique_name) for link in symbol.get_extra_links(): link.ref = "%s#%s" % (page_path, link.id_) tsl = self.typed_symbols.get(type(symbol)) if tsl: tsl.symbols.append(symbol) by_parent_symbols = self.by_parent_symbols.get(symbol.parent_name) if not by_parent_symbols: by_parent_symbols = self.__get_empty_typed_symbols() parent_name = symbol.parent_name if parent_name is None: parent_name = 'Others symbols' self.by_parent_symbols[symbol.parent_name] = by_parent_symbols by_parent_symbols.get(type(symbol)).symbols.append(symbol) self.symbols.append(symbol) debug( 'Resolved symbol %s to page %s' % (symbol.display_name, self.link.ref), 'resolution')