Skip to content

pyhacks/access-specifiers

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 

Repository files navigation

access-specifiers

This library provides runtime access modifiers with very high security and it is fully featured (e.g, supports private inheritance).

Installation

pip install access-specifiers

The recommended way to import the library is like below:

from access_specifiers import api as access_specifiers

This convoluted import statement is required in order to have a strong access security. Shortly, api protects the library from monkeypatching. Rest of the documentation assumes the library is imported as shown above.

Inheritance

In order to make access modifiers available to your simple class, you need to inherit from Restricted class:

class MyClass(access_specifiers.Restricted):
    pass

Metaclass of Restricted is Restrictor. If you need to inherit from classes which inherit from Restricted, you first need to create a new metaclass.

access_specifiers.create_restrictor(*bases)

Create a metaclass given the required bases.

class MyClass4(metaclass = access_specifiers.create_restrictor(MyClass1, MyClass2, MyClass3)):
    pass

Using The Modifiers

function access_specifiers.private(value)

decorator @access_specifiers.private(value)

function access_specifiers.protected(value)

decorator @access_specifiers.protected(value)

function access_specifiers.public(value)

decorator @access_specifiers.public(value)

Modifiers can be used both as a function and a decorator. Just call them with the value you need to set its modifier:

class MyClass(access_specifiers.Restricted):
    a = access_specifiers.private(10) 

    @access_specifiers.private
    def func(self):
        pass

Alternatively, you can also use a fancier syntax:

class access_specifiers.PrivateModifier

class access_specifiers.ProtectedModifier

class access_specifiers.PublicModifier

private = access_specifiers.PrivateModifier
protected = access_specifiers.ProtectedModifier
public = access_specifiers.PublicModifier

class MyClass(access_specifiers.Restricted):
    private .a = 10
    protected .b = 20
    public .c = 30

The dot (.) in between the modifier and the name is required.

You can also set new members with access modifiers after creating the class. Classes inheriting Restricted store ClassModifier objects. You can use them as modifiers:

class MyClass(access_specifiers.Restricted):
    @classmethod
    def func(cls):
        private = cls.private
        protected = cls.protected
        public = cls.public
        private .d = 10
        protected .e = 20
        public .f = 30

The dot (.) in between the modifier and the name is required.

You can also specify access modifiers for object attributes. Restricted objects store Modifier objects. You can use them as modifiers:

class MyClass(access_specifiers.Restricted):
    def func(self):
        private = self.private 
        protected = self.protected
        public = self.public 
        private .a = 10

Again, the dot in between is required.

function access_specifiers.set_default(modifier)

Set the default modifier when a member is defined with no explicit modifier. By default, default modifier is public. modifier parameter can be either access_specifiers.private, access_specifiers.protected or access_specifiers.public.

There is one more feature of access_specifiers.create_restrictor: private and protected inheritance. Replace your base classes with calls to modifiers:

class MyClass2(metaclass = access_specifiers.create_restrictor(access_specifiers.private(MyClass))):
    pass

 class MyClass3(metaclass = access_specifiers.create_restrictor(access_specifiers.protected(MyClass))):
    pass

Utils

function access_specifiers.super(obj_or_cls = None)

This function is equivalent to the built in super function and it should be used when the built in function doesn't work. Returns a proxy object to the superclass of obj_or_cls.

decorator @access_specifiers.Decorator(decorator)

Normally, if you decorate a method, the function returned by the decorator becomes the member and the original function won't be a member. This causes the original function to be unable to access private/protected members. Instead of decorating directly, pass your decorator to access_specifiers.Decorator() and this problem will be solved. Along with the original function, all the wrapper functions returned by each of the decorators will also be authorized. Lastly, access decorators must be topmost and you shouldn't pass them in access_specifiers.Decorator(). Example usage:

def factory1(func):
    def wrapper(*args, **kwargs):
        print("by factory1")
        func(*args, **kwargs)
    return wrapper

def factory2(func):
    def wrapper(*args, **kwargs):
        print("by factory2")
        func(*args, **kwargs)
    return wrapper

class MyClass(access_specifiers.Restricted):
    @access_specifiers.private
    @access_specifiers.Decorator(factory1)
    @access_specifiers.Decorator(factory2)
    def func(self):
        pass

Restricted class provides a few more useful things:

method Restricted.set_private(name, value, cls = None)

method Restricted.set_protected(name, value, cls = None)

method Restricted.set_public(name, value)

You can specify modifiers for dynamically generated variable names. If cls is specified it must be a class and the call to these functions will be treated as if it is done from one of the methods inside the cls. cls can either be the same as of the caller or it must be more derived than of the caller. If it is a parent of the caller's class, an access_specifiers.PrivateError will be raised.

method Restricted.authorize(func_or_cls, for_all = True)

This function acts like the "friend" keyword of c++. Allows func_or_cls to have as much member access right as any other method of this class. func_or_cls can either be a function or a class. If for_all is set, allows func_or_cls to access private/protected members of not only this object, but also every other instantiated object of this class and also all future objects of this class which will be instantiated later and even the class itself.

method Restricted.get_hidden_value(value, name = None)

Return a protected object whose only attribute "value" stores the given value. Access to the value attribute is only granted if the accessing function has the rights to access given name. If name is None, any class in the class hierarchy of this object can access the value but external access is rejected. If you are calling this method from a base class called MyBase and don't want any derived class to access value, name must be one of the private members of MyBase. Each object whose class derives from Restricted has private members whose names has the following structure: class_name + "_" + "private". For the case of MyBase, you can set name to "MyBase_private". This function is useful in case you wanna call an external function and want to prevent that function from obtaining (possibly private) local variables of the calling method. Example usage:

import sys

def external_func():
   print(sys._getframe(1).f_locals)

class Base(access_specifiers.Restricted):
   pass

class Derived(metaclass = access_specifiers.create_restrictor(Base)):
   def __init__(self):
       private = self.private
       private .a = 10

   def func(self):
       a = self.a
       hidden_value = self.get_hidden_value(a, name = "Derived_private")
       del a
       external_func()
       a = hidden_value.value

obj = Derived()
obj.func()

method Restricted.create_getattribute()

Return a __getattribute__ function which checks the access rights of the caller. Useful when you write a custom __getattribute__ and don't wanna manually check the caller:

    def __getattribute__(self, name):
        getter = self.create_getattribute()                
        value = getter(name)
        return value

method Restricted.create_setattr()

Return a __setattr__ function which checks the access rights of the caller. Useful when you write a custom __setattr__ and don't wanna manually check the caller:

    def __setattr__(self, name, value):
        setter = self.create_setattr()
        setter(name, value)

method Restricted.create_delattr()

Return a __delattr__ function which checks the access rights of the caller. Useful when you write a custom __delattr__ and don't wanna manually check the caller:

    def __delattr__(self, name):
        deleter = self.create_delattr()
        deleter(name)

Restricted._subclasses_

This is a class variable holding a list of subclasses. Elements of this list doesn't check access to their private and protected members but do check to private members coming from their bases.

Functions below are provided by Restrictor, which means they are only available to classes, not objects:

method Restrictor.set_class_private(name, value)

method Restrictor.set_class_protected(name, value)

method Restrictor.set_class_public(name, value)

After the class has been created, these methods can be used to set private, protected and public class members which have dynamically generated names.

method Restrictor.authorize_for_class(func_or_cls)

Authorize func_or_cls so it can access private and protected class members. func_or_cls can either be a function or a class. func_or_cls will also be authorized to access private and protected members of future objects, but not current ones.

Limitations

  • gc.get_objects() can leak private/protected members. In order to prevent this, you may consider adding this to the top of your code:
import gc
del gc.get_objects

This isn't done by this library in case the user actually requires this function.

  • @staticmethod is not supported

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages