From 1ce807012adde3ac9746420d371f9ef5081ae477 Mon Sep 17 00:00:00 2001 From: GodSaveTheDoge <51802433+GodSaveTheDoge@users.noreply.github.com> Date: Tue, 11 May 2021 22:07:10 +0200 Subject: [PATCH] Made it I guess. Missing type hinting for frame. --- .gitignore | 154 ++++++++++++++++++++++++++++++++++++++++ ProofOfConcept/const.py | 30 -------- ProofOfConcept/runme.py | 8 --- pyconstants.py | 33 +++++++++ test.py | 9 +++ 5 files changed, 196 insertions(+), 38 deletions(-) create mode 100644 .gitignore delete mode 100644 ProofOfConcept/const.py delete mode 100644 ProofOfConcept/runme.py create mode 100644 pyconstants.py create mode 100644 test.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..228a0b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,154 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +# End of https://www.toptal.com/developers/gitignore/api/python diff --git a/ProofOfConcept/const.py b/ProofOfConcept/const.py deleted file mode 100644 index c8e2cfe..0000000 --- a/ProofOfConcept/const.py +++ /dev/null @@ -1,30 +0,0 @@ -import sys -from dataclasses import dataclass -from typing import Any -from IPython import embed - -@dataclass -class ConstVar: - frame: Any # too lazy to type hint correctly. Doesn't matter - name: str - value: Any - -ConstList= [] - -def checkConsts(*a): - for cvar in ConstList: - if cvar.frame.f_locals[cvar.name] != cvar.value: - cvar.frame.f_locals[cvar.name] = cvar.value - raise SyntaxError('Cannot assign value to constant') - -class ConstSetter: - def __setattr__(self, key, value): - target_frame = sys._getframe(1) - target_frame.f_locals[key] = value - target_frame.f_trace = checkConsts - target_frame.f_trace_opcodes = True - target_frame.f_trace_lines = False - ConstList.append(ConstVar(target_frame, key, value)) - -const = ConstSetter() -sys.settrace(lambda *a: None) diff --git a/ProofOfConcept/runme.py b/ProofOfConcept/runme.py deleted file mode 100644 index 0c262eb..0000000 --- a/ProofOfConcept/runme.py +++ /dev/null @@ -1,8 +0,0 @@ -from const import const - -# Create a new constant -const .a = 5 -print(a) - -# Let's try to assign something to it -a = 6 diff --git a/pyconstants.py b/pyconstants.py new file mode 100644 index 0000000..f23f2d0 --- /dev/null +++ b/pyconstants.py @@ -0,0 +1,33 @@ +import sys +from collections import defaultdict +from typing import Dict, Callable, List, Final +from dataclasses import dataclass + +FRAMETYPE = object + +@dataclass +class ConstantValue: + frame: FRAMETYPE # FIXME + name: str + value: object + +def _traceFun(frame: FRAMETYPE, event: str, arg: object) -> None: # FIXME + for constant in constMap[frame]: + if constant.name in frame.f_locals and frame.f_locals[constant.name] != constant.value: + frame.f_locals[constant.name] = constant.value + raise SyntaxError(f"Cannot assign value to constant {repr(constant.name)}") + +class _ConstClass: + def __setattr__(self, name, value): + """This is the method called when a new constant is created""" + targetFrame = sys._getframe(1) + targetFrame.f_trace_lines = False + targetFrame.f_trace_opcodes = True + targetFrame.f_trace = _traceFun + targetFrame.f_locals[name] = value + constMap[targetFrame].append(ConstantValue(targetFrame, name, value)) + + +constMap: Dict[FRAMETYPE, List[ConstantValue]] = defaultdict(list) # FIXME +sys.settrace(lambda frame, event, arg: None) +const = _ConstClass() diff --git a/test.py b/test.py new file mode 100644 index 0000000..d9e2bbd --- /dev/null +++ b/test.py @@ -0,0 +1,9 @@ +import pytest +from pyconstants import const + +def test_costant(): + # It's not that complex, why am I even writing tests? + const .myvar = 5 + with pytest.raises(SyntaxError): + myvar = 7 + assert myvar == 5