Skip to content

Instantly share code, notes, and snippets.

@jymchng
Created July 24, 2023 16:17
Show Gist options
  • Save jymchng/cf5eccb783688f383dcbc6fe6ed214c1 to your computer and use it in GitHub Desktop.
Save jymchng/cf5eccb783688f383dcbc6fe6ed214c1 to your computer and use it in GitHub Desktop.
Python consts
import ast
from ast import NodeVisitor
import inspect
import textwrap
from types import MethodType
from typing_extensions import Annotated
from abc import ABCMeta
from pydantic.main import ModelMetaclass
class AssignVisitor(NodeVisitor):
class NameVisitorToFindConst(NodeVisitor):
def __init__(self):
self.found = False
def visit_Name(self, node):
if node.id == 'const':
self.found = True
def __init__(self, consts_dict: dict, strict=False):
self.consts_dict = consts_dict
self.strict = strict
self.exceptions_strs = []
def visit_Assign(self, node):
targets = node.targets
for target in targets:
if not isinstance(target, ast.Attribute):
return
var_name = target.attr
if self.strict:
self.exceptions_strs.append(
f'> Cannot re-assign any instance or class variable `{var_name}` in a method decorated with `const`')
return
if var_name in self.consts_dict:
self.exceptions_strs.append(
f'> Cannot re-assign `{var_name}` as it is a `const` variable')
def visit_AnnAssign(self, node):
if not isinstance(node.target, ast.Attribute):
return
var_name = node.target.attr
if self.strict:
self.exceptions_strs.append(
f'> Cannot re-assign any instance or class variable `{var_name}` in a method decorated with `const`')
return
if var_name in self.consts_dict:
if 'const' not in node.annotation.id:
self.exceptions_strs.append(
f'> Cannot change the annotation of a `const` variable')
return
self.exceptions_strs.append(
f'> Cannot re-assign `{var_name}` as it is a `const` variable')
return
if node.value is None:
self.exceptions_strs.append(
f'> `const` variable `{var_name}` must be declared with a value')
return
# if not isinstance(node.value, ast.Constant):
# self.exceptions_strs.append(
# f'> `const` variable `{var_name}` when declared, must be declared with a `Constant` value, the value declared is dynamic variable `{node.value.id}`')
# return
# else:
# if not hasattr(node.value, 'value'):
# self.exceptions_strs.append(
# f'> `const` variable `{var_name}` when declared, it must be declared with a `Constant` value, the value declared is dynamic variable `{node.value.id}`')
# return
name_node_visitor = self.NameVisitorToFindConst()
name_node_visitor.visit(node.annotation)
if name_node_visitor.found:
self.consts_dict[var_name] = const
def visit_AugAssign(self, node):
var_name = node.target.attr
if not isinstance(node.target, ast.Attribute):
return
if self.strict:
self.exceptions_strs.append(
f'> Cannot re-assign any instance or class variable `{var_name}` in a method decorated with `const`')
return
if var_name in self.consts_dict:
self.exceptions_strs.append(
f'> Cannot re-assign `{var_name}` as it is a `const` variable')
return
class const(str):
def __new__(self, func=None):
if func is not None:
func.__const__ = True
return func
return str.__new__(self, "const")
def __class_getitem__(self, index):
if not isinstance(index, (tuple,)):
index = index,
index = tuple([_ for _ in index])
return Annotated[const, index]
MappingProxy = type(const.__dict__)
def __setattr__(slf, _name, _value):
if _name in slf._consts_ and not slf._init:
raise Exception(
f'Cannot re-assign `{_name}` since it is annotated as `const`')
super(slf.__class__, slf).__setattr__(_name, _value)
def in_pydantic_fields(attrs, k):
return attrs['__fields__'].get(k, False)
def is_pydantic(attrs):
return '__fields__' in attrs
class ConstsMeta(ABCMeta):
def __new__(mcls, cls, bases=(), attrs={}):
bases = cls.__bases__
attrs = dict(cls.__dict__)
_consts_ = {}
exceptions_strs = []
print(mcls, cls, bases, attrs)
if hasattr(cls, '__annotations__'):
annotations = cls.__annotations__
else:
annotations = {}
for k, v in annotations.items():
if (type(v) is const) or ('const' in str(v)) or v is const:
if not hasattr(cls, k):
if is_pydantic(attrs):
if not in_pydantic_fields(attrs, k):
exceptions_strs.append(
f"> Pydantic field {k} is labelled as `const`, but it is not defined with a value")
else:
_consts_[k] = k
else:
exceptions_strs.append(
f">>> Class variable {k} is labelled as `const`, but it is not defined with a value")
continue
else:
_consts_[k] = attrs.pop(k)
elif hasattr(v, '__origin__'):
origin = v.__origin__
if hasattr(v, '__extra__'):
extra = v.__extra__
else:
extra = v.__metadata__
if (origin is const) or (origin=='const') or (const in extra) or ('const' in extra):
if not hasattr(cls, k):
exceptions_strs.append(
f">> Class variable {k} is labelled as `const`, but it is not defined with a value")
elif not in_pydantic_fields(attrs, k):
exceptions_strs.append(
f"> Pydantic field {k} is labelled as `const`, but it is not defined with a value")
else:
_consts_[k] = attrs.pop(k)
else:
pass
for k, v in attrs.items():
if callable(v) or isinstance(v, classmethod):
if isinstance(v, classmethod):
v = v.__func__
if isinstance(v, staticmethod):
continue
if hasattr(v, '__const__'):
assign_visitor_strict = AssignVisitor(_consts_, True)
tree = ast.parse(
textwrap.dedent(
inspect.getsource(
v.__code__)))
assign_visitor_strict.visit(tree)
if assign_visitor_strict.exceptions_strs:
indivi_exceptions = "\n\t".join(
assign_visitor_strict.exceptions_strs)
exceptions_strs.append(
f'> Method of type `{type(v)}` with name `{v.__name__}` raised an (or many) exception(s):\n\t{indivi_exceptions}')
_consts_.update(assign_visitor_strict.consts_dict)
else:
if not isinstance(v, type): # don't check for inner classes
assign_visitor = AssignVisitor(_consts_)
tree = ast.parse(
textwrap.dedent(
inspect.getsource(
v.__code__)))
assign_visitor.visit(tree)
if assign_visitor.exceptions_strs:
indivi_exceptions = "\n\t".join(
assign_visitor.exceptions_strs)
exceptions_strs.append(
f'> Method of type `{type(v)}` with name `{v.__name__}` raised an (or many) exception(s):\n\t{indivi_exceptions}')
_consts_.update(assign_visitor.consts_dict)
if exceptions_strs:
exceptions_strs[0] = f'\nFor class `{cls.__name__}`:\n' + \
exceptions_strs[0]
raise Exception("\n".join(exceptions_strs))
_consts_ = MappingProxy(_consts_)
attrs.update({'_consts_': _consts_})
return type.__new__(mcls, cls.__name__, bases, dict(attrs))
def __call__(cls, *args, **kwargs):
cls._init = True
inst = cls.__new__(cls, *args, **kwargs)
setattr(inst.__class__, '__setattr__', MethodType(__setattr__, inst))
inst.__init__(*args, **kwargs)
cls._init = False
return inst
def __setattr__(cls, __name, __value):
if __name in cls._consts_:
raise Exception(
f'Cannot re-assign `{__name}` since it is annotated as `const`')
super().__setattr__(__name, __value)
class ConstsPydanticMeta(ConstsMeta, ModelMetaclass):
...
consts = ConstsPydanticMeta
try:
@consts
class A:
# `B` declared as `const`, none of class `A`'s method can mutate it
B: const[str] = 'B'
# `K` declared as `const` but no value is assigned to it, raises Exception:
# > Class variable K is labelled as `const`, but it is not defined with a value
K: const[tuple]
# OK, `G` declared as `const`, none of class `A`'s method can mutate it
G: const = 52
# Not OK
# > Class variable KK is labelled as `const`, but it is not defined with a value
KK: const
# `H` and `HH` are declared as `const`, none of class `A`'s method can mutate it
# note the annotation can be very flexible
H: Annotated[int, const] = 22
HH: Annotated[const, int, str] = 55
HHH: Annotated[int, 'const', str] = 22
# annotated with str also works
HHHH: 'const[int]' = 77
D = 'D'
def __init__(self, E=57):
# `E` is an instance variable, assigned with a value dynamically and annotated as `const`
# all subsequent method calls post-init cannot mutate it, detected at reading of class definition
# and enforced subsequently
self.E: const[int] = E
# OK, normal instance variable
self.F: str = E
# declared `GGG` and `GGGG` as `const` during initialization
self.GGG: Annotated[const, str] = E
self.GGGG: Annotated[str, const, int] = E
# error > Cannot re-assign everything below as they are `const` variables
# these exceptions are raised when reading the class definition
self.HH = 33
self.HHH = 23
self.HHHH = 23
self.H = 33
self.G = 50
def hello(self):
self.F = 33
print("Hello")
# all methods are checked for illegal re-assignment of values to `const` variables
def changing_C(self):
self.C = 'D'
self.E = 55
self.GGG = 56
self.GGGG = 56
# method decorated with `const` cannot have any instance attribute being reassigned to a different value
# this is check when reading the class definition, not enforced subsequently because
# exception is raised
@const
def promise_not_to_change(self):
self.D = 'F' # should raise Exception because whole method is `const`
self.E = 'ZS'
@staticmethod
def bye():
print("Bye")
@classmethod
def hey(cls):
cls.B = 'BB'
print("Hey")
except Exception as err:
# these exceptions are raised when reading the class definition
# class `A` has not been instantiated
print(err)
class G:
...
@consts
class B(G, str):
B: const[str] = 'B'
C: const = 'C'
D = 'D'
def __new__(cls, E):
inst = str.__new__(cls, "HELLO!!!")
return inst
def __init__(self, E):
self.E: const = E
self.EE: const[int] = 55
self.EEE: Annotated[const, int] = 66
self.EEEE: Annotated[int, const, str] = 27
self.F = 'F'
@const
def hello(self):
print("Hello")
@staticmethod
def bye():
print("Bye")
@classmethod
def hey(cls):
print("Hey")
b = B(E=55)
print(str.__str__(b))
b.hello()
try:
b.B = 'hi'
except Exception as err:
print(err)
try:
b.C = 'hi'
except Exception as err:
print(err)
try:
b.E = 'hi'
except Exception as err:
print(err)
try:
b.EE = 'hi'
except Exception as err:
print(err)
try:
b.EEE = 'hi'
except Exception as err:
print(err)
try:
b.EEEE = 'hi'
except Exception as err:
print(err)
try:
B.B = 'hi'
except Exception as err:
print(err)
try:
B.C = 'hi'
except Exception as err:
print(err)
try:
B.E = 'hi'
except Exception as err:
print(err)
try:
B.EE = 'hi'
except Exception as err:
print(err)
try:
B.EEE = 'hi'
except Exception as err:
print(err)
try:
B.EEEE = 'hi'
except Exception as err:
print(err)
# B.__dict__['E'] = 68
# b.__dict__['E'] = 68
import pydantic
try:
@consts
class Model(pydantic.BaseModel):
first: str
last: const[str] = pydantic.Field(default='Boolean')
role: const[str] = pydantic.Field(default='Admin')
def change_last(self, new_last):
self.last = new_last
except Exception as err:
print(err)
@consts
class Model(pydantic.BaseModel):
first: str
last: const[str] = pydantic.Field(default='Boolean')
role: const[str] = pydantic.Field(default='Admin')
try:
m = Model(first='james')
m.last = 'hi'
print(m.last)
except Exception as err:
print(err)
try:
Model.last = 'hi'
print(Model.last)
except Exception as err:
print(err)
@jymchng
Copy link
Author

jymchng commented Nov 14, 2023

#![feature(generic_const_exprs)]

use std::{marker::PhantomData, ptr::NonNull, ops::Drop};

struct SecretBox<T: ?Sized> {
    pointer: NonNull<T>,
    _phantom: PhantomData<T>,
}

struct Secret<T: ?Sized, const MEC: usize, const EC: usize=0> {
    inner_box: SecretBox<T>,
}

struct SecretGuard<'a, T: ?Sized, const MEC: usize, const EC: usize> {
    inner_secret: &'a Secret<T, MEC, EC>,
}

impl<T, const MEC: usize> Secret<T, MEC> {
    pub fn new(value: T) -> Secret<T, MEC> {
        let sb = SecretBox {
            pointer: unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(value))) },
            _phantom: PhantomData,
        };
        Secret { inner_box: sb }
    }
}

impl<T, const MEC: usize, const EC: usize> Secret<T, MEC, EC> {
    const NOT_MAX: () = assert!(EC < MEC, "Secret is over-exposed");

    #[must_use]
    pub fn expose_secret(self, mut func: impl FnMut(&T) -> ()) -> Secret<T, MEC, {EC+1}> {
        let _ = Self::NOT_MAX;
        let ref_t = unsafe { self.inner_box.pointer.as_ref() };
        func(ref_t);
        Secret {
            inner_box: self.inner_box,
        }
    }
}

impl Drop for 


fn main() {
    struct UseSecret {
        inner: String
    }
    
    impl UseSecret {
        fn new(value: String) -> Self {
            Self {
                inner: value,
            }
        }
    }

    let new_secret: Secret::<String, 2> = Secret::new("hello".into());
    let mut use_secret: UseSecret = UseSecret::new("".to_string());
    let new_secret = new_secret.expose_secret(
        |ref _secret_string_ref| {
            use_secret = UseSecret::new(_secret_string_ref.to_string());
        }
    );
    let new_secret = new_secret.expose_secret(
        |ref _secret_string_ref| {
            use_secret = UseSecret::new(_secret_string_ref.to_string());
        }
    );
    println!("{:?}", use_secret.inner);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment