Module core.utils.paths

Utilities to help work with filesystem paths within the framework.

To make projects work on multiple developers machines, it is important that paths that need to be stored in the state be written as relative paths. The relative paths can be started from two places: Workspace and Intermediate.

The Workspace base is the base folder for all information that is unique to the current work space. This will be default be the directory that a cli command is issued from. Changing this value should be done with caution and understanding of how to framework/cli bootstraps itself.

The Intermediate base is the base folder for any artifacts that are created by the framework that need to be uploaded to the cloud. Any file made in this folder should be able to be deleted and recreated without affecting the larger framework. Right now this folder will be within the wWorkspace folder, but it is helpful to have the location as a seperate entity to make it possible in the future to have a smart implementation of a cache of artifacts that can be shared across multiple workspaces.

TODO: Make a custom type that denotes a path is a relative path to the workspace. This could be a pydantic type that way the constraints are validated at runtime. This is a important part to improve the DX of the working with library.

Expand source code
"""Utilities to help work with filesystem paths within the framework.


To make projects work on multiple developers machines, it is important that paths that
need to be stored in the state be written as relative paths. The relative paths can be
started from two places: Workspace and Intermediate.

The Workspace base is the base folder for all information that is unique to the current work
space. This will be default be the directory that a cli command is issued from. Changing
this value should be done with caution and understanding of how to framework/cli bootstraps
itself.


The Intermediate base is the base folder for any artifacts that are created by the framework
that need to be uploaded to the cloud. Any file made in this folder should be able to be deleted
and recreated without affecting the larger framework. Right now this folder will be within the
wWorkspace folder, but it is helpful to have the location as a seperate entity to make it possible
in the future to have a smart implementation of a cache of artifacts that can be shared across multiple
workspaces.



TODO: Make a custom type that denotes a path is a relative path to the workspace.
This could be a pydantic type that way the constraints are validated at runtime.
This is a important part to improve the DX of the working with library.
"""

import os
from typing import Union

from pydantic import DirectoryPath
from pydantic.types import FilePath

from core.constructs.workspace import Workspace


def get_relative_to_workspace_path(fullpath: FilePath) -> str:
    """Generate a relative path starting from the base path of the workspace.

    Note that the given path must be a descendent of the workspace path.

    Args:
        fullpath (str): Path to convert to a relative path

    Returns:
        str: Relative to the Workspace path
    """

    base_path = Workspace.instance().settings.BASE_PATH
    return os.path.relpath(fullpath, start=base_path)


def get_relative_to_intermediate_path(fullpath: str) -> str:
    """
    Generate a relative path starting from the intermediate directory of the workspace. The given path must be a descendent of the intermediate directory.

    Args:
        fullpath (str): Path to convert to a relative path

    Returns:
        str: Relative to the intermediate path path
    """
    intermediate_path = Workspace.instance().settings.INTERMEDIATE_FOLDER_LOCATION
    return os.path.relpath(fullpath, start=intermediate_path)


def get_full_path_from_workspace_base(relative_path: str) -> FilePath:
    """
    Given a relative path from the workspace path, create the full absolute path on the current filesystem.

    Args:
        relative_path (str): Relative Path from the workspace path.

    Returns:
        FilePath: Absolute path the file.
    """
    base_path = Workspace.instance().settings.BASE_PATH
    return os.path.join(
        base_path,
        relative_path,
    )


def get_full_path_from_intermediate_base(relative_path: str) -> FilePath:
    """
    Given a relative path from the intermediate folder, create the full absolute path on the current filesystem.

    Args:
        relative_path (str): Relative Path from the workspace path.

    Returns:
        FilePath: Absolute path the file.
    """
    intermediate_path = Workspace.instance().settings.INTERMEDIATE_FOLDER_LOCATION
    return os.path.join(intermediate_path, relative_path)


def is_in_workspace(full_path: str) -> bool:
    """Check if a given file is within the workspace file space

    Args:
        full_path (str): The full path to check

    Returns:
        bool
    """
    base_path = Workspace.instance().settings.BASE_PATH
    return os.path.commonprefix([full_path, base_path]) == base_path


def is_in_intermediate(full_path: str) -> bool:
    """Check if a given file is within the intermediate file space

    Args:
        full_path (str): The full path to check

    Returns:
        bool
    """
    intermediate_path = Workspace.instance().settings.INTERMEDIATE_FOLDER_LOCATION
    t = os.path.commonprefix([full_path, intermediate_path])
    return t == str(intermediate_path)


def get_workspace_path() -> DirectoryPath:
    """Get the Workspace path

    Returns:
        DirectoryPath: Path to workspace directory
    """
    base_path = Workspace.instance().settings.BASE_PATH
    return base_path


def get_intermediate_path() -> DirectoryPath:
    """Get the Intermediate path

    Returns:
        DirectoryPath: Path to intermediate directory
    """
    intermediate_path = Workspace.instance().settings.INTERMEDIATE_FOLDER_LOCATION
    return intermediate_path


def create_path_from_workspace(desired_path: DirectoryPath) -> None:

    if not is_in_workspace(desired_path):
        raise Exception

    if os.path.isdir(desired_path):
        return

    relative_parts = get_relative_to_workspace_path(desired_path).split("/")

    create_path(get_workspace_path(), relative_parts)


def create_path(startingpath, fullpath) -> DirectoryPath:
    """This functions takes a starting path and list of child dir and makes them all

    Example

        create_path("./basedir", ["sub1", "sub2"])

        creates:\n
            ./basedir/sub1/\n
            ./basedir/sub1/sub2\n

    Returns:
        DirectoryPath: the final path

    """

    if not os.path.isdir(startingpath):
        raise Exception

    intermediate_path = startingpath

    for p in fullpath:
        if not os.path.isdir(os.path.join(intermediate_path, p)):
            os.mkdir(os.path.join(intermediate_path, p))

        intermediate_path = os.path.join(intermediate_path, p)

    return intermediate_path


def touch_file(file_path: Union[FilePath, str]) -> None:
    """Helper function to touch a file

    Args:
        file_path (FilePath)

    Raises:
        Exception: Directory does not exist
    """
    if not os.path.isdir(os.path.dirname(file_path)):
        raise Exception(
            f"Can not create {file_path} because {os.path.dirname(file_path)} is not a directory."
        )

    with open(file_path, "a"):
        pass


def mkdir(directory_path: Union[DirectoryPath, str]) -> None:
    """Create a directory if it does not already exist.

    Args:
        directory_path (DirectoryPath)
    """
    if not os.path.isdir(directory_path):
        os.mkdir(directory_path)

Functions

def create_path(startingpath, fullpath) ‑> pydantic.types.DirectoryPath

This functions takes a starting path and list of child dir and makes them all

Example

create_path("./basedir", ["sub1", "sub2"])

creates:

    ./basedir/sub1/

    ./basedir/sub1/sub2

Returns

DirectoryPath
the final path
Expand source code
def create_path(startingpath, fullpath) -> DirectoryPath:
    """This functions takes a starting path and list of child dir and makes them all

    Example

        create_path("./basedir", ["sub1", "sub2"])

        creates:\n
            ./basedir/sub1/\n
            ./basedir/sub1/sub2\n

    Returns:
        DirectoryPath: the final path

    """

    if not os.path.isdir(startingpath):
        raise Exception

    intermediate_path = startingpath

    for p in fullpath:
        if not os.path.isdir(os.path.join(intermediate_path, p)):
            os.mkdir(os.path.join(intermediate_path, p))

        intermediate_path = os.path.join(intermediate_path, p)

    return intermediate_path
def create_path_from_workspace(desired_path: pydantic.types.DirectoryPath) ‑> None
Expand source code
def create_path_from_workspace(desired_path: DirectoryPath) -> None:

    if not is_in_workspace(desired_path):
        raise Exception

    if os.path.isdir(desired_path):
        return

    relative_parts = get_relative_to_workspace_path(desired_path).split("/")

    create_path(get_workspace_path(), relative_parts)
def get_full_path_from_intermediate_base(relative_path: str) ‑> pydantic.types.FilePath

Given a relative path from the intermediate folder, create the full absolute path on the current filesystem.

Args

relative_path : str
Relative Path from the workspace path.

Returns

FilePath
Absolute path the file.
Expand source code
def get_full_path_from_intermediate_base(relative_path: str) -> FilePath:
    """
    Given a relative path from the intermediate folder, create the full absolute path on the current filesystem.

    Args:
        relative_path (str): Relative Path from the workspace path.

    Returns:
        FilePath: Absolute path the file.
    """
    intermediate_path = Workspace.instance().settings.INTERMEDIATE_FOLDER_LOCATION
    return os.path.join(intermediate_path, relative_path)
def get_full_path_from_workspace_base(relative_path: str) ‑> pydantic.types.FilePath

Given a relative path from the workspace path, create the full absolute path on the current filesystem.

Args

relative_path : str
Relative Path from the workspace path.

Returns

FilePath
Absolute path the file.
Expand source code
def get_full_path_from_workspace_base(relative_path: str) -> FilePath:
    """
    Given a relative path from the workspace path, create the full absolute path on the current filesystem.

    Args:
        relative_path (str): Relative Path from the workspace path.

    Returns:
        FilePath: Absolute path the file.
    """
    base_path = Workspace.instance().settings.BASE_PATH
    return os.path.join(
        base_path,
        relative_path,
    )
def get_intermediate_path() ‑> pydantic.types.DirectoryPath

Get the Intermediate path

Returns

DirectoryPath
Path to intermediate directory
Expand source code
def get_intermediate_path() -> DirectoryPath:
    """Get the Intermediate path

    Returns:
        DirectoryPath: Path to intermediate directory
    """
    intermediate_path = Workspace.instance().settings.INTERMEDIATE_FOLDER_LOCATION
    return intermediate_path
def get_relative_to_intermediate_path(fullpath: str) ‑> str

Generate a relative path starting from the intermediate directory of the workspace. The given path must be a descendent of the intermediate directory.

Args

fullpath : str
Path to convert to a relative path

Returns

str
Relative to the intermediate path path
Expand source code
def get_relative_to_intermediate_path(fullpath: str) -> str:
    """
    Generate a relative path starting from the intermediate directory of the workspace. The given path must be a descendent of the intermediate directory.

    Args:
        fullpath (str): Path to convert to a relative path

    Returns:
        str: Relative to the intermediate path path
    """
    intermediate_path = Workspace.instance().settings.INTERMEDIATE_FOLDER_LOCATION
    return os.path.relpath(fullpath, start=intermediate_path)
def get_relative_to_workspace_path(fullpath: pydantic.types.FilePath) ‑> str

Generate a relative path starting from the base path of the workspace.

Note that the given path must be a descendent of the workspace path.

Args

fullpath : str
Path to convert to a relative path

Returns

str
Relative to the Workspace path
Expand source code
def get_relative_to_workspace_path(fullpath: FilePath) -> str:
    """Generate a relative path starting from the base path of the workspace.

    Note that the given path must be a descendent of the workspace path.

    Args:
        fullpath (str): Path to convert to a relative path

    Returns:
        str: Relative to the Workspace path
    """

    base_path = Workspace.instance().settings.BASE_PATH
    return os.path.relpath(fullpath, start=base_path)
def get_workspace_path() ‑> pydantic.types.DirectoryPath

Get the Workspace path

Returns

DirectoryPath
Path to workspace directory
Expand source code
def get_workspace_path() -> DirectoryPath:
    """Get the Workspace path

    Returns:
        DirectoryPath: Path to workspace directory
    """
    base_path = Workspace.instance().settings.BASE_PATH
    return base_path
def is_in_intermediate(full_path: str) ‑> bool

Check if a given file is within the intermediate file space

Args

full_path : str
The full path to check

Returns

bool

Expand source code
def is_in_intermediate(full_path: str) -> bool:
    """Check if a given file is within the intermediate file space

    Args:
        full_path (str): The full path to check

    Returns:
        bool
    """
    intermediate_path = Workspace.instance().settings.INTERMEDIATE_FOLDER_LOCATION
    t = os.path.commonprefix([full_path, intermediate_path])
    return t == str(intermediate_path)
def is_in_workspace(full_path: str) ‑> bool

Check if a given file is within the workspace file space

Args

full_path : str
The full path to check

Returns

bool

Expand source code
def is_in_workspace(full_path: str) -> bool:
    """Check if a given file is within the workspace file space

    Args:
        full_path (str): The full path to check

    Returns:
        bool
    """
    base_path = Workspace.instance().settings.BASE_PATH
    return os.path.commonprefix([full_path, base_path]) == base_path
def mkdir(directory_path: Union[pydantic.types.DirectoryPath, str]) ‑> None

Create a directory if it does not already exist.

Args

directory_path (DirectoryPath)

Expand source code
def mkdir(directory_path: Union[DirectoryPath, str]) -> None:
    """Create a directory if it does not already exist.

    Args:
        directory_path (DirectoryPath)
    """
    if not os.path.isdir(directory_path):
        os.mkdir(directory_path)
def touch_file(file_path: Union[pydantic.types.FilePath, str]) ‑> None

Helper function to touch a file

Args

file_path (FilePath)

Raises

Exception
Directory does not exist
Expand source code
def touch_file(file_path: Union[FilePath, str]) -> None:
    """Helper function to touch a file

    Args:
        file_path (FilePath)

    Raises:
        Exception: Directory does not exist
    """
    if not os.path.isdir(os.path.dirname(file_path)):
        raise Exception(
            f"Can not create {file_path} because {os.path.dirname(file_path)} is not a directory."
        )

    with open(file_path, "a"):
        pass