@@ -531,6 +531,113 @@ def idfu() -> int:
531
531
self .key = key
532
532
self .id = idfu
533
533
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
+
534
641
def getvar (var : str ):
535
642
"""
536
643
Get a system user variable without mem leaks
0 commit comments