#!/usr/bin/env python
# -*- coding: utf-8 -*-
# License: BSD-3 (https://tldrlegal.com/license/bsd-3-clause-license-(revised))
# Copyright (c) 2016-2021, Cabral, Juan; Luczywo, Nadia
# Copyright (c) 2022, 2023, 2024 QuatroPe
# All rights reserved.
# =============================================================================
# DOCS
# =============================================================================
"""Multiple context managers to use inside scikit-criteria."""
# =============================================================================
# IMPORTS
# =============================================================================
import contextlib
import inspect
# =============================================================================
# TEMPORAL HEADER
# =============================================================================
# =============================================================================
# EHIDDEN GLOBAL ATTRIBUTES
# =============================================================================
[docs]
class HiddenAlreadyUsedInThisContext(RuntimeError):
"""Raised when a context attempts to use the 'hidden' context manager \
more than once within the same scope."""
[docs]
class NonGlobalHidden(RuntimeError):
"""Exception raised when the 'hidden' decorator is used in a context that \
is not the global scope of a module.
This exception indicates that the 'hidden' decorator should only be
applied globally, outside of any functions or methods, and an attempt to
use it within a local context (e.g., inside a function or method) has
been detected.
"""
class _DirWithHidden:
"""Custom directory function with hidden objects filtering.
Parameters
----------
frame : frame
The frame whose global variables will be considered.
hidden_objects : dict
A dictionary containing names of objects to be hidden and their
corresponding objects.
Examples
--------
>>> frame = inspect.currentframe()
>>> hidden = {'obj_to_hide': object()}
>>> dir_with_hidden = _DirWithHidden(frame, hidden)
>>> visible_attrs = dir_with_hidden()
>>> print(visible_attrs)
['visible_obj1', 'visible_obj2']
"""
def __init__(self, frame, hidden_objects):
self.frame = frame
self.hidden_objects = hidden_objects
def __call__(self):
"""Call method to retrieve visible attributes in the frame.
Returns
-------
list
A list of visible attribute names in the frame, considering
the hidden_objects.
"""
attrs = []
for obj_name, obj in self.frame.f_globals.items():
if obj_name not in self.hidden_objects:
attrs.append(obj_name)
elif self.hidden_objects[obj_name] is not obj:
attrs.append(obj_name)
return attrs
[docs]
@contextlib.contextmanager
def hidden(*, hide_this=True, dry=False):
"""A context manager for hiding objects in the global scope.
Parameters
----------
hide_this : bool, optional
Whether to hide the 'hidden' context manager itself and/or the hidden
module. Defaults to True.
dry : bool, optional, default False
If is True, the objects are not hide. Useful for testing.
Raises
------
NonGlobalHidden
If 'hidden' is declared inside a function, class or method.
HiddenAlreadyUsedInThisContext
If the 'hidden' context manager is used more than once in the same
context.
Yields
------
None
Notes
-----
- This context manager is intended to be used globally (outside any
functions or methods).
- It hides objects within the global scope for the duration of the context.
Implementation Details
----------------------
- The context manager retrieves the current frame and ensures it is used
globally.
- It captures the state of the global scope before entering the context.
- Objects introduced within the context are hidden in the global scope.
- The '__dir__' attribute of the global scope is customized to include
logic to hide the objects introduced within the context.
"""
self = hidden # this function
# two levels because of the decorator @contextmanager
frame = inspect.currentframe().f_back.f_back
# if co_name != <module> we are inside a function or class
if frame.f_code.co_name != "<module>":
check_here = (
f"{frame.f_code.co_filename}:"
f"{frame.f_lineno}::"
f"{frame.f_code.co_name}"
)
raise NonGlobalHidden(
f"hidden() can only be used globally. Check {check_here!r}"
)
# get the current state
pre_f_globals = dict(frame.f_globals)
# if he current state alreade replace the __dir__ hidden is used two times
if isinstance(pre_f_globals.get("__dir__"), _DirWithHidden):
raise HiddenAlreadyUsedInThisContext(frame.f_code.co_filename)
yield # execute the decorator
# check whatever the code declared inside the context
hidden_objects = {}
for obj_name, obj in frame.f_globals.items():
if obj_name not in pre_f_globals:
hidden_objects[obj_name] = obj
elif obj is self and hide_this:
hidden_objects[obj_name] = obj
# create the new dir object
custom_dir = _DirWithHidden(frame=frame, hidden_objects=hidden_objects)
# if dry, don't do anything simply stop now
# otherwise replace the global __dir__
if not dry:
frame.f_globals["__dir__"] = custom_dir