Skip to content

Making FabSim3 installable as a Python package using an existing build backend #250

@matt-graham

Description

@matt-graham

Is your feature request related to a problem? Please describe.

Currently FabSim3 uses a bespoke approach for having users install the application via the configure_fabsim.py script, and the fabsim directory is not structured as a regular Python package due to the lack of __init__.py files in the top-level and sub directories.

This creates a few related issues:

  • FabSim3 cannot be installed with existing package managment tools like pip, conda, poetry or pdm. This means it cannot be specified as a dependency for other packages and libraries using standard approaches (for example within a pyproject.toml file, setup.py script, requirements.txt or other tool specific format such as conda environment YAML or lock files). This also makes it difficult to use tools like tox, which are designed to help automating running Python tests in isolated environments, with FabSim3 itself or packages / modules (such as FabSim3 plugins) which themselves depend on FabSim. For example we have hit against precisely this issue when trying to run tests for the FabNESO plugin using tox (First draft of tests on the ensemble_tools UCL/FabNESO#23).
  • If a user follows the recommendation in the installation script to add the root of the FabSim3 repository to PYTHONPATH in .bashrc (or equivalent for other shells), all of the directories within this root directory that are valid Python package / module names (that is docs, fabsim, lists, plugins, tests) will be importable as namespace packages in all Python environments that the user runs that do not otherwise overwrite PYTHONPATH. Polluting the Python import namespace globally like this is not ideal, and given the very generic names of some of these directories could lead to confusion for example if a user imports lists and finds that nothing is defined there.
  • The configure_fabsim.py script reimplements standard functionality available in build backends such as setuptools by manually installing dependencies in a way which makes it difficult to flexibly used FabSim with an environment management system of the users choice (the script forces use of pip to install dependencies and uses the --user argument unless the VIRTUALENV variable is set - this means if installing in a conda environment for example, the packages will still be installed in the user directory rather than isolated to the conda environment).
  • Presumably partly because of the above non-standard approach for making fabsim importable, plugins are recommended to use a verbose approach of importing names from fabsim.base.fab, which shouldn't be necessary if fabsim was installed into the site-packages directory as a regular package.
  • Because of the lack of __init__.py files in fabsim and its subdirectories, any imports from that namespace will be resolved as an (implicit) namespace package which carries a performance penalty compared to imporing a regular package.

Describe the solution you'd like

  • For the fabsim directory to be made in a regular Python package by including __init__.py files in the top-level and all subdirectories.
  • For the current configure_fabsim.py script approach used for installing FabSim3 to be changed to instead use a modern pyproject.toml based approach for declaring package metadata combined with a build backend such as setuptools or poetry.
  • For the fabsim package to register a console script entrypoint for its command line interface rather than having the user add the fabsim/bin directory manually to search path.
  • For FabSim3 plugins to be able to register a fabsim.plugins entrypoint as a (potentially alternative to while maintaining current) approach for allowing FabSim3 to discover available plugins.
  • For FabSim3 user specific configurations files such as each plugins machines_{plugin_name}_user.yml file and the top-level machines_user.yml file to be located in an appropriate platform specific application data directory using something like platformdirs.
  • Make the current automatic setup of SSH keys in

    FabSim3/configure_fabsim.py

    Lines 209 to 221 in 7d80396

    # setup ssh localhost
    if os.path.isfile("{}/.ssh/id_rsa.pub".format(os.path.expanduser("~"))):
    print("local id_rsa key already exists.")
    else:
    os.system('ssh-keygen -q -f ~/.ssh/id_rsa -t rsa -b 4096 -N ""')
    os.system("cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys")
    os.system("chmod og-wx ~/.ssh/authorized_keys")
    os.system("ssh-keyscan -H localhost >> ~/.ssh/known_hosts")
    # use ssh-add instead of ssh-copy-id for MacOSX
    if FS3_env.OS_system == "OSX":
    os.system("ssh-add /Users/{}/.ssh/id_rsa".format(FS3_env.user_name))

    something the user separately explicitly opts in to post install by running a fabsim command, as some users may not want FabSim3 to make changes to these files.

Describe alternatives you've considered

An alternative to making fabsim a regular package, would be to keep it as a namespace package as currently and either use a src layout plus auomatic package discovery or explicitly specify the package directory in a pyproject.toml / setup.cfg / setup.py file. This would then allow FabSim3 plugins to 'register' themselves by making themselves part of the same namespace package and the pkguti.iter_modules() function used to discover all registered modules in a particular namespace (for example fabsim.plugins). The disadvantage of this approach is the aforementioned slight performance disadvantage at import time of namespace versus regular packages, and that this approach will force all plugins which wish to use this approach of registering themselves to be namespace packages as a plugin created as regular package with an __init__.py would make other subpackages in namespace non-importable.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions