Coverage for src/hdmf/common/__init__.py: 75%

123 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-04 02:57 +0000

1'''This package will contain functions, classes, and objects 

2for reading and writing data in according to the HDMF-common specification 

3''' 

4import os.path 

5from copy import deepcopy 

6 

7CORE_NAMESPACE = 'hdmf-common' 

8EXP_NAMESPACE = 'hdmf-experimental' 

9 

10 

11from ..spec import NamespaceCatalog # noqa: E402 

12from ..utils import docval, getargs, get_docval # noqa: E402 

13from ..backends.io import HDMFIO # noqa: E402 

14from ..backends.hdf5 import HDF5IO # noqa: E402 

15from ..validate import ValidatorMap # noqa: E402 

16from ..build import BuildManager, TypeMap # noqa: E402 

17from ..container import _set_exp # noqa: E402 

18 

19 

20# a global type map 

21global __TYPE_MAP 

22 

23 

24# a function to register a container classes with the global map 

25@docval({'name': 'data_type', 'type': str, 'doc': 'the data_type to get the spec for'}, 

26 {'name': 'namespace', 'type': str, 'doc': 'the name of the namespace', 'default': CORE_NAMESPACE}, 

27 {"name": "container_cls", "type": type, 

28 "doc": "the class to map to the specified data_type", 'default': None}, 

29 is_method=False) 

30def register_class(**kwargs): 

31 """Register an Container class to use for reading and writing a data_type from a specification 

32 If container_cls is not specified, returns a decorator for registering an Container subclass 

33 as the class for data_type in namespace. 

34 """ 

35 data_type, namespace, container_cls = getargs('data_type', 'namespace', 'container_cls', kwargs) 

36 if namespace == EXP_NAMESPACE: 

37 def _dec(cls): 

38 _set_exp(cls) 

39 __TYPE_MAP.register_container_type(namespace, data_type, cls) 

40 return cls 

41 else: 

42 def _dec(cls): 

43 __TYPE_MAP.register_container_type(namespace, data_type, cls) 

44 return cls 

45 

46 if container_cls is None: 46 ↛ 49line 46 didn't jump to line 49, because the condition on line 46 was never false

47 return _dec 

48 else: 

49 _dec(container_cls) 

50 

51 

52# a function to register an object mapper for a container class 

53@docval({"name": "container_cls", "type": type, 

54 "doc": "the Container class for which the given ObjectMapper class gets used for"}, 

55 {"name": "mapper_cls", "type": type, "doc": "the ObjectMapper class to use to map", 'default': None}, 

56 is_method=False) 

57def register_map(**kwargs): 

58 """Register an ObjectMapper to use for a Container class type 

59 If mapper_cls is not specified, returns a decorator for registering an ObjectMapper class 

60 as the mapper for container_cls. If mapper_cls specified, register the class as the mapper for container_cls 

61 """ 

62 container_cls, mapper_cls = getargs('container_cls', 'mapper_cls', kwargs) 

63 

64 def _dec(cls): 

65 __TYPE_MAP.register_map(container_cls, cls) 

66 return cls 

67 if mapper_cls is None: 67 ↛ 70line 67 didn't jump to line 70, because the condition on line 67 was never false

68 return _dec 

69 else: 

70 _dec(mapper_cls) 

71 

72 

73def __get_resources(): 

74 try: 

75 from importlib.resources import files 

76 except ImportError: 

77 # TODO: Remove when python 3.9 becomes the new minimum 

78 from importlib_resources import files 

79 

80 __location_of_this_file = files(__name__) 

81 __core_ns_file_name = 'namespace.yaml' 

82 __schema_dir = 'hdmf-common-schema/common' 

83 

84 ret = dict() 

85 ret['namespace_path'] = str(__location_of_this_file / __schema_dir / __core_ns_file_name) 

86 return ret 

87 

88 

89def _get_resources(): 

90 # LEGACY: Needed to support legacy implementation. 

91 return __get_resources() 

92 

93 

94@docval({'name': 'namespace_path', 'type': str, 

95 'doc': 'the path to the YAML with the namespace definition'}, 

96 returns="the namespaces loaded from the given file", rtype=tuple, 

97 is_method=False) 

98def load_namespaces(**kwargs): 

99 ''' 

100 Load namespaces from file 

101 ''' 

102 namespace_path = getargs('namespace_path', kwargs) 

103 return __TYPE_MAP.load_namespaces(namespace_path) 

104 

105 

106def available_namespaces(): 

107 return __TYPE_MAP.namespace_catalog.namespaces 

108 

109 

110# a function to get the container class for a give type 

111@docval({'name': 'data_type', 'type': str, 

112 'doc': 'the data_type to get the Container class for'}, 

113 {'name': 'namespace', 'type': str, 'doc': 'the namespace the data_type is defined in'}, 

114 is_method=False) 

115def get_class(**kwargs): 

116 """Get the class object of the Container subclass corresponding to a given neurdata_type. 

117 """ 

118 data_type, namespace = getargs('data_type', 'namespace', kwargs) 

119 return __TYPE_MAP.get_dt_container_cls(data_type, namespace) 

120 

121 

122@docval({'name': 'extensions', 'type': (str, TypeMap, list), 

123 'doc': 'a path to a namespace, a TypeMap, or a list consisting paths to namespaces and TypeMaps', 

124 'default': None}, 

125 returns="the namespaces loaded from the given file", rtype=tuple, 

126 is_method=False) 

127def get_type_map(**kwargs): 

128 ''' 

129 Get a BuildManager to use for I/O using the given extensions. If no extensions are provided, 

130 return a BuildManager that uses the core namespace 

131 ''' 

132 extensions = getargs('extensions', kwargs) 

133 type_map = None 

134 if extensions is None: 134 ↛ 137line 134 didn't jump to line 137, because the condition on line 134 was never false

135 type_map = deepcopy(__TYPE_MAP) 

136 else: 

137 if isinstance(extensions, TypeMap): 

138 type_map = extensions 

139 else: 

140 type_map = deepcopy(__TYPE_MAP) 

141 if isinstance(extensions, list): 

142 for ext in extensions: 

143 if isinstance(ext, str): 

144 type_map.load_namespaces(ext) 

145 elif isinstance(ext, TypeMap): 

146 type_map.merge(ext) 

147 else: 

148 msg = 'extensions must be a list of paths to namespace specs or a TypeMaps' 

149 raise ValueError(msg) 

150 elif isinstance(extensions, str): 

151 type_map.load_namespaces(extensions) 

152 elif isinstance(extensions, TypeMap): 

153 type_map.merge(extensions) 

154 return type_map 

155 

156 

157@docval(*get_docval(get_type_map), 

158 returns="a build manager with namespaces loaded from the given file", rtype=BuildManager, 

159 is_method=False) 

160def get_manager(**kwargs): 

161 ''' 

162 Get a BuildManager to use for I/O using the given extensions. If no extensions are provided, 

163 return a BuildManager that uses the core namespace 

164 ''' 

165 type_map = get_type_map(**kwargs) 

166 return BuildManager(type_map) 

167 

168 

169@docval({'name': 'io', 'type': HDMFIO, 

170 'doc': 'the HDMFIO object to read from'}, 

171 {'name': 'namespace', 'type': str, 

172 'doc': 'the namespace to validate against', 'default': CORE_NAMESPACE}, 

173 {'name': 'experimental', 'type': bool, 

174 'doc': 'data type is an experimental data type', 'default': False}, 

175 returns="errors in the file", rtype=list, 

176 is_method=False) 

177def validate(**kwargs): 

178 """Validate an file against a namespace""" 

179 io, namespace, experimental = getargs('io', 'namespace', 'experimental', kwargs) 

180 if experimental: 180 ↛ 181line 180 didn't jump to line 181, because the condition on line 180 was never true

181 namespace = EXP_NAMESPACE 

182 builder = io.read_builder() 

183 validator = ValidatorMap(io.manager.namespace_catalog.get_namespace(name=namespace)) 

184 return validator.validate(builder) 

185 

186 

187@docval(*get_docval(HDF5IO.__init__), is_method=False) 

188def get_hdf5io(**kwargs): 

189 """ 

190 A convenience method for getting an HDF5IO object using an HDMF-common build manager if none is provided. 

191 """ 

192 manager = getargs('manager', kwargs) 

193 if manager is None: 

194 kwargs['manager'] = get_manager() 

195 return HDF5IO(**kwargs) 

196 

197 

198# load the hdmf-common namespace 

199__resources = __get_resources() 

200if os.path.exists(__resources['namespace_path']): 200 ↛ 223line 200 didn't jump to line 223, because the condition on line 200 was never false

201 __TYPE_MAP = TypeMap(NamespaceCatalog()) 

202 

203 load_namespaces(__resources['namespace_path']) 

204 

205 # import these so the TypeMap gets populated 

206 from . import io as __io # noqa: E402 

207 

208 from . import table # noqa: E402 

209 from . import alignedtable # noqa: E402 

210 from . import sparse # noqa: E402 

211 from . import resources # noqa: E402 

212 from . import multi # noqa: E402 

213 

214 # register custom class generators 

215 from .io.table import DynamicTableGenerator 

216 __TYPE_MAP.register_generator(DynamicTableGenerator) 

217 

218 from .. import Data, Container 

219 __TYPE_MAP.register_container_type(CORE_NAMESPACE, 'Container', Container) 

220 __TYPE_MAP.register_container_type(CORE_NAMESPACE, 'Data', Data) 

221 

222else: 

223 raise RuntimeError("Unable to load a TypeMap - no namespace file found") 

224 

225 

226DynamicTable = get_class('DynamicTable', CORE_NAMESPACE) 

227VectorData = get_class('VectorData', CORE_NAMESPACE) 

228VectorIndex = get_class('VectorIndex', CORE_NAMESPACE) 

229ElementIdentifiers = get_class('ElementIdentifiers', CORE_NAMESPACE) 

230DynamicTableRegion = get_class('DynamicTableRegion', CORE_NAMESPACE) 

231EnumData = get_class('EnumData', EXP_NAMESPACE) 

232CSRMatrix = get_class('CSRMatrix', CORE_NAMESPACE) 

233HERD = get_class('HERD', EXP_NAMESPACE) 

234SimpleMultiContainer = get_class('SimpleMultiContainer', CORE_NAMESPACE) 

235AlignedDynamicTable = get_class('AlignedDynamicTable', CORE_NAMESPACE)