From f13962890fc9656d0c863a86b001ebca8e5ee854 Mon Sep 17 00:00:00 2001 From: adrienmalin <41926238+adrienmalin@users.noreply.github.com> Date: Sun, 19 Aug 2018 04:49:56 +0200 Subject: [PATCH] Use my new super module propertize --- point.py | 6 ++-- propertize/__init__.py | 41 +++++++++++++++++++++++++++ propertize/convert_string.py | 14 ++++++++++ propertize/propertize.py | 49 +++++++++++++++++++++++++++++++++ propertize/rename_attributes.py | 34 +++++++++++++++++++++++ 5 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 propertize/__init__.py create mode 100644 propertize/convert_string.py create mode 100644 propertize/propertize.py create mode 100644 propertize/rename_attributes.py diff --git a/point.py b/point.py index 31f5311..be97735 100644 --- a/point.py +++ b/point.py @@ -4,16 +4,16 @@ from consts import CLOCKWISE from qt5 import QtCore +from propertize import propertize, rename_attributes, snake_case +@propertize("", "set") +@rename_attributes(snake_case) class Point(QtCore.QPoint): """ Point of coordinates (x, y) """ - x = property(QtCore.QPoint.x, QtCore.QPoint.setX) - y = property(QtCore.QPoint.y, QtCore.QPoint.setY) - def rotate(self, center, direction=CLOCKWISE): """ Returns the Point image of the rotation of self through 90° CLOKWISE or COUNTERCLOCKWISE around center""" diff --git a/propertize/__init__.py b/propertize/__init__.py new file mode 100644 index 0000000..03fb55d --- /dev/null +++ b/propertize/__init__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +""" + Class decorators: + + @propertize buids derived classes + with a properties for each attribute with accessors + + @convert_attributes_name buids derived classes + with attributes name converted by a function + + Accessors are found among the class's methods (its callable attributes) + if the method's name start with getters_prefix or setters_prefix. + If getters_prefix is "", creates property for each method named "method_name" + only if a setter named "setters_prefix"+"method_name" is found and vice_versa. + Attributes are named by their accessors' name minus the prefix. + You can use @rename_attributes(snake_case) to rename all class' attributes + + :Example: + + >>> from propertize import propertize, snake_case + >>> class AccessorsClass: + ... def __init__(self): + ... self._private_attribute = 0 + ... def getAttribute(self): + ... return self._private_attribute + ... def setAttribute(self, value): + ... self._private_attribute = value + ... + >>> @propertize("get", "set") + ... @rename_attributes(snake_case) + ... class PropertiesClass(AccessorsClass): pass + ... + >>> instance = PropertiesClass() + >>> instance.attribute = 1 + >>> instance.attribute + 1 +""" + +from .convert_string import snake_case +from .rename_attributes import rename_attributes +from .propertize import propertize diff --git a/propertize/convert_string.py b/propertize/convert_string.py new file mode 100644 index 0000000..86c41f3 --- /dev/null +++ b/propertize/convert_string.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +import re + + +first_cap_re = re.compile('(.)([A-Z][a-z]+)') +all_cap_re = re.compile('([a-z0-9])([A-Z])') + +def snake_case(s): + """ + Convert a CamelCase string to snake_case + """ + + s1 = first_cap_re.sub(r'\1_\2', s) + return all_cap_re.sub(r'\1_\2', s1).lower() \ No newline at end of file diff --git a/propertize/propertize.py b/propertize/propertize.py new file mode 100644 index 0000000..41c2ba5 --- /dev/null +++ b/propertize/propertize.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +from collections import defaultdict + +""" + Class decorator building derived classes + with a properties for each hidden attribute with accessors +""" + +def propertize( + getters_prefix = "get_", + setters_prefix = "set_" + ): + """ + :param getters_prefix: methods starting with this prefix will be considered as getters + :type getters_prefix: str + :param setters_prefix: methods starting with this prefix will be considered as setters + :type setters_prefix: str + :return: a derived class with a properties for each attribute with accessors + :return type: type + """ + + def _propertize(cls): + if not getters_prefix and not setters_prefix: return cls + + properties_accessors = defaultdict(dict) + for method_name in dir(cls): + for accessor_type, prefix in ("getter", getters_prefix), ("setter", setters_prefix): + if method_name.startswith(prefix): + try: + method = getattr(cls, method_name) + except AttributeError: + pass + else: + if callable(method): + property_name = method_name[len(prefix):] + properties_accessors[property_name][accessor_type] = method + + class propertizeClass(cls): + pass + + for property_name, accessors in properties_accessors.items(): + getter = accessors.get("getter", None) + setter = accessors.get("setter", None) + if (getters_prefix or setter) and (setters_prefix or getter): + setattr(propertizeClass, property_name, property(getter, setter)) + + return propertizeClass + + return _propertize diff --git a/propertize/rename_attributes.py b/propertize/rename_attributes.py new file mode 100644 index 0000000..97900a3 --- /dev/null +++ b/propertize/rename_attributes.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" + Class decorator building derived classes + with all attributes renamed by a function +""" + + +def rename_attributes(convert_function): + """ + :param function_to_convert_name: function used to convert attributes' names + :type function_to_convert_name: callable + :return: a derived class with renamed attributes + :return type: type + """ + + def _rename_attributes(cls): + if not convert_function: return cls + + class ConvertedCase(cls): + pass + + for attribute_name in dir(cls): + try: + attribute = getattr(cls, attribute_name) + except AttributeError: + pass + else: + new_name = convert_function(attribute_name) + if new_name != attribute_name: + setattr(ConvertedCase, new_name, attribute) + + return ConvertedCase + + return _rename_attributes