|
| 1 | +Public interface guidelines |
| 2 | +=========================== |
| 3 | + |
| 4 | +Overview |
| 5 | +-------- |
| 6 | + |
| 7 | +This document provides guidelines for the management of RDFLib's public |
| 8 | +interface. |
| 9 | + |
| 10 | +These guidelines attempt to balance the following: |
| 11 | + |
| 12 | +* The need for stable public interface that do not frequently change in ways that are |
| 13 | + backwards incompatible. |
| 14 | +* The need for modern, easy to use, well-designed public interface free of defects. |
| 15 | +* The need to change the public interface in a backwards incompatible way to |
| 16 | + modernize it, eliminate defects, and improve usability and design. |
| 17 | + |
| 18 | +Versioning |
| 19 | +---------- |
| 20 | + |
| 21 | +RDFLib follows semantic versioning [SEMVER]_, which can be summarized as: |
| 22 | + |
| 23 | + Given a version number MAJOR.MINOR.PATCH, increment the: |
| 24 | + |
| 25 | + #. MAJOR version when you make incompatible API changes |
| 26 | + #. MINOR version when you add functionality in a backwards compatible |
| 27 | + manner |
| 28 | + #. PATCH version when you make backwards compatible bug fixes |
| 29 | + |
| 30 | + Additional labels for pre-release and build metadata are available as |
| 31 | + extensions to the MAJOR.MINOR.PATCH format. |
| 32 | + |
| 33 | +Guidelines for backwards compatible changes |
| 34 | +--------------------------------------------- |
| 35 | + |
| 36 | +Examples of backwards compatible changes include: |
| 37 | + |
| 38 | +* Adding entirely new modules, functions, variables, or classes. |
| 39 | +* Adding new parameters to existing functions with defaults that maintains the |
| 40 | + existing behavior of the function. |
| 41 | + |
| 42 | +Non-breaking changes can be made directly in the main RDFLib module, i.e. |
| 43 | +:mod:`rdflib`. |
| 44 | + |
| 45 | +In some cases it may be appropriate to place new identifiers under a |
| 46 | +``_provisional`` module (see `Provisional Python modules`_), especially if they |
| 47 | +provide complex functionality, this gives the users of RDFLib some opportunity |
| 48 | +to try them in non-critical settings while still allowing RDFLib developers to |
| 49 | +change the behavior before committing to them and making them part of the public |
| 50 | +interface. |
| 51 | + |
| 52 | +Guidelines for backwards incompatible changes |
| 53 | +--------------------------------------------- |
| 54 | + |
| 55 | +Examples of backwards incompatible changes include: |
| 56 | + |
| 57 | +* Altering a function to behave differently in a way that cannot be considered a |
| 58 | + bug fix, some more specific examples: |
| 59 | + |
| 60 | + * Changing the return type to an incompatible type. |
| 61 | + * Changing the supported argument types to incompatible types. |
| 62 | + * Changing the required arguments. |
| 63 | +* Changing the class hierarchy so that class relations that held previously no |
| 64 | + longer holds. |
| 65 | + |
| 66 | +When backwards incompatible changes will be introduced users should be provided |
| 67 | +with at least one release advanced notice, and, if possible, users should be |
| 68 | +given the opportunity to adapt their code to the new behavior before the old |
| 69 | +behavior is removed. |
| 70 | + |
| 71 | +At the same time it is preferable to integrate code that implements the new |
| 72 | +behavior of backwards incompatible changes into the main branch of RDFLib as |
| 73 | +soon as possible, so that long-lived pull requests or branches can be avoided. |
| 74 | + |
| 75 | +This section details various options for integrating code that implements |
| 76 | +backwards incompatible changes into the main branch while allowing maintainers |
| 77 | +explicit choice in when the backwards incompatible changes are released to users |
| 78 | +and allowing users of RDFLib the opportunity to opt into the new behavior. |
| 79 | + |
| 80 | +Simple deprecation |
| 81 | +^^^^^^^^^^^^^^^^^^ |
| 82 | + |
| 83 | +In the simplest case if a function, class, or variable will be removed or |
| 84 | +replaced with a new function with a different name, the old identifier should be |
| 85 | +marked as deprecated and the deprecation notice should indicate the new function |
| 86 | +that should be used instead. |
| 87 | + |
| 88 | +For functions `warnings.warn` should be used with `DeprecationWarning`, and for |
| 89 | +variables and classes the `Sphinx deprecated directive |
| 90 | +<https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-deprecated>`_ |
| 91 | +should be used. |
| 92 | + |
| 93 | +When deprecating whole classes a deprecation warning should be raised from |
| 94 | +`object.__init__` or `object.__new__`. |
| 95 | + |
| 96 | +The deprecation notice should indicate in what version of RDFLib the identifiers |
| 97 | +will be removed, and in most cases this should be the next major version. |
| 98 | + |
| 99 | +Release preparation for major versions of RDFLib should remove all code that was |
| 100 | +marked as deprecated for removal in the new major version. |
| 101 | + |
| 102 | +Use versioned top level module |
| 103 | +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 104 | + |
| 105 | +In some cases critical parts of the class hierarchy like `rdflib.graph.Graph` |
| 106 | +and `rdflib.term.Node` must be changed. For changes like this versioned top |
| 107 | +level modules can be used (see `Top-level Python modules`_). |
| 108 | + |
| 109 | +For example, if a new `rdflib.graph.Graph` class hierarchy needs to be created |
| 110 | +for RDFLib 7, the process would be: |
| 111 | + |
| 112 | +* Some time before releasing RDFLib 7: |
| 113 | + |
| 114 | + * Create ``rdflib.v7.graph.Graph``. |
| 115 | + * Mark `rdflib.graph.Graph` as deprecated. |
| 116 | +* When releasing RDFLib 7: |
| 117 | + |
| 118 | + * Remove `rdflib.graph.Graph`. |
| 119 | + * Import ``rdflib.v7.graph.Graph`` to `rdflib.graph.Graph` |
| 120 | + |
| 121 | +With this approach users can start using the new class before RDFLib 7 is |
| 122 | +released, and the class does not have to be kept in a separate branch or pull |
| 123 | +request until RDFLib 7 is ready for release. |
| 124 | + |
| 125 | +Some care must be taken to avoid duplicated code, some options available to do |
| 126 | +this is: |
| 127 | + |
| 128 | +* Move shared functionality out to another class or function that can be used |
| 129 | + from the old and new classes. |
| 130 | +* Import the old class and override the needed/relevant functionality. |
| 131 | + |
| 132 | +Release preparation for major versions should import the identifiers from the |
| 133 | +versioned python modules (e.g. :mod:`rdflib.v7`) into the main python module |
| 134 | +(i.e. :mod:`rdflib`) and remove the identifiers that were marked as deprecated. |
| 135 | + |
| 136 | +Use the ``rdflib._version._ENABLE_V{major_version}`` flags |
| 137 | +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 138 | + |
| 139 | +With each released of RDFLib the corresponding |
| 140 | +``rdflib._version._ENABLE_V{major_version}`` variable will be set to `True`. |
| 141 | + |
| 142 | +These variables can be used to select default values or change other behavior of |
| 143 | +functions, for example changing the default format for `rdflib.graph.Graph.serialize`. |
| 144 | + |
| 145 | +Boolean flags are used as they can be used with the ``always_true`` and |
| 146 | +``always_false`` `directives from mypy |
| 147 | +<https://mypy.readthedocs.io/en/stable/config_file.html#confval-always_true>`_. |
| 148 | + |
| 149 | +Python modules |
| 150 | +-------------- |
| 151 | + |
| 152 | +This section describes Python modules that are relevant to the management of |
| 153 | +RDFLib's public interface. |
| 154 | + |
| 155 | +Top-level Python modules |
| 156 | +^^^^^^^^^^^^^^^^^^^^^^^^ |
| 157 | + |
| 158 | +:mod:`rdflib`, the main Python module: |
| 159 | + This is the main Python module for RDFLib and represents the public |
| 160 | + interface of the current version of RDFLib. |
| 161 | + |
| 162 | +``rdflib{major_version}`` (e.g. :mod:`rdflib.v7`), versioned modules: |
| 163 | + These modules are for parts of RDFLib public interface specific to a major |
| 164 | + version, and is intended to be imported into the main RDFLib module (i.e. |
| 165 | + :mod:`rdflib`) for RDFLib releases with the same or later major version. |
| 166 | + |
| 167 | + So for example, identifiers from the :mod:`rdflib.v7` module should not be |
| 168 | + imported as public identifiers into :mod:`rdflib` until version 7 of RDFLib |
| 169 | + is released but may be symlinked into :mod:`rdflib` from version 7 and |
| 170 | + onward. |
| 171 | + |
| 172 | + These modules exist to facilitate integrating code into the main branch that |
| 173 | + should go into future major versions of RDFLib, and in most cases will be |
| 174 | + used for things that replace parts of the older interface. |
| 175 | + |
| 176 | + Versioned modules part of the public RDFLib interface, and once something |
| 177 | + appears in a versioned module it should remain part of the public interface |
| 178 | + until the next major version of RDFLib. |
| 179 | + |
| 180 | + That is, things from :mod:`rdflib.v7` should not be removed until version 8 of |
| 181 | + RDFLib is released. |
| 182 | + |
| 183 | + |
| 184 | +Provisional Python modules |
| 185 | +^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 186 | + |
| 187 | +``rdflib._provisional`` and ``rdflib.v{major_version}._provisional`` (e.g. ``rdflib.v7._provisional``), provisional modules: |
| 188 | + These modules contain code that is expected to become part of :mod:`rdflib` |
| 189 | + and ``rdflib.v{major_version}`` respectively but may change before this |
| 190 | + happens. |
| 191 | + |
| 192 | + Given that the provisional modules are marked for internal use (i.e. |
| 193 | + prefixed with an underscore ``_``) they are not considered part of RDFLib's |
| 194 | + public interface and therefore the guarantees associated with RDFLib's |
| 195 | + public interface does not extend to content of these modules, however, there |
| 196 | + is some expectation that the provisional module will be used and to minimize |
| 197 | + the impact to users of the provisional module anything that is moved out of |
| 198 | + the provisional module into the main or versioned RDFLib modules will be |
| 199 | + imported back into the provisional module so that code that used the |
| 200 | + provisional interface can potentially still remain operational. |
| 201 | + |
| 202 | +Practical Examples |
| 203 | +------------------ |
| 204 | + |
| 205 | +Glossary |
| 206 | +-------- |
| 207 | + |
| 208 | +`Python identifier <https://docs.python.org/3/reference/lexical_analysis.html#identifiers>`_ |
| 209 | + The name of a module, variable, class, function, or function argument. In |
| 210 | + some cases this is also referred to as a Python name. |
| 211 | + |
| 212 | +Backwards incompatible change |
| 213 | + A change to RDFLib that require changes to code that use RDFLib in order for |
| 214 | + that code to keep performing the same function. Also referred to as a |
| 215 | + breaking change. |
| 216 | + |
| 217 | +`Public interface <https://en.wikipedia.org/wiki/Public_interface>`_ |
| 218 | + The parts of RDFLib that are intended for use by the users of RDFLib. This |
| 219 | + more or less corresponds to the identifiers that are not directly or |
| 220 | + indirectly marked as internal by prefixing them or packages containing them |
| 221 | + with a single leading underscore [PEP8]_. |
| 222 | + |
| 223 | +Public Python identifier |
| 224 | + A Python identifier that qualifies as part of a public interface. |
| 225 | + |
| 226 | +`Python module <https://docs.python.org/3/glossary.html#term-module>`_ |
| 227 | + "An object that serves as an organizational unit of Python code. Modules |
| 228 | + have a namespace containing arbitrary Python objects. Modules are loaded |
| 229 | + into Python by the process of importing." |
| 230 | + |
| 231 | + The phrase "Python module" is used preferentially over `Python package |
| 232 | + <https://docs.python.org/3/glossary.html#term-package>`_ as it is less |
| 233 | + ambiguous. |
| 234 | + |
| 235 | +References |
| 236 | +---------- |
| 237 | + |
| 238 | +.. [PEP8] `PEP 8 – Style Guide for Python Code |
| 239 | + <https://peps.python.org/pep-0008/>`_ |
| 240 | +.. [SEMVER] `Semantic Versioning 2.0.0 <https://semver.org/spec/v2.0.0.html>`_ |
0 commit comments