@@ -531,6 +531,113 @@ def idfu() -> int:
531531 self .key = key
532532 self .id = idfu
533533
534+ class VObject :
535+ """
536+ A virtual object, proxying another object with limited permissions, set at creation.
537+
538+ This way of doing things is private variables for CircuitPython.
539+ However note that CPython's `inspect` module can still find it.
540+ """
541+
542+ def __init__ (
543+ self ,
544+ target , # The real object
545+ attr_whitelist , # If None, the other filters apply
546+ attr_blacklist , # Same
547+ writes : bool , # Permit calling functions and setting attributes
548+ ):
549+ _target = target # Private variables (not accessible via self)
550+ _whitelist = set (attr_whitelist ) if attr_whitelist else None
551+ _blacklist = set (attr_blacklist ) if attr_blacklist else None
552+ _writes = writes
553+
554+ class _Proxy :
555+ def __getattr__ (_ , name ):
556+ if (_whitelist and name not in _whitelist ) or (
557+ _blacklist and name in _blacklist
558+ ):
559+ raise AttributeError (
560+ f'Access to "{ name } " is restricted.'
561+ )
562+
563+ attr = getattr (_target , name )
564+ if callable (attr ): # Proxy method calls
565+ if not _writes :
566+ raise AttributeError (
567+ f'Calls to "{ name } " are restricted.'
568+ )
569+ return lambda * args , ** kwargs : attr (* args , ** kwargs )
570+ return attr
571+
572+ def __setattr__ (_ , name : str , value ) -> None :
573+ if (
574+ (_whitelist and name not in _whitelist )
575+ or (_blacklist and name in _blacklist )
576+ or not _writes
577+ ):
578+ raise AttributeError (
579+ f"Access to '{ name } ' is restricted."
580+ )
581+ setattr (_target , name , value )
582+
583+ def __dir__ (_ ) -> list :
584+ return dir (_target )
585+
586+ self ._proxy = _Proxy ()
587+
588+ def __getattr__ (self , name : str ):
589+ return getattr (
590+ self ._proxy , name
591+ ) # Delegate attributes to the internal proxy.
592+
593+ def __setattr__ (self , name : str , value ) -> None :
594+ return setattr (self ._proxy , name , value ) # Same
595+
596+ def __dir__ (self ):
597+ return dir (self ._proxy ) # Delegate `dir()` to the internal proxy.
598+
599+ class SafeExec :
600+ def __init__ (self ):
601+ self ._globals = {}
602+
603+ def add_object (
604+ self ,
605+ obj ,
606+ name = None , # Variable name in scope override
607+ whitelist = None , # Reference VObject comments
608+ blacklist = None ,
609+ writes : bool = False ,
610+ ):
611+ """Adds a restricted object inside exec()."""
612+ if name is None :
613+ try :
614+ name = obj .__name__
615+ except :
616+ raise RuntimeError ("No name on nameless object" )
617+ self ._globals [name ] = VObject (obj , whitelist , blacklist , mode )
618+
619+ def add_variable (self , ** kwargs ):
620+ """Adds variables that can be modified inside exec but don't affect outside."""
621+ for key , value in kwargs .items ():
622+ if isinstance (value , (int , float , str , bool , type (None ))):
623+ self ._globals [key ] = value # Immutable types are fine
624+ elif isinstance (value , list ):
625+ self ._globals [key ] = list (value )
626+ elif isinstance (value , tuple ):
627+ self ._globals [key ] = tuple (value )
628+ elif isinstance (value , set ):
629+ self ._globals [key ] = set (value )
630+ elif isinstance (value , dict ):
631+ self ._globals [key ] = dict (value )
632+ else :
633+ raise TypeError (
634+ f"Type: { type (value ).__name__ } , cannot be copied"
635+ )
636+
637+ def run (self , code ):
638+ """Executes the given code in the constructed isolated environment."""
639+ exec (code , self ._globals , {})
640+
534641 def getvar (var : str ):
535642 """
536643 Get a system user variable without mem leaks
0 commit comments