Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
main.py481 linesDownload Raw Back to dotenv
1import io2import logging3import os4import pathlib5import stat6import sys7import tempfile8from collections import OrderedDict9from contextlib import contextmanager10from typing import IO, Dict, Iterable, Iterator, Mapping, Optional, Tuple, Union11 12from .parser import Binding, parse_stream13from .variables import parse_variables14 15# A type alias for a string path to be used for the paths in this file.16# These paths may flow to `open()` and `os.replace()`.17StrPath = Union[str, "os.PathLike[str]"]18 19logger = logging.getLogger(__name__)20 21 22def _load_dotenv_disabled() -> bool:23    """24    Determine if dotenv loading has been disabled.25    """26    if "PYTHON_DOTENV_DISABLED" not in os.environ:27        return False28    value = os.environ["PYTHON_DOTENV_DISABLED"].casefold()29    return value in {"1", "true", "t", "yes", "y"}30 31 32def with_warn_for_invalid_lines(mappings: Iterator[Binding]) -> Iterator[Binding]:33    for mapping in mappings:34        if mapping.error:35            logger.warning(36                "python-dotenv could not parse statement starting at line %s",37                mapping.original.line,38            )39        yield mapping40 41 42class DotEnv:43    def __init__(44        self,45        dotenv_path: Optional[StrPath],46        stream: Optional[IO[str]] = None,47        verbose: bool = False,48        encoding: Optional[str] = None,49        interpolate: bool = True,50        override: bool = True,51    ) -> None:52        self.dotenv_path: Optional[StrPath] = dotenv_path53        self.stream: Optional[IO[str]] = stream54        self._dict: Optional[Dict[str, Optional[str]]] = None55        self.verbose: bool = verbose56        self.encoding: Optional[str] = encoding57        self.interpolate: bool = interpolate58        self.override: bool = override59 60    @contextmanager61    def _get_stream(self) -> Iterator[IO[str]]:62        if self.dotenv_path and _is_file_or_fifo(self.dotenv_path):63            with open(self.dotenv_path, encoding=self.encoding) as stream:64                yield stream65        elif self.stream is not None:66            yield self.stream67        else:68            if self.verbose:69                logger.info(70                    "python-dotenv could not find configuration file %s.",71                    self.dotenv_path or ".env",72                )73            yield io.StringIO("")74 75    def dict(self) -> Dict[str, Optional[str]]:76        """Return dotenv as dict"""77        if self._dict:78            return self._dict79 80        raw_values = self.parse()81 82        if self.interpolate:83            self._dict = OrderedDict(84                resolve_variables(raw_values, override=self.override)85            )86        else:87            self._dict = OrderedDict(raw_values)88 89        return self._dict90 91    def parse(self) -> Iterator[Tuple[str, Optional[str]]]:92        with self._get_stream() as stream:93            for mapping in with_warn_for_invalid_lines(parse_stream(stream)):94                if mapping.key is not None:95                    yield mapping.key, mapping.value96 97    def set_as_environment_variables(self) -> bool:98        """99        Load the current dotenv as system environment variable.100        """101        if not self.dict():102            return False103 104        for k, v in self.dict().items():105            if k in os.environ and not self.override:106                continue107            if v is not None:108                os.environ[k] = v109 110        return True111 112    def get(self, key: str) -> Optional[str]:113        """ """114        data = self.dict()115 116        if key in data:117            return data[key]118 119        if self.verbose:120            logger.warning("Key %s not found in %s.", key, self.dotenv_path)121 122        return None123 124 125def get_key(126    dotenv_path: StrPath,127    key_to_get: str,128    encoding: Optional[str] = "utf-8",129) -> Optional[str]:130    """131    Get the value of a given key from the given .env.132 133    Returns `None` if the key isn't found or doesn't have a value.134    """135    return DotEnv(dotenv_path, verbose=True, encoding=encoding).get(key_to_get)136 137 138@contextmanager139def rewrite(140    path: StrPath,141    encoding: Optional[str],142    follow_symlinks: bool = False,143) -> Iterator[Tuple[IO[str], IO[str]]]:144    if follow_symlinks:145        path = os.path.realpath(path)146 147    try:148        source: IO[str] = open(path, encoding=encoding)149        try:150            path_stat = os.lstat(path)151            original_mode: Optional[int] = (152                stat.S_IMODE(path_stat.st_mode)153                if stat.S_ISREG(path_stat.st_mode)154                else None155            )156        except BaseException:157            source.close()158            raise159    except FileNotFoundError:160        source = io.StringIO("")161        original_mode = None162 163    with tempfile.NamedTemporaryFile(164        mode="w",165        encoding=encoding,166        delete=False,167        prefix=".tmp_",168        dir=os.path.dirname(os.path.abspath(path)),169    ) as dest:170        dest_path = pathlib.Path(dest.name)171        error = None172 173        try:174            with source:175                yield (source, dest)176        except BaseException as err:177            error = err178 179    if error is None:180        try:181            if original_mode is not None:182                os.chmod(dest_path, original_mode)183 184            os.replace(dest_path, path)185        except BaseException:186            dest_path.unlink(missing_ok=True)187            raise188    else:189        dest_path.unlink(missing_ok=True)190        raise error from None191 192 193def set_key(194    dotenv_path: StrPath,195    key_to_set: str,196    value_to_set: str,197    quote_mode: str = "always",198    export: bool = False,199    encoding: Optional[str] = "utf-8",200    follow_symlinks: bool = False,201) -> Tuple[Optional[bool], str, str]:202    """203    Adds or Updates a key/value to the given .env204 205    The target .env file is created if it doesn't exist.206 207    This function doesn't follow symlinks by default, to avoid accidentally208    modifying a file at a potentially untrusted path. If you don't need this209    protection and need symlinks to be followed, use `follow_symlinks`.210    """211    if quote_mode not in ("always", "auto", "never"):212        raise ValueError(f"Unknown quote_mode: {quote_mode}")213 214    quote = quote_mode == "always" or (215        quote_mode == "auto" and not value_to_set.isalnum()216    )217 218    if quote:219        value_out = "'{}'".format(value_to_set.replace("'", "\\'"))220    else:221        value_out = value_to_set222    if export:223        line_out = f"export {key_to_set}={value_out}\n"224    else:225        line_out = f"{key_to_set}={value_out}\n"226 227    with rewrite(dotenv_path, encoding=encoding, follow_symlinks=follow_symlinks) as (228        source,229        dest,230    ):231        replaced = False232        missing_newline = False233        for mapping in with_warn_for_invalid_lines(parse_stream(source)):234            if mapping.key == key_to_set:235                dest.write(line_out)236                replaced = True237            else:238                dest.write(mapping.original.string)239                missing_newline = not mapping.original.string.endswith("\n")240        if not replaced:241            if missing_newline:242                dest.write("\n")243            dest.write(line_out)244 245    return True, key_to_set, value_to_set246 247 248def unset_key(249    dotenv_path: StrPath,250    key_to_unset: str,251    quote_mode: str = "always",252    encoding: Optional[str] = "utf-8",253    follow_symlinks: bool = False,254) -> Tuple[Optional[bool], str]:255    """256    Removes a given key from the given `.env` file.257 258    If the .env path given doesn't exist, fails.259    If the given key doesn't exist in the .env, fails.260 261    This function doesn't follow symlinks by default, to avoid accidentally262    modifying a file at a potentially untrusted path. If you don't need this263    protection and need symlinks to be followed, use `follow_symlinks`.264    """265    if not os.path.exists(dotenv_path):266        logger.warning("Can't delete from %s - it doesn't exist.", dotenv_path)267        return None, key_to_unset268 269    removed = False270    with rewrite(dotenv_path, encoding=encoding, follow_symlinks=follow_symlinks) as (271        source,272        dest,273    ):274        for mapping in with_warn_for_invalid_lines(parse_stream(source)):275            if mapping.key == key_to_unset:276                removed = True277            else:278                dest.write(mapping.original.string)279 280    if not removed:281        logger.warning(282            "Key %s not removed from %s - key doesn't exist.", key_to_unset, dotenv_path283        )284        return None, key_to_unset285 286    return removed, key_to_unset287 288 289def resolve_variables(290    values: Iterable[Tuple[str, Optional[str]]],291    override: bool,292) -> Mapping[str, Optional[str]]:293    new_values: Dict[str, Optional[str]] = {}294 295    for name, value in values:296        if value is None:297            result = None298        else:299            atoms = parse_variables(value)300            env: Dict[str, Optional[str]] = {}301            if override:302                env.update(os.environ)  # type: ignore303                env.update(new_values)304            else:305                env.update(new_values)306                env.update(os.environ)  # type: ignore307            result = "".join(atom.resolve(env) for atom in atoms)308 309        new_values[name] = result310 311    return new_values312 313 314def _walk_to_root(path: str) -> Iterator[str]:315    """316    Yield directories starting from the given directory up to the root317    """318    if not os.path.exists(path):319        raise IOError("Starting path not found")320 321    if os.path.isfile(path):322        path = os.path.dirname(path)323 324    last_dir = None325    current_dir = os.path.abspath(path)326    while last_dir != current_dir:327        yield current_dir328        parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))329        last_dir, current_dir = current_dir, parent_dir330 331 332def find_dotenv(333    filename: str = ".env",334    raise_error_if_not_found: bool = False,335    usecwd: bool = False,336) -> str:337    """338    Search in increasingly higher folders for the given file339 340    Returns path to the file if found, or an empty string otherwise341    """342 343    def _is_interactive():344        """Decide whether this is running in a REPL or IPython notebook"""345        if hasattr(sys, "ps1") or hasattr(sys, "ps2"):346            return True347        try:348            main = __import__("__main__", None, None, fromlist=["__file__"])349        except ModuleNotFoundError:350            return False351        return not hasattr(main, "__file__")352 353    def _is_debugger():354        return sys.gettrace() is not None355 356    if usecwd or _is_interactive() or _is_debugger() or getattr(sys, "frozen", False):357        # Should work without __file__, e.g. in REPL or IPython notebook.358        path = os.getcwd()359    else:360        # will work for .py files361        frame = sys._getframe()362        current_file = __file__363 364        while frame.f_code.co_filename == current_file or not os.path.exists(365            frame.f_code.co_filename366        ):367            assert frame.f_back is not None368            frame = frame.f_back369        frame_filename = frame.f_code.co_filename370        path = os.path.dirname(os.path.abspath(frame_filename))371 372    for dirname in _walk_to_root(path):373        check_path = os.path.join(dirname, filename)374        if _is_file_or_fifo(check_path):375            return check_path376 377    if raise_error_if_not_found:378        raise IOError("File not found")379 380    return ""381 382 383def load_dotenv(384    dotenv_path: Optional[StrPath] = None,385    stream: Optional[IO[str]] = None,386    verbose: bool = False,387    override: bool = False,388    interpolate: bool = True,389    encoding: Optional[str] = "utf-8",390) -> bool:391    """Parse a .env file and then load all the variables found as environment variables.392 393    Parameters:394        dotenv_path: Absolute or relative path to .env file.395        stream: Text stream (such as `io.StringIO`) with .env content, used if396            `dotenv_path` is `None`.397        verbose: Whether to output a warning the .env file is missing.398        override: Whether to override the system environment variables with the variables399            from the `.env` file.400        encoding: Encoding to be used to read the file.401    Returns:402        Bool: True if at least one environment variable is set else False403 404    If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the405    .env file with it's default parameters. If you need to change the default parameters406    of `find_dotenv()`, you can explicitly call `find_dotenv()` and pass the result407    to this function as `dotenv_path`.408 409    If the environment variable `PYTHON_DOTENV_DISABLED` is set to a truthy value,410    .env loading is disabled.411    """412    if _load_dotenv_disabled():413        logger.debug(414            "python-dotenv: .env loading disabled by PYTHON_DOTENV_DISABLED environment variable"415        )416        return False417 418    if dotenv_path is None and stream is None:419        dotenv_path = find_dotenv()420 421    dotenv = DotEnv(422        dotenv_path=dotenv_path,423        stream=stream,424        verbose=verbose,425        interpolate=interpolate,426        override=override,427        encoding=encoding,428    )429    return dotenv.set_as_environment_variables()430 431 432def dotenv_values(433    dotenv_path: Optional[StrPath] = None,434    stream: Optional[IO[str]] = None,435    verbose: bool = False,436    interpolate: bool = True,437    encoding: Optional[str] = "utf-8",438) -> Dict[str, Optional[str]]:439    """440    Parse a .env file and return its content as a dict.441 442    The returned dict will have `None` values for keys without values in the .env file.443    For example, `foo=bar` results in `{"foo": "bar"}` whereas `foo` alone results in444    `{"foo": None}`445 446    Parameters:447        dotenv_path: Absolute or relative path to the .env file.448        stream: `StringIO` object with .env content, used if `dotenv_path` is `None`.449        verbose: Whether to output a warning if the .env file is missing.450        encoding: Encoding to be used to read the file.451 452    If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the453    .env file.454    """455    if dotenv_path is None and stream is None:456        dotenv_path = find_dotenv()457 458    return DotEnv(459        dotenv_path=dotenv_path,460        stream=stream,461        verbose=verbose,462        interpolate=interpolate,463        override=True,464        encoding=encoding,465    ).dict()466 467 468def _is_file_or_fifo(path: StrPath) -> bool:469    """470    Return True if `path` exists and is either a regular file or a FIFO.471    """472    if os.path.isfile(path):473        return True474 475    try:476        st = os.stat(path)477    except (FileNotFoundError, OSError):478        return False479 480    return stat.S_ISFIFO(st.st_mode)481 
codekingpro/portable-devtools · Team Ai