Exemple #1
0
    def _readlink(self, path: AbstractPath, recursive: bool) -> Path:
        self.__ensure_sftp()
        lpath = cast(PurePosixPath, path)
        if recursive:
            # SFTP's normalize() raises if there's a link loop or a \
            # non-existing target, which we don't want, so we use \
            # our own algorithm.
            max_iter = 32
            cur_path = lpath
            iter_count = 0
            while self._is_symlink(cur_path) and iter_count < max_iter:
                target = PurePosixPath(self.__sftp.readlink(str(cur_path)))
                if not target.is_absolute():
                    target = cur_path.parent / target
                cur_path = target
                iter_count += 1

            if iter_count == max_iter:
                raise RuntimeError('Too many symbolic links detected')

            target = PurePosixPath(self.__sftp.normalize(str(path)))
        else:
            target = PurePosixPath(self.__sftp.readlink(str(path)))
            if not target.is_absolute():
                target = lpath.parent / target

        return Path(self, target)
    def find_page(self, model, data):
        """
        Find a page by its URL and import its data.

        Data importing has to be done here because often the page can't
        be saved until the data is imported (i.e. null fields)
        """
        try:
            url = PurePosixPath(data.pop('url'))
            if not url.is_absolute():
                raise CommandError("Path %s must be absolute" % url)

        except KeyError:
            raise CommandError("Need `url' for page")

        try:
            page = model.objects.get(url_path=normalise(url))
            self.import_data(page, data)
            page.save()
            self.stdout.write("Updating existing page %s" % url)
        except model.DoesNotExist:
            try:
                # pylint:disable=no-member
                parent = Page.objects.get(url_path=normalise(url.parent))
            except Page.DoesNotExist:
                raise CommandError("Parent of %s doesn't exist" % url)

            page = model(slug=url.name)
            self.import_data(page, data)
            parent.add_child(instance=page)
            self.stdout.write("Creating new page %s" % url)

        return page
Exemple #3
0
    def _possible_names(cls, package: PackageInfo) -> List[str]:
        names = list()
        for url_str in package.get_urls():
            url = urlparse(url_str)
            host = url.netloc
            if not host.endswith('github.com'):
                continue

            path = PurePosixPath(url.path)
            parts = path.parts
            if path.is_absolute():
                parts = parts[1:]

            if len(parts) >= 2:
                # Get the first 2 path components without extensions
                # this should handle:
                # - owner/project
                # - owner/project.git
                # - owner/project/releases

                name = PurePosixPath(parts[0])
                # strip off .git if the project name contains it
                # don't just strip off any ext because "." is valid
                name_project = parts[1]
                if name_project.endswith('.git'):
                    name_project = name_project[:-len('.git')]
                name = name.joinpath(name_project)

                names.append(str(name))

        return names
    def addPrefix(self, s: pathlib.PurePosixPath, n) -> pathlib.PurePosixPath:
        if s.is_absolute():
            if len(s.parts) == 1:
                raise Exception('Invalid path: %s' % s)
            prefix = s.parts[1]
        else:
            prefix = s.parts[0]

        assert len(prefix) != 0
        if len(prefix) > n:
            prefix = prefix[0:n]

        if s.is_absolute():
            return pathlib.PurePosixPath('/', prefix, *s.parts[1:])
        else:
            return pathlib.PurePosixPath(prefix, *s.parts)
Exemple #5
0
def default_logger(name=None,
                   logger=None,
                   level=None,
                   logfile=None,
                   message=None):
    """
    使用python默认的日志记录的装饰器

    :param logger: 日志记录器
    :param level: 日志的等级
    :param name: 日志名称
    :param logfile: 日志文件
    :param message: 日志消息
    :return: 返回一个装饰器
    """
    if logfile:
        if not PurePosixPath.is_absolute(logfile):
            raise OSError(f"{logfile} is not a absolute path!")

    def decorater(func):
        """构造内部的logger"""
        if logger:
            inner_logger = logger
        else:
            logname = name if name else "common"
            inner_logger = logging.getLogger(logname)
            inner_logger.setLevel("INFO")
            formatter = logging.Formatter(
                '%(asctime)s :%(levelname)s  %(message)s')
            fh = logging.FileHandler(logfile if logfile else PurePosixPath.
                                     joinpath(LOG_DIR, f"{logname}.log"))
            fh.setFormatter(formatter)
            streamh = logging.StreamHandler()
            streamh.setFormatter(formatter)
            logmsg = message if message else func.__name__
            loglevel = level if level else logging.INFO

        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                inner_logger.addHandler(fh)
                inner_logger.addHandler(streamh)
                inner_logger.log(loglevel, f"executing function {logmsg}")
                inner_logger.handlers.clear()
                return func(*args, **kwargs)
            except ValueError:
                raise
            except Exception:
                inner_logger.addHandler(fh)
                inner_logger.addHandler(streamh)
                inner_logger.info(
                    "#####################################################")
                inner_logger.exception(f"something wrong in function {logmsg}")
                inner_logger.info(
                    "#####################################################")
                inner_logger.handlers.clear()

        return wrapper

    return decorater
def update_modules(module_name_prefix: str, modules_path: Optional[str],
                   **kwargs):
    LOG.debug("Generating update_modules proposal")

    # Validate module name prefix
    module_name_prefix_ = PurePosixPath(module_name_prefix)
    if not module_name_prefix_.is_absolute():
        raise ValueError("module name prefix must be an absolute path")
    if any(folder in [".", ".."] for folder in module_name_prefix_.parents):
        raise ValueError(
            "module name prefix must not contain . or .. components")
    if not module_name_prefix.endswith("/"):
        raise ValueError("module name prefix must end with /")

    # Read module files and build relative module names
    modules = []
    if modules_path:
        for path in glob.glob(f"{modules_path}/**/*.js", recursive=True):
            rel_module_name = os.path.relpath(path, modules_path)
            rel_module_name = rel_module_name.replace("\\",
                                                      "/")  # Windows support
            with open(path) as f:
                js = f.read()
                modules.append({
                    "rel_name": rel_module_name,
                    "module": {
                        "js": js
                    }
                })

    proposal_args = {"prefix": module_name_prefix, "modules": modules}

    return build_proposal("update_modules", proposal_args, **kwargs)
Exemple #7
0
def normalise_path(path: H5Path,
                   current_gpath: H5Path = None,
                   relative=False) -> H5Path:
    path = H5Path(path)
    if current_gpath is not None:
        current_gpath = H5Path(current_gpath)
        path = current_gpath / path

    if not path.is_absolute():
        raise ValueError("Path must be absolute")

    parts: List[str] = []
    for part in path.parts:
        if set(part) == {"."}:
            for _ in part[1:]:
                try:
                    parts.pop()
                    if len(parts) == 0:
                        raise IndexError()
                except IndexError:
                    raise ValueError("Tried to traverse beyond root")
        else:
            parts.append(part)

    out = H5Path(*parts)
    if relative and current_gpath:
        out = out.relative_to(current_gpath)
    return out
Exemple #8
0
    def write_path_template(self):
        rootp = self.reg_root
        if self.path_semantics == 'posix':
            ret = PurePosixPath(self._write_path_template)
        elif self.path_semantics == 'windows':
            ret = PureWindowsPath(self._write_path_template)
        elif self.path_semantics is None:
            # We are forced to guess which path semantics to use.
            # Guess that the AD driver is running on the same OS as this client.
            ret = PurePath(self._write_path_template)
        else:
            # This should never happen, but just for the sake of future-proofing...
            raise ValueError(
                f"Cannot handle path_semantics={self.path_semantics}")

        if self._read_path_template is None and rootp not in ret.parents:
            if not ret.is_absolute():
                ret = rootp / ret
            else:
                raise ValueError(
                    ('root: {!r} in not consistent with '
                     'read_path_template: {!r}').format(rootp, ret))

        return _ensure_trailing_slash(str(ret),
                                      path_semantics=self.path_semantics)
Exemple #9
0
def _decode_name(name):
    if name is None:
        return None
    path = PurePosixPath(name)
    if path.is_absolute():
        path = path.relative_to(path.root)
    parts = [i for i in path.parts if i not in ('.', '..')]
    return PurePosixPath(*parts)
Exemple #10
0
 def _check_path_relative(
     self,
     attribute: attr.Attribute[PurePosixPath],
     value: PurePosixPath,
 ) -> None:
     if value.is_absolute():
         raise ConfigSemanticException(
             f'Directory path "{value}" in addon config must be relative (not '
             "absolute)")
 def mkdir(self, cmd):
     '''Create an empty directory.      Usage: mkdir <directory>'''
     directory = PurePosixPath(cmd[1])
     if not directory.is_absolute():
         directory = self.cwd / directory
     r = requests.put(f'{self.url}/mkdir', data={'path': str(directory)})
     if not r.ok:
         print(r.text)
         return
Exemple #12
0
def main(args):
    import logging
    logging.basicConfig(level=logging.DEBUG)
    logger = logging.getLogger(__name__)

    parser = argparse.ArgumentParser(description='Generate a distribution archive.', prog=args[0])

    parser.add_argument('build_dir',
        help='the build directory (defaults to CWD)',
        default=Path(os.getcwd()),
        type=Path,
        nargs='?',
    )

    parser.add_argument('output',
        help='the output file path',
        type=Path,
    )

    parser.add_argument('--format',
        help='the archive format',
        default='zip',
        choices=sorted(map(lambda x: x[0], shutil.get_archive_formats())),
    )

    parser.add_argument('--prefix',
        help='add a common prefix to all files and directories in the archive',
        default=None,
    )

    add_common_args(parser)
    args = parser.parse_args(args[1:])

    if args.prefix is not None:
        p = PurePosixPath(args.prefix)

        if p.is_absolute() or p.is_reserved() or '..' in p.parts:
            raise ValueError('Bad prefix: {}'.format(args.prefix))

        args.prefix = str(p)

        if args.prefix == '.':
            args.prefix = None

    with temp_install(args.build_dir) as install_dir:
        if args.prefix is not None:
            os.chdir(str(install_dir.parent))
            install_dir.rename(install_dir.parent / args.prefix)
            archive = shutil.make_archive(str(args.output), args.format, '.', str(args.prefix))
        else:
            archive = shutil.make_archive(str(args.output), args.format, str(install_dir))

        archive = Path(archive)
        archive.rename(args.output)

    print("Generated distribution archive {}".format(str(args.output)))
Exemple #13
0
 def convert_posix_to_win32(rootfs, cwd, path):
     # rootfs is a concrete path.
     rootfs = Path(rootfs)
     # cwd and path are pure paths
     cwd = PurePosixPath(cwd[1:])
     path = PurePosixPath(path)
     if path.is_absolute():
         return rootfs / path.relative_to(path.anchor)
     else:
         return rootfs / cwd / path
Exemple #14
0
 def convert_posix_to_win32(rootfs, cwd, path):
     # rootfs is a concrete path.
     rootfs = Path(rootfs)
     # cwd and path are pure paths
     cwd = PurePosixPath(cwd[1:])
     path = PurePosixPath(path)
     if path.is_absolute():
         return rootfs / PathUtils.normalize(path)
     else:
         return rootfs / PathUtils.normalize(cwd / path)
Exemple #15
0
    def convert_posix_to_win32(rootfs: Union[str, Path], cwd: str,
                               path: str) -> Path:
        _rootfs = Path(rootfs)
        _cwd = PurePosixPath(cwd[1:])
        _path = PurePosixPath(path)

        if _path.is_absolute():
            return _rootfs / QlPathManager.normalize(_path)
        else:
            return _rootfs / QlPathManager.normalize(_cwd / _path)
Exemple #16
0
def reroot_path(filename: PurePosixPath, docpath: PurePath,
                project_root: Path) -> Tuple[FileId, Path]:
    """Files within a project may refer to other files. Return a canonical path
       relative to the project root."""
    if filename.is_absolute():
        rel_fn = FileId(*filename.parts[1:])
    else:
        rel_fn = FileId(
            *docpath.parent.joinpath(filename).parts).collapse_dots()
    return rel_fn, project_root.joinpath(rel_fn).resolve()
Exemple #17
0
 def resolve_path(self, path):
     """
     Resolves strings with variables relating to the job id.
     """
     path = path.replace('$JOB_ID', self.job_id)
     path = path.replace('$JOB_NUMBER', self.job_number)
     path = PurePosixPath(path)
     if path.is_absolute():
         return path
     return self.working_dir / path
Exemple #18
0
    def __init__(
        self,
        *,
        datascheme: Optional[DataScheme] = None,
        protocol: Protocol,
        hostname: str,
        port: Optional[int] = None,
        path: PurePosixPath,
        search: Optional[Mapping[str, str]] = None,
        hash_: Optional[str] = None,
        search_quoting_method: SearchQuotingMethod = SearchQuotingMethod.
        QUOTE_PLUS,
    ):
        if not path.is_absolute():
            raise ValueError("Path '{path}' is not absolute")
        path_parts: List[str] = []
        for part in path.as_posix().split("/"):
            if part == "." or part == "":
                continue
            if part == "..":
                if len(path_parts) > 0:
                    _ = path_parts.pop()
            else:
                path_parts.append(part)

        self.datascheme = datascheme
        self.protocol = protocol
        self.hostname = hostname
        self.host = hostname + ("" if port is None else f":{port}")
        self.port = port
        self.path = PurePosixPath("/") / "/".join(path_parts)
        self.search = search or {}
        self.hash_ = hash_
        self.schemeless_raw = f"{protocol}://{self.host}"
        self.schemeless_raw += str(path)
        if self.search:
            if search_quoting_method == SearchQuotingMethod.QUOTE_PLUS:
                quote_via = quote_plus
            else:
                quote_via = quote
            self.schemeless_raw += "?" + urlencode(
                self.search, doseq=True, quote_via=quote_via)
        if self.hash_:
            self.schemeless_raw += "#" + self.hash_

        if self.datascheme:
            self.raw = f"{self.datascheme}+{self.schemeless_raw}"
            self.double_protocol_raw = f"{self.datascheme}://{self.schemeless_raw}"
        else:
            self.raw = self.schemeless_raw
            self.double_protocol_raw = self.raw

        if hostname == "" and protocol not in (Protocol.FILE, Protocol.MEMORY):
            raise ValueError(f"Missing hostname in {self.raw}")
        super().__init__()
    def ls(self, cmd):
        '''Lists directory contents.       Usage: ls [directory]'''
        directory = self.cwd
        if len(cmd) > 1:
            directory = PurePosixPath(cmd[1])

        if not directory.is_absolute():
            directory = self.cwd / directory

        r = requests.get(f'{self.url}/ls', data={'path': str(directory)})
        print(*r.json(), sep='\n')
Exemple #20
0
 def workfile_for_path(self, file):
     # Transform work file path to some canonical form
     posix_path = PurePosixPath(file)
     if posix_path.is_absolute():
         # TODO: what should we do with absolute workpath here?
         raise Exception("Absolute work path not allowed: %s" % posix_path)
     if posix_path not in self.workfiles:
         wf = WorkFile(posix_path)
         self.workfiles[posix_path] = wf
         return wf
     else:
         return self.workfiles[posix_path]
Exemple #21
0
def set_module(module_name: str, module_path: str, **kwargs):
    module_name_ = PurePosixPath(module_name)
    if not module_name_.is_absolute():
        raise ValueError("module name must be an absolute path")
    if any(folder in [".", ".."] for folder in module_name_.parents):
        raise ValueError("module name must not contain . or .. components")
    if module_name_.suffix == ".js":
        with open(module_path) as f:
            js = f.read()
        proposal_args = {"name": module_name, "module": {"js": js}}
    else:
        raise ValueError("module name must end with .js")
    return build_proposal("set_module", proposal_args, **kwargs)
Exemple #22
0
    def validate_pre_sds_if_applicable(self,
                                       hds: HomeDs) -> Optional[TextRenderer]:
        if self.path_str == '':
            return text_docs.single_line(_EMPTY_FILE_NAME)

        path = PurePosixPath(self.path_str)
        if path.is_absolute():
            return text_docs.single_line(
                str_constructor.FormatMap(
                    'A {FILE_NAME} must not be absolute: {path}', {
                        'FILE_NAME': syntax.FILE_NAME.name,
                        'path': str(path),
                    }))
        return None
Exemple #23
0
def systemd_escape_path(path: pathlib.PurePosixPath) -> str:
    """Escape a path for inclusion in a systemd unit name.

    See the 'systemd-escape --path' command for details.
    """
    if not path.is_absolute():
        raise ValueError("systemd_escape_path can only escape absolute paths")
    if ".." in path.parts:
        raise ValueError(
            "systemd_escape_path can only escape paths without '..' components"
        )
    stdout: bytes = subprocess.check_output(
        ["systemd-escape", "--path", "--", str(path)]
    )
    return stdout.decode("utf-8").rstrip("\n")
Exemple #24
0
 def fetch_resource(self, resource_name):
     resource_table = self._resource_tables[resource_name]
     for destination, source_name in resource_table.items():
         source_subpath = PurePosixPath(source_name)
         if (
             source_subpath.is_absolute() or
             len(source_subpath.parts) > 1 or '..' in source_subpath.parts
         ):
             raise RuntimeError(source_name)
         source_path = self._resource_dir_path / source_name
         destination_subpath = PurePosixPath(destination)
         if (
             destination_subpath.is_absolute() or
             '..' in destination_subpath.parts
         ):
             raise RuntimeError(destination)
         destination_path: PosixPath = self.root / destination_subpath
         if not destination_path.parent.exists():
             destination_path.parent.mkdir(parents=True)
         if destination_path.is_dir():
             raise IsADirectoryError(str(destination))
         if destination_path.is_symlink():
             destination_path.unlink()
         shutil.copyfile(str(source_path), str(destination_path))
Exemple #25
0
def systemd_escape_path(path: pathlib.PurePosixPath) -> str:
    """Escape a path for inclusion in a systemd unit name.

    See the 'systemd-escape --path' command for details.
    """
    if not path.is_absolute():
        raise ValueError("systemd_escape_path can only escape absolute paths")
    if ".." in path.parts:
        raise ValueError(
            "systemd_escape_path can only escape paths without '..' components"
        )
    stdout: bytes = subprocess.check_output(
        ["systemd-escape", "--path", "--", str(path)]
    )
    return stdout.decode("utf-8").rstrip("\n")
Exemple #26
0
def find_resource(root: Site, path: PurePosixPath) -> Resource:
    """Given a path-like string, walk the tree and return object.

    Resources in a resource tree can be found with path-like lookups.
    This implementation uses ``PurePosixPath`` as the path language.

    Paths must start with a leading ``/``, but a trailing slash is optional.
    Folder paths can be with or without a trailing ``index`` part.
    If the provided path ends with ``index``, it will be removed for the
    purposes of walking the resource tree.

    Args:
        root: The top of the resource tree.
        path: A specification of how to walk down to the target resource.

    Returns:
        The resource at that path.

    Raises:
        ValueError: Paths must start with a slash.
        KeyError: Asking for a path part that isn't in the dict at that
            part of the tree.
    """
    if not path.is_absolute():
        # ResourceLike paths must start with a slash, so this is
        # probably a static resource path
        m = f'ResourceLike path "{path}" must start with a slash'
        raise ValueError(m)

    # "Normalize" the path if it ends with ``index``.
    pp = path.parts
    # noinspection PyTypeChecker
    parts = iter(pp[1:-1] if path.name == "index" else pp[1:])

    # Now walk the tree
    # TODO: Fix the algorithm here to not need all the cast()
    result = root
    while True:
        try:
            part = next(parts)
            try:
                result = result[part]  # type: ignore
            except KeyError:  # noqa: B904
                m = f'No resource at path "{path}"'
                raise KeyError(m)  # noqa: B904
        except StopIteration:
            break
    return cast(Resource, result)
Exemple #27
0
    def validate_pre_sds_if_applicable(self, hds: HomeDs) -> Optional[TextRenderer]:
        path_str = self.path_str
        if path_str == '':
            return text_docs.single_line(_ERR__FILE_NAME__EMPTY)

        for path_separator in _PATH_SEPARATORS:
            if path_separator in path_str:
                return self._err_msg(path_str, _ERR__FILE_NAME__PATH_SEPARATOR_IN_FILE_NAME)

        path = PurePosixPath(path_str)
        if path.is_absolute():
            return self._err_msg(path_str, _ERR__FILE_NAME__ABSOLUTE)
        if '..' in path.parts:
            return self._err_msg(path_str, _ERR__FILE_NAME__RELATIVE_COMPONENTS)

        return None
    def rmdir(self, cmd):
        '''Remove a directory.             Usage: rmdir <directory>'''
        directory = PurePosixPath(cmd[1])
        if not directory.is_absolute():
            directory = self.cwd / directory

        r = requests.delete(f'{self.url}/rmdir', data={'path': str(directory)})
        if not r.ok:
            print(r.text)
            if r.status_code == 400:
                answer = ''
                while answer not in ['y', 'n']:
                    print("Do you still want to delete? (y/n)")
                    answer = input()
                if answer == 'y':
                    payload = {'path': str(directory), 'force': 'force'}
                    r = requests.delete(f'{self.url}/rmdir', data=payload)
def full_split(_path):
    """
    Return a list with all the intermediate paths.
    The input path must be a POSIX path string (i.e., Linux or OSX).
    """
    intermediate_paths = list()

    _path = PurePosixPath(_path)

    if _path.is_absolute():
        _path = _path.relative_to("/")

    parts = _path.parts

    for i in range(1, len(parts)):
        intermediate_paths.append(PurePosixPath(*parts[0:i]).as_posix())

    return intermediate_paths
Exemple #30
0
def update_modules(module_name_prefix: str, modules_path: Optional[str],
                   **kwargs):
    LOG.debug("Generating update_modules proposal")

    # Validate module name prefix
    module_name_prefix_ = PurePosixPath(module_name_prefix)
    if not module_name_prefix_.is_absolute():
        raise ValueError("module name prefix must be an absolute path")
    if any(folder in [".", ".."] for folder in module_name_prefix_.parents):
        raise ValueError(
            "module name prefix must not contain . or .. components")
    if not module_name_prefix.endswith("/"):
        raise ValueError("module name prefix must end with /")

    # Read module files and build relative module names
    modules = []
    if modules_path:
        modules = read_modules(modules_path)

    proposal_args = {"prefix": module_name_prefix, "modules": modules}

    return build_proposal("update_modules", proposal_args, **kwargs)
    def cd(self, cmd):
        '''Changes the current directory.  Usage: cd <directory>'''
        directory = cmd[1]
        parts = directory.split('/')
        while parts[0] == '..':
            self.cwd = self.cwd.parent
            parts = parts[1:]
            if len(parts) == 0:
                return

        directory = PurePosixPath('/'.join(parts))

        if not directory.is_absolute():
            directory = self.cwd / directory

        payload = {'path': str(directory)}
        r = requests.get(f'{self.url}/ls', data=payload)
        if not r.ok:
            print(r.text)
            return

        # If request passed, the folder exists
        self.cwd = directory
Exemple #32
0
def get_path_from_url(prefix, url, url_to_path):
    if is_external(url, prefix):
        raise ValueError(f'external url {url}')

    path = url.path

    if path.startswith(prefix.path):
        path = path[len(prefix.path):]

    result = url_to_path(path)

    result = PurePosixPath(result)

    if result.is_absolute():
        raise ValueError(
            f"Path may not be absolute: {result}(from {url.to_url()})")
    assert '.' not in result.parts
    if '..' in result.parts:
        raise ValueError(
            f"Path may not contain /../ segment: {result}(from {url.to_url()})"
        )

    return result
Exemple #33
0
    def get_by_path(user, path):
        '''
        Checks whether directory specified by given `path` exists for
        given `user`.

        Return value is a 2-tuple (n, directory) where directory
        is the deepest ancestor found or the found directory, and n is the
        number of directories that were not found.
        So if given path = "/a/b/c/d/e", and "/a/b/c" exists but "d" doesn't,
        This will return `(2, <directory object for /a/b/c>)` since "d" and
        "e" were not found.

        Also note that this function ignores anything that isn't a directory.
        Using the above example, if "d" is a file, the return value will still
        be the same since "d" isn't a directory and thus is ignored.

        Arguments:
        user -- user to be checked; Should match settings.AUTH_USER_MODEL.
        path -- a path-like object specifying a POSIX path to check.
                If relative, considered as relative-from-root.
        '''
        path = PurePosixPath(path)
        parts = iter(path.parts)
        if path.is_absolute():
            next(parts)  # discard first item

        current_dir = UserStorage.objects.filter(
            user=user).select_related('root_dir').get().root_dir

        for (i, part) in enumerate(parts):
            try:
                next_dir = current_dir.children.get(name__exact=part).directory
            except (DirectoryEntry.DoesNotExist, Directory.DoesNotExist):
                return (len(parts) - i, current_dir)
            current_dir = next_dir
        return (0, current_dir)
Exemple #34
0
 def from_source_path( cls, source_path: PurePosixPath
 ) -> 'RecordPath':
     if source_path.is_absolute():
         raise ValueError(source_path)
     return cls.from_parts(source_path.parts)