"""Functionality related with node paths in a PyTables file. Variables ========= `__docformat`__ The format of documentation strings in this module. """ import re import warnings import keyword from .exceptions import NaturalNameWarning __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" _python_id_re = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') """Python identifier regular expression.""" _reserved_id_re = re.compile('^_[cfgv]_') """PyTables reserved identifier regular expression. - c: class variables - f: class public methods - g: class private methods - v: instance variables """ _hidden_name_re = re.compile('^_[pi]_') """Nodes with a name *matching* this expression are considered hidden. For instance, ``name`` whould be visible while ``_i_name`` would not. """ _hidden_path_re = re.compile('/_[pi]_') """Nodes with a path *containing* this expression are considered hidden. For instance, a node with a pathname like ``/a/b/c`` would be visible while nodes with pathnames like ``/a/c/_i_x`` or ``/a/_p_x/y`` would not. """ _warnInfo = ( "you will not be able to use natural naming to access this object; " "using ``getattr()`` will still work, though") """Warning printed when a name will not be reachable through natural naming""" def check_attribute_name(name): """Check the validity of the `name` of an attribute in AttributeSet. If the name is not valid, a ``ValueError`` is raised. If it is valid but it can not be used with natural naming, a `NaturalNameWarning` is issued. >>> warnings.simplefilter("ignore") >>> check_attribute_name('a') >>> check_attribute_name('a_b') >>> check_attribute_name('a:b') # NaturalNameWarning >>> check_attribute_name('/a/b') # NaturalNameWarning >>> check_attribute_name('/') # NaturalNameWarning >>> check_attribute_name('.') # NaturalNameWarning >>> check_attribute_name('__members__') Traceback (most recent call last): ... ValueError: ``__members__`` is not allowed as an object name >>> check_attribute_name(1) Traceback (most recent call last): ... TypeError: object name is not a string: 1 >>> check_attribute_name('') Traceback (most recent call last): ... ValueError: the empty string is not allowed as an object name """ if not isinstance(name, str): # Python >= 2.3 raise TypeError(f"object name is not a string: {name!r}") if name == '': raise ValueError("the empty string is not allowed as an object name") # Check whether `name` is a valid Python identifier. if not _python_id_re.match(name): warnings.warn("object name is not a valid Python identifier: %r; " "it does not match the pattern ``%s``; %s" % (name, _python_id_re.pattern, _warnInfo), NaturalNameWarning, stacklevel=2) return # However, Python identifiers and keywords have the same form. if keyword.iskeyword(name): warnings.warn("object name is a Python keyword: %r; %s" % (name, _warnInfo), NaturalNameWarning, stacklevel=2) return # Still, names starting with reserved prefixes are not allowed. if _reserved_id_re.match(name): raise ValueError("object name starts with a reserved prefix: %r; " "it matches the pattern ``%s``" % (name, _reserved_id_re.pattern)) # ``__members__`` is the only exception to that rule. if name == '__members__': raise ValueError("``__members__`` is not allowed as an object name") def check_name_validity(name): """Check the validity of the `name` of a Node object, which more limited than attribute names. If the name is not valid, a ``ValueError`` is raised. If it is valid but it can not be used with natural naming, a `NaturalNameWarning` is issued. >>> warnings.simplefilter("ignore") >>> check_name_validity('a') >>> check_name_validity('a_b') >>> check_name_validity('a:b') # NaturalNameWarning >>> check_name_validity('/a/b') Traceback (most recent call last): ... ValueError: the ``/`` character is not allowed in object names: '/a/b' >>> check_name_validity('.') Traceback (most recent call last): ... ValueError: ``.`` is not allowed as an object name >>> check_name_validity('') Traceback (most recent call last): ... ValueError: the empty string is not allowed as an object name """ check_attribute_name(name) # Check whether `name` is a valid HDF5 name. # http://hdfgroup.org/HDF5/doc/UG/03_Model.html#Structure if name == '.': raise ValueError("``.`` is not allowed as an object name") elif '/' in name: raise ValueError("the ``/`` character is not allowed " "in object names: %r" % name) def join_path(parentpath, name): """Join a *canonical* `parentpath` with a *non-empty* `name`. .. versionchanged:: 3.0 The *parentPath* parameter has been renamed into *parentpath*. >>> join_path('/', 'foo') '/foo' >>> join_path('/foo', 'bar') '/foo/bar' >>> join_path('/foo', '/foo2/bar') '/foo/foo2/bar' >>> join_path('/foo', '/') '/foo' """ if name.startswith('./'): # Support relative paths (mainly for links) name = name[2:] if parentpath == '/' and name.startswith('/'): pstr = '%s' % name elif parentpath == '/' or name.startswith('/'): pstr = f'{parentpath}{name}' else: pstr = f'{parentpath}/{name}' if pstr.endswith('/'): pstr = pstr[:-1] return pstr def split_path(path): """Split a *canonical* `path` into a parent path and a node name. The result is returned as a tuple. The parent path does not include a trailing slash. >>> split_path('/') ('/', '') >>> split_path('/foo/bar') ('/foo', 'bar') """ lastslash = path.rfind('/') ppath = path[:lastslash] name = path[lastslash + 1:] if ppath == '': ppath = '/' return (ppath, name) def isvisiblename(name): """Does this `name` make the named node a visible one?""" return _hidden_name_re.match(name) is None def isvisiblepath(path): """Does this `path` make the named node a visible one?""" return _hidden_path_re.search(path) is None def _test(): """Run ``doctest`` on this module.""" import doctest doctest.testmod() if __name__ == '__main__': _test()