From 76219c4d1354e464320bcbe80c10c1bdc7d707ad Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Thu, 29 May 2025 18:18:08 +0200 Subject: [PATCH 1/3] Add dynamicProperty/normalization info and polish. --- .../source/environments/objects/index.rst | 149 +++++++++++------- 1 file changed, 89 insertions(+), 60 deletions(-) diff --git a/documentation/source/environments/objects/index.rst b/documentation/source/environments/objects/index.rst index bf89ede8..4a5df7f9 100644 --- a/documentation/source/environments/objects/index.rst +++ b/documentation/source/environments/objects/index.rst @@ -12,7 +12,7 @@ Environments will need to implement their own subclasses of: .. toctree:: :maxdepth: 1 - :hidden: + font info @@ -31,32 +31,24 @@ Environments will need to implement their own subclasses of: guideline image -Each of these require their own specific environment overrides, but the general structure follows this form:: +Each of these require their own specific environment overrides, but the general structure follows this form: - from fontParts.base import BaseSomething +Import and declaration: + :: - class MySomething(BaseSomething): + from fontParts.base import BaseSomething - # Initialization: - # This will be called when objects are initialized. - # The behavior, args and kwargs may be designed by the - # subclass to implement specific behaviors. + class MySomething(BaseSomething): + + +Initialization: + This will be called when objects are initialized. The behavior, args and kwargs may be designed by the subclass to implement specific behaviors:: def _init(self, myObj): self.myObj = myObj - # Comparison: - # The __eq__ method must be implemented by subclasses. - # It must return a boolean indicating if the lower level - # objects are the same object. This does not mean that two - # objects that have the same content should be considered - # equal. It means that the object must be the same. The - # corrilary __ne__ is optional to define. - # - # Note that the base implentation of fontParts provides - # __eq__ and __ne__ methods that test the naked objects - # for equality. Depending on environmental needs this can - # be overridden. +Comparison: + The ``__eq__`` method must be implemented by subclasses. It must return a boolean indicating if the lower level objects are the same object. This does not mean that two objects that have the same content should be considered equal; it means that the object must be the same. The corrilary ``__ne__`` is optional to define. :: def __eq__(self, other): return self.myObj == other.myObj @@ -64,36 +56,61 @@ Each of these require their own specific environment overrides, but the general def __ne__(self, other): return self.myObj != other.myObj - # Properties: - # Properties are get and set through standard method names. - # Within these methods, the subclass may do whatever is - # necessary to get/set the value from/to the environment. + .. note:: + + The base implentation of fontParts provides ``__eq__`` and ``__ne__`` methods that test the naked objects for equality. Depending on environmental needs these can be overridden. + +Properties: + Properties in FontParts are accessed and modified using standardized getter and setter method names. To support subclassing of these accessors independently, FontParts uses a special class: :class:~fontParts.base.dynamicProperty. + + A property built with :class:~fontParts.base.dynamicProperty typically looks like this:: + + something = dynamicProperty(...) + + def _get_base_something(self): + value = self._get_something() + ... + return value + + def _set_base_something(self, value): + ... + self._set_something(value) + + def _get_something(self) + self.raiseNotImplementedError() + + def _set_something(self, value): + self.raiseNotImplementedError() + + The ``_get_something`` and ``set_something`` methods are the *environment-level + implementations*. Subclasses override these to define how the value is retrieved from or written to the actual object in the environment:: def _get_something(self): + ... return self.myObj.getSomething() def _set_something(self, value): + ... self.myObj.setSomething(value) - # Methods: - # Generally, the public methods call internal methods with - # the same name, but preceded with an underscore. Subclasses - # may implement the internal method. Any values passed to - # the internal methods will have been normalized and will - # be a standard type. + Subclasses do *not* need to handle normalization or validation of data. That logic is + handled by the base class methods: + + - ``_get_base_something`` calls ``_get_something``, validates and normalizes the return value, and provides it to the scripter. + + - ``_set_base_something`` takes the value from the scripter, validates and normalizes it, then passes it to ``_set_something``. + + + See :ref:`data-normalization` below for more information about normalization in FontParts. + +Methods: + Generally, the public methods call internal methods with the same name, but preceded with an underscore (``_``). Subclasses may implement the internal method. Any values passed to the internal methods will have been normalized and will be a standard type. :: def _whatever(self, value): self.myObj.doWhatever(value) - # Copying: - # Copying is handled in most cases by the base objects. - # If subclasses have a special class that should be used - # when creating a copy of an object, the class must be - # defined with the copyClass attribute. If anything special - # needs to be done during the copying process, the subclass - # can implement the copyData method. This method will be - # called automatically. The subclass must call the base class - # method with super. +Copying: + Copying is handled in most cases by the base objects. If subclasses have a special class that should be used when creating a copy of an object, the class must be defined with the `copyClass` attribute. If anything special needs to be done during the copying process, the subclass can implement the :meth:`~fontParts.base.BaseObject.copyData` method. This method will be called automatically. The subclass must call the base class method with super. :: copyClass = MyObjectWithoutUI @@ -101,38 +118,50 @@ Each of these require their own specific environment overrides, but the general super(MySomething, self).copyData(source) self.myObj.internalThing = source.internalThing - # Environment updating: - # If the environment requires the scripter to manually - # notify the environment that the object has been changed, - # the subclass must implement the changed method. Please - # try to avoid requiring this. +Environment updating: + If the environment requires the scripter to manually notify the environment that the object has been changed, the subclass must implement the changed method. Please try to avoid requiring this. :: def changed(self): myEnv.goUpdateYourself() - # Wrapped objects: - # It is very useful for scripters to have access to the - # lower level, wrapped object. Subclasses implement this - # with the naked method. +Wrapped objects: + It is very useful for scripters to have access to the lower level, wrapped object. Subclasses implement this with the naked method. :: def naked(self): return self.myObj -All methods that must be overridden are labeled with "Subclasses must override this method." in the method's documentation string. If a method may optionally be overridden, the documentation string is labeled with "Subclasses may override this method." All other methods, attributes and properties **must not** be overridden. +All methods that must be overridden are labeled with the following note in the method's documentation string: + +.. important:: + + Subclasses must override this method. + +If a method may optionally be overridden, the documentation string is labeled with: + +.. note:: + + Subclasses may override this method. + +All other methods, attributes and properties **must not** be overridden. -An example implementation that wraps the defcon library with fontParts is located in fontParts/objects/fontshell. +An example implementation that wraps the defcon library with fontParts is located in ``fontParts/objects/fontshell``. + +.. _data-normalization: Data Normalization ================== -When possible, incoming and outgoing values are checked for type validity and are coerced to a common type for return. This is done with a set of functions located here: +When possible, incoming and outgoing values are checked for type validity and are coerced to a common type for return. This is done with a set of functions located in the :mod:`~fontParts.base.normalizers` module. -.. toctree:: - :maxdepth: 1 +In many cases, a ``(x, y)`` tuple needs to be validated. Instead of repeatedly writing +code to check that there are exactly two values, that each is an :class:`int` or +:class:`float`, and that the result is a :class:`tuple` (not a :class:`list`, +:class:`set`, or some other type), we use a single function to handle all these checks +in one place. + +FontParts employs - objects/normalizers -These are done in a central place rather than within the objects for consitency. There are many cases where a ``(x, y)`` tuple is defined and than rewriting all of the code to check if there are exactly two values, that each is an ``int`` or a ``float`` and so on before finally making sure that the value to be returned is a ``tuple`` not an instance of ``list``, ``OrderedDict`` or some native object we consolidate the code into a single function and call that. Environment Specific Methods, Attributes and Arguments ====================================================== @@ -144,7 +173,7 @@ FontParts is designed to be environment agnostic. Therefore, it is a 100% certai def doSomething(self, skip=None, double=None): # go -This *will* work. However, if FontParts adds a ``doSomething`` method in a later version that does something other than what or accepts different arguments than your method does, it's not going to work. Either the ``doSomething`` method will have to be changed in your implementation or you will not support the FontParts ``doSomething`` method. This is going to be lead to you being mad at FontParts, your scripters being mad at you or something else unpleasant. +This *will* work. However, if FontParts adds a ``doSomething`` method in a later version that does something different or accepts different arguments than your method does, it's not going to work. Either the ``doSomething`` method will have to be changed in your implementation or you will not support the FontParts ``doSomething`` method. This is going to be lead to you being mad at FontParts, your scripters being mad at you or something else unpleasant. The solution to this problem is to prevent it from happening in the first place. To do this, environment specific methods, proprties and attributes must be prefixed with an environment specific tag followed by an ``_`` and then your method name. For example:: @@ -158,9 +187,9 @@ This applies to any method, attribute or property additions to the FontParts obj Method Arguments ---------------- -In some cases, you are likely to discover that your environment supports specific options in a method that are not supported by the environment agnostic API. For example, your environment may have an optional heuristic that can be used in the ``font.autoUnicodes`` method. However, the ``font.autoUnicodes`` method does not have a ``useHeuristics`` argument. Unfortunately, Python doesn't offer a way to handle this in a way that is both flexible for developers and friendly for scripters. The only two options for handling this are: +In some cases, you are likely to discover that your environment supports specific options in a method that are not supported by the environment agnostic API. For example, your environment may have an optional heuristic that can be used in the :meth:`~fontParts.base.BaseFont.autoUnicodes` method. However, this method does not have a `useHeuristics` argument. Unfortunately, Python doesn't offer a way to handle this in a way that is both flexible for developers and friendly for scripters. The only two options for handling this are: - #. Create an environment specific clone of the ``font.autoUnicodes`` method as ``myapp_autoUnicodes`` and add your ``useHeuristics`` argument there. - #. Contact the FontParts developers by opening a GitHub issue requesting support for your argument. If it is generic enough, we may add support for it. + #. Create an environment specific clone of the :meth:`~fontParts.base.BaseFont.autoUnicodes` method as ``myapp_autoUnicodes`` and add your `useHeuristics` argument there. + #. Contact the FontParts developers by opening a `GitHub `_ issue requesting support for your argument. If it is generic enough, we may add support for it. -We're experimenting with a third way to handle this. You can see it as the ``**environmentOptions`` argument in the :meth:`BaseFont.generate` method. This may or may not move to other methods. Please contact us if you are interested in this being applied to other methods. +We're experimenting with a third way to handle this. You can see it as the `**environmentOptions` argument in the :meth:`~fontParts.base.BaseFont.generate` method. This may or may not move to other methods. Please contact us if you are interested in this being applied to other methods. From 99af19bcd3a5876d1fd0463a0962575772b468fd Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Thu, 29 May 2025 23:02:00 +0200 Subject: [PATCH 2/3] Fix indentation issue and typo. --- .../source/environments/objects/index.rst | 153 ++++++++++-------- 1 file changed, 84 insertions(+), 69 deletions(-) diff --git a/documentation/source/environments/objects/index.rst b/documentation/source/environments/objects/index.rst index 4a5df7f9..b0ec6722 100644 --- a/documentation/source/environments/objects/index.rst +++ b/documentation/source/environments/objects/index.rst @@ -31,104 +31,123 @@ Environments will need to implement their own subclasses of: guideline image +Structure +========= + Each of these require their own specific environment overrides, but the general structure follows this form: -Import and declaration: - :: +Import and declaration +---------------------- + +:: - from fontParts.base import BaseSomething + from fontParts.base import BaseSomething - class MySomething(BaseSomething): + class MySomething(BaseSomething): -Initialization: - This will be called when objects are initialized. The behavior, args and kwargs may be designed by the subclass to implement specific behaviors:: +Initialization +-------------- - def _init(self, myObj): - self.myObj = myObj +This will be called when objects are initialized. The behavior, args and kwargs may be designed by the subclass to implement specific behaviors:: -Comparison: - The ``__eq__`` method must be implemented by subclasses. It must return a boolean indicating if the lower level objects are the same object. This does not mean that two objects that have the same content should be considered equal; it means that the object must be the same. The corrilary ``__ne__`` is optional to define. :: + def _init(self, myObj): + self.myObj = myObj - def __eq__(self, other): - return self.myObj == other.myObj +Comparison +---------- - def __ne__(self, other): - return self.myObj != other.myObj +The ``__eq__`` method must be implemented by subclasses. It must return a boolean indicating if the lower level objects are the same object. This does not mean that two objects that have the same content should be considered equal; it means that the object must be the same. The corrilary ``__ne__`` is optional to define. :: - .. note:: + def __eq__(self, other): + return self.myObj == other.myObj - The base implentation of fontParts provides ``__eq__`` and ``__ne__`` methods that test the naked objects for equality. Depending on environmental needs these can be overridden. + def __ne__(self, other): + return self.myObj != other.myObj -Properties: - Properties in FontParts are accessed and modified using standardized getter and setter method names. To support subclassing of these accessors independently, FontParts uses a special class: :class:~fontParts.base.dynamicProperty. - - A property built with :class:~fontParts.base.dynamicProperty typically looks like this:: +.. note:: - something = dynamicProperty(...) - - def _get_base_something(self): - value = self._get_something() - ... - return value + The base implentation of fontParts provides ``__eq__`` and ``__ne__`` methods that test the naked objects for equality. Depending on environmental needs these can be overridden. - def _set_base_something(self, value): - ... - self._set_something(value) +Properties +---------- - def _get_something(self) - self.raiseNotImplementedError() +Properties in FontParts are accessed and modified using standardized getter and setter method names. To support subclassing of these accessors independently, FontParts uses a special class: :class:~fontParts.base.dynamicProperty. - def _set_something(self, value): - self.raiseNotImplementedError() +A property built with :class:~fontParts.base.dynamicProperty typically looks like this:: + + something = dynamicProperty(...) + + def _get_base_something(self): + value = self._get_something() + ... + return value - The ``_get_something`` and ``set_something`` methods are the *environment-level - implementations*. Subclasses override these to define how the value is retrieved from or written to the actual object in the environment:: + def _set_base_something(self, value): + ... + self._set_something(value) - def _get_something(self): - ... - return self.myObj.getSomething() + def _get_something(self) + self.raiseNotImplementedError() - def _set_something(self, value): - ... - self.myObj.setSomething(value) + def _set_something(self, value): + self.raiseNotImplementedError() - Subclasses do *not* need to handle normalization or validation of data. That logic is - handled by the base class methods: +The ``_get_something`` and ``set_something`` methods are the *environment-level +implementations*. Subclasses override these to define how the value is retrieved from or written to the actual object in the environment:: + + def _get_something(self): + ... + return self.myObj.getSomething() + + def _set_something(self, value): + ... + self.myObj.setSomething(value) + +Subclasses do *not* need to handle normalization or validation of data. That logic is +handled by the base class methods: - - ``_get_base_something`` calls ``_get_something``, validates and normalizes the return value, and provides it to the scripter. - - - ``_set_base_something`` takes the value from the scripter, validates and normalizes it, then passes it to ``_set_something``. +- ``_get_base_something`` calls ``_get_something``, validates and normalizes the return value, and provides it to the scripter. +- ``_set_base_something`` takes the value from the scripter, validates and normalizes it, then passes it to ``_set_something``. - See :ref:`data-normalization` below for more information about normalization in FontParts. + +See :ref:`data-normalization` below for more information about normalization in FontParts. -Methods: - Generally, the public methods call internal methods with the same name, but preceded with an underscore (``_``). Subclasses may implement the internal method. Any values passed to the internal methods will have been normalized and will be a standard type. :: +Methods +------- + +Generally, the public methods call internal methods with the same name, but preceded with an underscore (``_``). Subclasses may implement the internal method. Any values passed to the internal methods will have been normalized and will be a standard type. :: + + def _whatever(self, value): + self.myObj.doWhatever(value) + +Copying +------- - def _whatever(self, value): - self.myObj.doWhatever(value) +Copying is handled in most cases by the base objects. If subclasses have a special class that should be used when creating a copy of an object, the class must be defined with the `copyClass` attribute. If anything special needs to be done during the copying process, the subclass can implement the :meth:`~fontParts.base.BaseObject.copyData` method. This method will be called automatically. The subclass must call the base class method with super. :: -Copying: - Copying is handled in most cases by the base objects. If subclasses have a special class that should be used when creating a copy of an object, the class must be defined with the `copyClass` attribute. If anything special needs to be done during the copying process, the subclass can implement the :meth:`~fontParts.base.BaseObject.copyData` method. This method will be called automatically. The subclass must call the base class method with super. :: + copyClass = MyObjectWithoutUI - copyClass = MyObjectWithoutUI + def copyData(self, source): + super(MySomething, self).copyData(source) + self.myObj.internalThing = source.internalThing - def copyData(self, source): - super(MySomething, self).copyData(source) - self.myObj.internalThing = source.internalThing +Environment updating +-------------------- -Environment updating: - If the environment requires the scripter to manually notify the environment that the object has been changed, the subclass must implement the changed method. Please try to avoid requiring this. :: +If the environment requires the scripter to manually notify the environment that the object has been changed, the subclass must implement the changed method. Please try to avoid requiring this. :: - def changed(self): - myEnv.goUpdateYourself() + def changed(self): + myEnv.goUpdateYourself() -Wrapped objects: - It is very useful for scripters to have access to the lower level, wrapped object. Subclasses implement this with the naked method. :: +Wrapped objects +--------------- - def naked(self): - return self.myObj +It is very useful for scripters to have access to the lower level, wrapped object. Subclasses implement this with the naked method. :: + + def naked(self): + return self.myObj All methods that must be overridden are labeled with the following note in the method's documentation string: @@ -159,10 +178,6 @@ code to check that there are exactly two values, that each is an :class:`int` or :class:`set`, or some other type), we use a single function to handle all these checks in one place. -FontParts employs - - - Environment Specific Methods, Attributes and Arguments ====================================================== From caa70e4bbe942673df16ccd18ae377ac67701886 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Thu, 29 May 2025 23:12:38 +0200 Subject: [PATCH 3/3] Fix cross reference. --- documentation/source/environments/objects/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/source/environments/objects/index.rst b/documentation/source/environments/objects/index.rst index b0ec6722..7663e885 100644 --- a/documentation/source/environments/objects/index.rst +++ b/documentation/source/environments/objects/index.rst @@ -72,9 +72,9 @@ The ``__eq__`` method must be implemented by subclasses. It must return a boolea Properties ---------- -Properties in FontParts are accessed and modified using standardized getter and setter method names. To support subclassing of these accessors independently, FontParts uses a special class: :class:~fontParts.base.dynamicProperty. +Properties in FontParts are accessed and modified using standardized getter and setter method names. To support subclassing of these accessors independently, FontParts uses a special class: :class:`~fontParts.base.dynamicProperty`. -A property built with :class:~fontParts.base.dynamicProperty typically looks like this:: +A property built with :class:`~fontParts.base.dynamicProperty` typically looks like this:: something = dynamicProperty(...)