For developers ============== .. note:: The content of this section is under continuous improvement as the project gathers more contributors. It is highly inspired by `Scikit-Learn `_ and `Scipy `_ contributing documentation. We highly encourage new comers to read these documentations if ours do not answer their questions. Local set up ------------ 1. Fork ReLife repository First, you need to `create an account `_ on GitHub (if you do not already have one) and fork the project repository by clicking on the ‘Fork’ button near the top of the page. This creates a copy of the code under your Github account. For more details on how to fork a repository see `this guide `_. It explains how to set up a local clone of your forked git repository. 2. Set up a local clone of your fork Clone your fork of the ReLife repo from your GitHub account to your local disk. The url to use with the clone command can be found by clicking on the green 'Code' button of the GitHub repo. When you clone a project, you get a local copy of an existing git repository that is uploaded on a server. The command below will create a relife directory on your computer with the codebase and every version of every file for the history of the project: .. code-block:: $ git clone https://github.com/YourLogin/relife.git # add --depth 1 if your connection is slow $ cd relife # go into the directory .. _upstream: Next, add the ``upstream`` remote. This saves a reference to the main ReLife repository, which you can use to keep your repository synchronized with the latest changes (you'll need this later in the :ref:`development_workflow`): .. code-block:: $ git remote add upstream https://github.com/rte-france/relife.git Check that the `upstream` and `origin` remote aliases are configured correctly by running: .. code-block:: $ git remote -v This should display: .. code-block:: text origin https://github.com/YourLogin/relife.git (fetch) origin https://github.com/YourLogin/relife.git (push) upstream https://github.com/rte-france/relife.git (fetch) upstream https://github.com/rte-france/relife.git (push) 3. ReLife local installation Make sure Python ``3.11+`` is installed on your machine. Using this Python, create a `Python virtual environment `_ with the name of your choice. **Activate your virtual environment**. Install ReLife in `editable mode `_ from the source code with the **dev** or **doc** dependencies depending on your plan to contribute to the development or the documentation. .. code-block:: $ (YourEnv) python -m pip install -e . --group dev For **uv** users, it is possible to install ReLife in developer or documentation mode with the following commands. Note that uv uses `editable installation `_ for workspace packages by default. .. code-block:: $ (YourEnv) uv sync --group dev 4. Configure your IDE If you installed the **dev** dependencies group, the commands above should have installed `ruff `_. Ruff is a powerful all-in-one tool for code linting and formatting. - The `Ruff formatter `_ is an extremely fast Python code formatter designed as a drop-in replacement for Black. This tool allows you to automatically `format your code `_. Ensure that your IDE is configured to call them to format on save so that you don't have to call them manually. - The `Ruff Linter `_ is an extremely fast Python `linter `_ designed as a drop-in replacement of well-known linters like flake8. Ensure that your IDE captures diagnostics from this tool while you are coding. Static type checking -------------------- Additionally, `static type checkers `_. have been installed: - `basedpyright `_. Again, ensure that your IDE communicates with the basedpyright language server (`LSP `_) to receive feedback on your type annotations. Type checking can be challenging and may not be desired at first, so consider flagging your modules with ``#pyright: basic`` to start. Once you feel comfortable, gradually enhance your type annotations by removing ``#pyright: basic`` and enabling strict mode in basedpyright configurations. - After successfully passing all Pyright analyses, use `mypy `_ to validate or supplement the diagnostics provided by basedyright. .. _development_workflow: Development workflow -------------------- The next steps describe the process of modifying code and submitting a PR: 1. Synchronize your ``main`` branch with the ``upstream/main`` branch, more details on `GitHub Docs `_: .. code-block:: bash $ git checkout main $ git pull upstream main 2. Create a feature branch to hold your development changes: .. code-block:: bash $ git checkout -b my_feature and start making changes. Always use a feature branch. It's good practice to never work on the ``main`` branch! 3. Develop the feature on your feature branch on your computer, using Git to do the version control. When you're done editing, add changed files using ``git add`` and then ``git commit``: .. code-block:: bash $ git add modified_files $ git commit You will be prompted to enter a commit message. Please, as much as possible, start your message with the dedicated :ref:`commit_markers`. Your message must look like this .. code-block:: text : commit title commit description Then push the changes to your GitHub account with: .. code-block:: bash $ git push -u origin my_feature 4. Before opening a pull request, please verify that your work meets our :ref:`pull_requests_checklist`. 5. Follow `these `_ instructions to create a pull request from your fork. This will send a notification to potential reviewers. It is often helpful to keep your local feature branch synchronized with the latest changes of the main ReLife repository. To do that, regularly check the evolution of ReLife codebase, pull main changes and merge them in your feature branch. .. code-block:: bash $ git pull upstream main $ git checkout my_feature $ git merge main Subsequently, you might need to solve the conflicts. You can refer to the `Git documentation related to resolving merge conflict using the command line `_ or the `Git documentation itself (Basic Merge Conflicts) `_ .. note:: One very helping tool to manage git command is `Lazygit `_. It comes with a very user-friendly TUI and preconfigured set of usefull commands to manage commits and branches. .. _commit_markers: Commit message markers ---------------------- Please follow and use these standard acronyms to start your commit messages : .. code-block:: BUG: bug fix DEP: deprecate something, or remove a deprecated object DEV: development tool or utility DOC: documentation ENH: enhancement MAINT: maintenance commit (refactoring, typos, etc.) REV: revert an earlier commit STY: style fix (PEP8, reformat, etc.) TYP: typing TEST: addition or modification of tests REL: related to releasing ReLife CI: related to CI .. _pull_requests_checklist: Pull request checklist ---------------------- Before a pull request can be merged, it needs to be approved by 1 core developer. An incomplete contribution -- where you expect to do more work before receiving a full review -- should be marked as a `draft pull request `_ and changed to "ready for review" when it matures. In order to ease the reviewing process, we recommend that your contribution complies with the following rules before marking a PR as "ready for review". The **bolded** ones are especially important: 1. **Give your pull request a helpful title** that summarizes what your contribution does. This title will often become the commit message once merged so it should summarize your contribution for posterity. In some cases "Fix " is enough. "Fix #" is never a good title. 2. **Do not include unecessary/unjustified commits** that don't have direct relations with the issue. Do not include unecessary lines rewrittings or modifications of code style unless it is clearly motivated. Also, remove personal comments in your code. 3. **Make sure to address the whole issue**. Don't propose code that resolves the issue partly. If case you have any doubt, discuss it directly in the corresponding issue post. 4. **Make sure your code passes the tests**. The whole test suite can be run with ``pytest``, but it is usually not recommended since it takes a long time. It is often enough to only run the tests related to your changes: for example, if you changed something in ``relife/lifetime_model/_conditional_model.py``, running the following commands will usually be enough: - ``pytest relife/lifetime_model/tests/test_conditional_model.py`` to run the tests specific to the file - ``pytest relife/lifetime_model`` to test the whole ``relife.lifetime_model`` module 5. **Make sure your code is properly commented and documented**, and **make sure the documentation renders properly**. To build the documentation locally, please refer to :ref:`build_the_doc`. 6. Typing your code is not necessary at first, but make sure it is logic. The typing can be handled by the core team of ReLife. It is still a work in progress. About conception ---------------- If you want to adress conception problem, you're welcome. **But these issues must be carefully motivated and well justified**. More precisely, we won't accept any modifications that would be too subjective, e.g. *only because you think it is more readable*. .. warning:: We are aware that overall ReLife code base *design* can be improved. Especially, we are currently having a special care on typing and are *stubifying* the code base. This work is done in addition to feature enhancements and progress at its own pace. It will be tested against mypy and it complements development principles that we study. At the end, we expect this static type checking will get the overall code base quality in the right direction. .. _build_the_doc: Build the documentation ----------------------- Before being able to build the documentation, you need to install `Pandoc `_ which is a powerful file converter between many different formats. To build the documentation locally run the following commands : .. code-block:: $ make html -C docs To run a local documentation server and read your built documentation : .. code-block:: $ python -m http.server -d docs/build/html/ Then go to `http://localhost:8000 `_ .. warning:: Using Sphinx for the first time is frustrating. It is easy to get tons of errors as they are cumulative. Read the Sphinx documentation and **be carefull** about unwanted blank lines or missing spaces in reST directives. The documentation is built with `PyData Sphinx Theme `_ and uses the NumPy documentation style. Here are important points to have in mind if you want to **contribute to the documentation** : * Read the `NumPy documentation style guide `_ * Classes documentation .rst files are generated using `Jinja2 `_ template engine.The template is written in ``class_template.rst`` of the ``doc/source/_templates``. A guide to Jinja2 templating is given in `Sphinx autosummary documentation `_. Basically, one can catch usefull variables like ``methods`` and create nice autosummary tables inside an ``autoclass``. We chose this style because it creates very clean and comprehension interface documentation of the class. * Take a special care to attributes class documentation. Sphinx does not handle attribute instances easily, especially when they are **inherited**. One must reference them manually in the object class under the `Attributes` field of the docstring. As it is mentionned in `NumPy documentation style guide `__, property methods (getter and/or setter) can be listed there. Their attached docstring will be loaded automatically. One more thing, some IDE (like PyCharm) may raise warnings about unreferenced variables. It is a bug... ignore or disable it at the statement level. Typing ------ Most of ReLife methods are Numpy compatible. For typing, we use `optype `__. It offers more functionnalities to type Numpy code and it is compatible with ``scipy-stubs``, the official typing of Scipy. By default, many methods use either: * ``ST`` : stands for Scalar Type, alias of ``float`` and ``int``. * ``NumpyST`` : stands for Numpy Scalar Type, alias of ``np.floating`` and ``np.uint``. * ``ArrayND[NumpyST]`` : stands for ``np.ndarray`` of ``NumpyST`` values. The return types are generally narrowed to either ``np.float64`` or ``ArrayND[np.float64]``. Additionally, when it makes sense, the shape of arrays are specified with e.g. ``Array1D`` or ``Array[AtMost{}D, ...]``. .. note:: Technically, the output type values could be different with Numpy operations. For instance, it could be ``np.floating | np.integer | np.bool`` depending on the types of the inputs. In practice, we don't know how to type the code in a more generic way without being too heavy or clumsy. So, at runtime, we force the output to be always ``np.float64`` that is used most often. It aligns with the typing and simplifies definitions. .. note:: ``freeze`` methods don't accept ``ArrayND[NumpyST]`` as input like other methods. Inputs are narrowed to ``Array[AtMost2D, NumpyST]`` because frozen models are meant to be used in stochastic processes or policies where additionnal args can't have more than 2 dimensions. That's why ``freeze`` is not part of ``ParametricLifetimeModel`` interface by default but is specific to derived classes. Linting ------- In many places, docstrings can be longer than 80 characters. In this case, we add a ``# noqa: E501`` mark to deactivate ``ruff`` warning.