def box_from_file(file: Union[str, PathLike], file_type: str = None, encoding: str = "utf-8", errors: str = "strict", **kwargs) -> Union[Box, BoxList]: """ Loads the provided file and tries to parse it into a Box or BoxList object as appropriate. :param file: Location of file :param encoding: File encoding :param errors: How to handle encoding errors :param file_type: manually specify file type: json, toml or yaml :return: Box or BoxList """ if not isinstance(file, Path): file = Path(file) if not file.exists(): raise BoxError(f'file "{file}" does not exist') file_type = file_type or file.suffix file_type = file_type.lower().lstrip(".") if file_type.lower() in converters: return converters[file_type.lower()](file, encoding, errors, **kwargs) # type: ignore raise BoxError( f'"{file_type}" is an unknown type. Please use either csv, toml, msgpack, yaml or json' )
def __delattr__(self, item): if self._box_config['frozen_box']: raise BoxError('Box is frozen') if item == '_box_config': raise BoxError('"_box_config" is protected') if item in self._protected_keys: raise BoxKeyError(f'Key name "{item}" is protected') self.__delitem__(item)
def _to_msgpack(file, _, __, **kwargs): if not msgpack_available: raise BoxError( f'File "{file}" is msgpack but no package is available to open it. Please install "msgpack"' ) try: return Box.from_msgpack(filename=file, **kwargs) except (UnpackException, ValueError): raise BoxError("File is not msgpack as expected") except BoxError: return BoxList.from_msgpack(filename=file, **kwargs)
def _to_toml(file, encoding, errors, **kwargs): if not toml_available: raise BoxError( f'File "{file}" is toml but no package is available to open it. Please install "toml"' ) try: return Box.from_toml(filename=file, encoding=encoding, errors=errors, **kwargs) except TomlDecodeError: raise BoxError("File is not TOML as expected")
def _exists(filename, create=False): path = Path(filename) if create: try: path.touch(exist_ok=True) except OSError as err: raise BoxError(f'Could not create file {filename} - {err}') else: return if not path.exists(): raise BoxError(f'File "{filename}" does not exist') if not path.is_file(): raise BoxError(f'{filename} is not a file')
def _exists(filename: Union[str, PathLike], create: bool = False) -> Path: path = Path(filename) if create: try: path.touch(exist_ok=True) except OSError as err: raise BoxError(f"Could not create file {filename} - {err}") else: return path if not path.exists(): raise BoxError(f'File "{filename}" does not exist') if not path.is_file(): raise BoxError(f"{filename} is not a file") return path
def _to_yaml( obj, filename: Union[str, PathLike] = None, default_flow_style: bool = False, encoding: str = "utf-8", errors: str = "strict", ruamel_typ: str = "rt", ruamel_attrs: Optional[Dict] = None, **yaml_kwargs, ): if not ruamel_attrs: ruamel_attrs = {} if filename: _exists(filename, create=True) with open(filename, "w", encoding=encoding, errors=errors) as f: if ruamel_available: yaml_dumper = YAML(typ=ruamel_typ) yaml_dumper.default_flow_style = default_flow_style for attr, value in ruamel_attrs.items(): setattr(yaml_dumper, attr, value) return yaml_dumper.dump(obj, stream=f, **yaml_kwargs) elif pyyaml_available: return yaml.dump(obj, stream=f, default_flow_style=default_flow_style, **yaml_kwargs) else: raise BoxError( "No YAML Parser available, please install ruamel.yaml>0.17 or PyYAML" ) else: if ruamel_available: yaml_dumper = YAML(typ=ruamel_typ) yaml_dumper.default_flow_style = default_flow_style for attr, value in ruamel_attrs.items(): setattr(yaml_dumper, attr, value) with StringIO() as string_stream: yaml_dumper.dump(obj, stream=string_stream, **yaml_kwargs) return string_stream.getvalue() elif pyyaml_available: return yaml.dump(obj, default_flow_style=default_flow_style, **yaml_kwargs) else: raise BoxError( "No YAML Parser available, please install ruamel.yaml>0.17 or PyYAML" )
def from_toml( cls, toml_string: str = None, filename: Union[str, PathLike] = None, key_name: str = "toml", encoding: str = "utf-8", errors: str = "strict", **kwargs, ): """ Transforms a toml string or file into a BoxList object :param toml_string: string to pass to `toml.load` :param filename: filename to open and pass to `toml.load` :param key_name: Specify the name of the key to pull the list from (cannot directly convert from toml) :param encoding: File encoding :param errors: How to handle encoding errors :param kwargs: parameters to pass to `Box()` :return: """ box_args = {} for arg in list(kwargs.keys()): if arg in BOX_PARAMETERS: box_args[arg] = kwargs.pop(arg) data = _from_toml(toml_string=toml_string, filename=filename, encoding=encoding, errors=errors) if key_name not in data: raise BoxError(f"{key_name} was not found.") return cls(data[key_name], **box_args)
def from_yaml( cls, yaml_string: str = None, filename: Union[str, PathLike] = None, encoding: str = "utf-8", errors: str = "strict", **kwargs, ): """ Transform a yaml object string into a BoxList object. :param yaml_string: string to pass to `yaml.load` :param filename: filename to open and pass to `yaml.load` :param encoding: File encoding :param errors: How to handle encoding errors :param kwargs: parameters to pass to `BoxList()` or `yaml.load` :return: BoxList object from yaml data """ box_args = {} for arg in list(kwargs.keys()): if arg in BOX_PARAMETERS: box_args[arg] = kwargs.pop(arg) data = _from_yaml(yaml_string=yaml_string, filename=filename, encoding=encoding, errors=errors, **kwargs) if not data: return cls(**box_args) if not isinstance(data, list): raise BoxError(f"yaml data not returned as a list but rather a {type(data).__name__}") return cls(data, **box_args)
def from_json( cls, json_string: str = None, filename: Union[str, PathLike] = None, encoding: str = "utf-8", errors: str = "strict", multiline: bool = False, **kwargs, ): """ Transform a json object string into a BoxList object. If the incoming json is a dict, you must use Box.from_json. :param json_string: string to pass to `json.loads` :param filename: filename to open and pass to `json.load` :param encoding: File encoding :param errors: How to handle encoding errors :param multiline: One object per line :param kwargs: parameters to pass to `Box()` or `json.loads` :return: BoxList object from json data """ box_args = {} for arg in list(kwargs.keys()): if arg in BOX_PARAMETERS: box_args[arg] = kwargs.pop(arg) data = _from_json( json_string, filename=filename, encoding=encoding, errors=errors, multiline=multiline, **kwargs ) if not isinstance(data, list): raise BoxError(f"json data not returned as a list, but rather a {type(data).__name__}") return cls(data, **box_args)
def __getattr__(self, item): try: try: value = self.__getitem__(item, _ignore_default=True) except KeyError: value = object.__getattribute__(self, item) except AttributeError as err: if item == "__getstate__": raise BoxKeyError(item) from _exception_cause(err) if item == "_box_config": raise BoxError( "_box_config key must exist") from _exception_cause(err) if self._box_config["conversion_box"]: safe_key = self._safe_attr(item) if safe_key in self._box_config["__safe_keys"]: return self.__getitem__( self._box_config["__safe_keys"][safe_key]) if self._box_config["default_box"]: if item.startswith("_") and item.endswith("_"): raise BoxKeyError( f"{item}: Does not exist and internal methods are never defaulted" ) return self.__get_default(item, attr=True) raise BoxKeyError(str(err)) from _exception_cause(err) return value
def from_msgpack(cls, msgpack_bytes: bytes = None, filename: Union[str, PathLike] = None, **kwargs): """ Transforms a toml string or file into a BoxList object :param msgpack_bytes: string to pass to `msgpack.packb` :param filename: filename to open and pass to `msgpack.pack` :param kwargs: parameters to pass to `Box()` :return: """ box_args = {} for arg in list(kwargs.keys()): if arg in BOX_PARAMETERS: box_args[arg] = kwargs.pop(arg) data = _from_msgpack(msgpack_bytes=msgpack_bytes, filename=filename, **kwargs) if not isinstance(data, list): raise BoxError( f"msgpack data not returned as a list but rather a {type(data).__name__}" ) return cls(data, **box_args)
def _to_yaml(data): try: return Box.from_yaml(data) except YAMLError: raise BoxError('File is not YAML as expected') except BoxError: return BoxList.from_yaml(data)
def _to_json(data): try: return Box.from_json(data) except JSONDecodeError: raise BoxError('File is not JSON as expected') except BoxError: return BoxList.from_json(data)
def _to_csv(box_list, filename: Union[str, PathLike] = None, encoding: str = "utf-8", errors: str = "strict", **kwargs): csv_column_names = list(box_list[0].keys()) for row in box_list: if list(row.keys()) != csv_column_names: raise BoxError( "BoxList must contain the same dictionary structure for every item to convert to csv" ) if filename: _exists(filename, create=True) out_data = open(filename, "w", encoding=encoding, errors=errors, newline="") else: out_data = StringIO("") writer = csv.DictWriter(out_data, fieldnames=csv_column_names, **kwargs) writer.writeheader() for data in box_list: writer.writerow(data) if not filename: return out_data.getvalue() # type: ignore out_data.close()
def from_yaml(cls, yaml_string: str = None, filename: Union[str, Path] = None, encoding: str = 'utf-8', errors: str = 'strict', **kwargs): """ Transform a yaml object string into a Box object. By default will use SafeLoader. :param yaml_string: string to pass to `yaml.load` :param filename: filename to open and pass to `yaml.load` :param encoding: File encoding :param errors: How to handle encoding errors :param kwargs: parameters to pass to `Box()` or `yaml.load` :return: Box object from yaml data """ box_args = {} for arg in kwargs.copy(): if arg in BOX_PARAMETERS: box_args[arg] = kwargs.pop(arg) data = _from_yaml(yaml_string=yaml_string, filename=filename, encoding=encoding, errors=errors, **kwargs) if not isinstance(data, dict): raise BoxError( f'yaml data not returned as a dictionary but rather a {type(data).__name__}' ) return cls(data, **box_args)
def from_msgpack( cls, msgpack_bytes: bytes = None, filename: Union[str, PathLike] = None, **kwargs, ) -> "Box": """ Transforms msgpack bytes or file into a Box object :param msgpack_bytes: string to pass to `msgpack.unpackb` :param filename: filename to open and pass to `msgpack.unpack` :param kwargs: parameters to pass to `Box()` :return: Box object """ box_args = {} for arg in kwargs.copy(): if arg in BOX_PARAMETERS: box_args[arg] = kwargs.pop(arg) data = _from_msgpack(msgpack_bytes=msgpack_bytes, filename=filename, **kwargs) if not isinstance(data, dict): raise BoxError( f"msgpack data not returned as a dictionary but rather a {type(data).__name__}" ) return cls(data, **box_args)
def __setattr__(self, key, value): if key != '_box_config' and self._box_config[ 'frozen_box'] and self._box_config['__created']: raise BoxError('Box is frozen') if key in self._protected_keys: raise BoxKeyError(f'Key name "{key}" is protected') if key == '_box_config': return object.__setattr__(self, key, value) value = self.__recast(key, value) if key not in self.keys() and (self._box_config['conversion_box'] or self._box_config['camel_killer_box']): if self._box_config['conversion_box']: k = _conversion_checks(key, self.keys(), self._box_config) self[key if not k else k] = value elif self._box_config['camel_killer_box']: for each_key in self: if key == _camel_killer(each_key): self[each_key] = value break else: self[_camel_killer(key)] = value else: self[key] = value self.__convert_and_store(key, value) self.__create_lineage()
def from_json(cls, json_string: str = None, filename: Union[str, Path] = None, encoding: str = 'utf-8', errors: str = 'strict', **kwargs): """ Transform a json object string into a Box object. If the incoming json is a list, you must use BoxList.from_json. :param json_string: string to pass to `json.loads` :param filename: filename to open and pass to `json.load` :param encoding: File encoding :param errors: How to handle encoding errors :param kwargs: parameters to pass to `Box()` or `json.loads` :return: Box object from json data """ box_args = {} for arg in kwargs.copy(): if arg in BOX_PARAMETERS: box_args[arg] = kwargs.pop(arg) data = _from_json(json_string, filename=filename, encoding=encoding, errors=errors, **kwargs) if not isinstance(data, dict): raise BoxError( f'json data not returned as a dictionary, but rather a {type(data).__name__}' ) return cls(data, **box_args)
def _parse_box_dots(item): for idx, char in enumerate(item): if char == '[': return item[:idx], item[idx:] elif char == '.': return item[:idx], item[idx + 1:] raise BoxError('Could not split box dots properly')
def __getattr__(self, item): try: try: value = self.__getitem__(item, _ignore_default=True) except KeyError: value = object.__getattribute__(self, item) except AttributeError as err: if item == '__getstate__': raise BoxKeyError(item) from None if item == '_box_config': raise BoxError('_box_config key must exist') from None kill_camel = self._box_config['camel_killer_box'] if self._box_config['conversion_box'] and item: k = _conversion_checks(item, self.keys(), self._box_config) if k: return self.__getitem__(k) if kill_camel: for k in self.keys(): if item == _camel_killer(k): return self.__getitem__(k) if self._box_config['default_box']: return self.__get_default(item) raise BoxKeyError(str(err)) from None else: if item == '_box_config': return value # return self.__convert_and_store(item, value) return value
def to_toml(self, filename: Union[str, PathLike] = None, encoding: str = "utf-8", errors: str = "strict"): raise BoxError( 'toml is unavailable on this system, please install the "toml" package' )
def items(self, dotted: Union[bool] = False): if not dotted: return super().items() if not self._box_config["box_dots"]: raise BoxError("Cannot return dotted keys as this Box does not have `box_dots` enabled") return [(k, self[k]) for k in self.keys(dotted=True)]
def popitem(self): if self._box_config["frozen_box"]: raise BoxError("Box is frozen") try: key = next(self.__iter__()) except StopIteration: raise BoxKeyError("Empty box") from None return key, self.pop(key)
def __delattr__(self, item): if self._box_config['frozen_box']: raise BoxError('Box is frozen') if item == '_box_config': raise BoxError('"_box_config" is protected') if item in self._protected_keys: raise BoxKeyError(f'Key name "{item}" is protected') try: self.__delitem__(item) except KeyError as err: if self._box_config['conversion_box']: safe_key = self._safe_attr(item) if safe_key in self._box_config['__safe_keys']: self.__delitem__(self._box_config['__safe_keys'][safe_key]) del self._box_config['__safe_keys'][safe_key] return raise BoxKeyError(err)
def _to_yaml(file, encoding, errors, **kwargs): if not yaml_available: raise BoxError( f'File "{file}" is yaml but no package is available to open it. Please install "ruamel.yaml" or "PyYAML"' ) try: return Box.from_yaml(filename=file, encoding=encoding, errors=errors, **kwargs) except YAMLError: raise BoxError("File is not YAML as expected") except BoxError: return BoxList.from_yaml(filename=file, encoding=encoding, errors=errors, **kwargs)
def __delattr__(self, item): if self._box_config["frozen_box"]: raise BoxError("Box is frozen") if item == "_box_config": raise BoxError('"_box_config" is protected') if item in self._protected_keys: raise BoxKeyError(f'Key name "{item}" is protected') try: self.__delitem__(item) except KeyError as err: if self._box_config["conversion_box"]: safe_key = self._safe_attr(item) if safe_key in self._box_config["__safe_keys"]: self.__delitem__(self._box_config["__safe_keys"][safe_key]) del self._box_config["__safe_keys"][safe_key] return raise BoxKeyError(str(err)) from None
def to_yaml( self, filename: Union[str, PathLike] = None, default_flow_style: bool = False, encoding: str = "utf-8", errors: str = "strict", **yaml_kwargs, ): raise BoxError('yaml is unavailable on this system, please install the "ruamel.yaml" or "PyYAML" package')
def from_yaml( cls, yaml_string: str = None, filename: Union[str, PathLike] = None, encoding: str = "utf-8", errors: str = "strict", **kwargs, ): raise BoxError('yaml is unavailable on this system, please install the "ruamel.yaml" or "PyYAML" package')
def from_msgpack( cls, msgpack_bytes: bytes = None, filename: Union[str, PathLike] = None, encoding: str = "utf-8", errors: str = "strict", **kwargs, ): raise BoxError('msgpack is unavailable on this system, please install the "msgpack" package')