diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 836de4b..8ab3a1f 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -32,7 +32,7 @@ jobs: echo "versioningit $(versioningit ../)" # build the package VERSION=$(versioningit ../) conda mambabuild --channel conda-forge --output-folder . . - conda verify noarch/mypackagename*.tar.bz2 + conda verify noarch/refl1d_models*.tar.bz2 - name: upload conda package to anaconda shell: bash -l {0} if: startsWith(github.ref, 'refs/tags/v') @@ -44,7 +44,7 @@ jobs: CONDA_LABEL="main" if [ "${IS_RC}" = "true" ]; then CONDA_LABEL="rc"; fi echo pushing ${{ github.ref }} with label $CONDA_LABEL - anaconda upload --label $CONDA_LABEL conda.recipe/noarch/mypackagename*.tar.bz2 + anaconda upload --label $CONDA_LABEL conda.recipe/noarch/refl1d_models*.tar.bz2 pypi-publish: name: upload release to PyPI diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 469e00f..d9c1932 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,17 +9,13 @@ repos: args: [--allow-multiple-documents] exclude: "conda.recipe/meta.yaml" - id: end-of-file-fixer - exclude: "tests/cis_tests/.*" - id: trailing-whitespace - exclude: "tests/cis_tests/.*" - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.3 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - exclude: "tests/cis_tests/.*" - id: ruff-format - exclude: "tests/cis_tests/.*" - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: diff --git a/README.md b/README.md index 4c51529..993a111 100644 --- a/README.md +++ b/README.md @@ -1,154 +1,4 @@ -python_project_template -======================= +refl1d models +============= -This repository is a template repository for Python projects under neutrons. -After you create a new repository using this repo as template, please follow the following steps to adjust it for the new project. - -Codebase Adjustments --------------------- - -1. Adjust the branch protection rules for the new repo. By default, we should protect the `main` (stable), `qa` (release candidate), and `next` (development) branches. - - 1.1 Go to the `Settings` tab of the new repo. - - 1.2 Click on `Branches` on the left side. - - 1.3 Click on `Add rule` button. - - 1.4 Follow the instructions from Github. - - -2. Change the License if MIT license is not suitable for you project. For more information about licenses, please -refer to [Choose an open source license](https://choosealicense.com/). - - -3. Update the environment dependency file `environment.yml`, which contain both runtime and development dependencies. -For more information about conda environment file, please refer to [Conda environment file](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-file-manually). - - 3.1 Specify environment 'name' field to match package name - - 3.2 We strongly recommended using a single `environment.yml` file to manage all the dependencies, including the runtime and development dependencies. - - 3.3 Please add comments to the `environment.yml` file to explain the dependencies. - - 3.4 Please prune the dependencies to the minimum when possible, we would like the solver to figure out the dependency tree for us. - - -4. Adjust pre-commit configuration file, `.pre-commit-config.yaml` to enable/disable the hooks you need. For more information about pre-commit, please refer to [pre-commit](https://pre-commit.com/). - - -5. Having code coverage, `codecov.yaml` is **strongly recommended**, please refer to [Code coverage](https://coverage.readthedocs.io/en/coverage-5.5/) for more information. - - -6. Adjust the demo Github action yaml files for CI/CD. For more information about Github action, please refer to [Github action](https://docs.github.com/en/actions). - - 6.1 Specify package name at: .github/workflows/package.yml#L34 - - 6.2 Specify package name at: .github/workflows/package.yml#L46 - - -7. Adjust the conda recipe, `conda-recipe/meta.yaml` to provide the meta information for the conda package. For more information about conda recipe, please refer to [Conda build](https://docs.conda.io/projects/conda-build/en/latest/). - - 7.1 Specify package name at: conda.recipe/meta.yaml#L15 - - 7.2 Update license family, if necessary: conda.recipe/meta.yaml#L42 - - -8. Adjust `pyproject.toml` to match your project. For more information about `pyproject.toml`, -please refer to [pyproject.toml](https://www.python.org/dev/peps/pep-0518/). - - 8.1 Specify package name at: pyproject.toml#L2 - - 8.2 Specify package description at: pyproject.toml#L3 - - 8.3 Specify package name at: pyproject.toml#L39 - - 8.4 Specify any terminal entry points (terminal commands) at: pyproject.toml#48. - -In the example, invoking `packagename-cli` in a terminal is equivalent to running the python script `from packagenamepy.packagename.import main; main()` - - 8.5 Projects will use a single `pyproject.toml` file to manage all the project metadata, including the project name, version, author, license, etc. - - 8.6 Python has moved away from `setup.cfg`/`setup.py`, and we would like to follow the trend for our new projects. - - -10. Specify package name at src/packagenamepy - - -11. Specify package name at: src/packagenamepy/packagename.py - -12. If a GUI isn't used, delete the MVP structure at src/packagenamepy: - 11.1: mainwindow.py - 11.2: home/ - 11.3: help/ - - -11. Clear the content of this file and add your own README.md as the project README file. -We recommend putting badges of the project status at the top of the README file. -For more information about badges, please refer to [shields.io](https://shields.io/). - -Repository Adjustments ----------------------- - -### Add an access token to anaconda - -Here we assume your intent is to upload the conda package to the [anaconda.org/neutrons](https://anaconda.org/neutrons) organization. -An administrator of `anaconda.org/neutrons` must create an access token for your repository in the [access settings](https://anaconda.org/neutrons/settings/access). - -After created, the token must be stored in a `repository secret`: -1. Navigate to the main page of the repository on GitHub.com. -2. Click on the "Settings" tab. -3. In the left sidebar, navigate to the "Security" section and select "Secrets and variables" followed by "Actions". -4. Click on the "New repository secret" button. -5. Enter `ANACONDA_TOKEN` for the secret name -6. Paste the Anaconda access token -7. Click on the "Add secret" button -8. Test the setup by creating a release candidate tag, -which will result in a package built and uploaded to https://anaconda.org/neutrons/mypackagename - -### Add an access token to codecov - -Follow the instructions in the [Confluence page](https://ornl-neutrons.atlassian.net/wiki/spaces/NDPD/pages/103546883/Coverage+reports) -to create the access token. - -Packaging building instructions -------------------------------- - -The default package publishing service is anaconda. -However, we also support PyPI publishing as well. - -### Instruction for publish to PyPI - -1. Make sure you have the correct access to the project on PyPI. -2. Make sure `git status` returns a clean state. -3. At the root of the repo, use `python -m build` to generate the wheel. -4. Check the wheel with `twine check dist/*`, everything should pass before we move to next step. -5. When doing manual upload test, make sure to use testpypi instead of pypi. -6. Use `twine upload --repository testpypi dist/*` to upload to testpypi, you will need to specify the testpipy url in your `~/.pypirc`, i.e. - -`````` -[distutils] -index-servers = pypi, testpypi - -[testpypi] - repository = https://test.pypi.org/legacy/ - username = __token__ - password = YOUR_TESTPYPI_TOKEN - -`````` - -7. Test the package on testpypi with `pip install --index-url https://test.pypi.org/simple/ mypackagename`. -8. If everything is good, use the Github workflow, `package.yml` to trigger the publishing to PyPI. - -### Instruction for publish to Anaconda - -Publishing to Anaconda is handled via workflow, `package.yml`. - -Development environment setup ------------------------------ - -### Build development environment - -1. By default, we recommend providing a single `environment.yml` that covers all necessary packages for development. -2. The runtime dependency should be in `meta.yaml` for anaconda packaging, and `pyproject.toml` for PyPI publishing. -3. When performing editable install for your feature branch, make sure to use `pip install --no-deps -e .` to ensure that `pip` does not install additional packages from `pyproject.toml` into development environment by accident. +Curated repository of refl1d models. diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 09e37d7..31a6ae6 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -12,7 +12,7 @@ {% set build_number = 0 %} package: - name: mypackagename + name: refl1d_models version: {{ version_number }} source: diff --git a/environment.yml b/environment.yml index 2a1abfd..6720ebd 100644 --- a/environment.yml +++ b/environment.yml @@ -42,3 +42,4 @@ dependencies: - pip: - bm3d-streak-removal # example - pytest-playwright + - refl1d diff --git a/pyproject.toml b/pyproject.toml index 3943d8b..d21b5fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "examplepyapp" -description = "Example Python repo for neutrons" +name = "refl1d_models" +description = "Curated repository of refl1d models" dynamic = ["version"] requires-python = ">=3.10" dependencies = [ @@ -11,10 +11,10 @@ keywords = ["neutrons", "example", "python"] readme = "README.md" [project.urls] -homepage = "https://github.com/neutrons/python_project_template/" # if no homepage, use repo url -repository = "https://github.com/neutrons/python_project_template/" +homepage = "https://github.com/reflectometry/refl1d_models/" # if no homepage, use repo url +repository = "https://github.com/reflectometry/refl1d_models/" # documentation = add_url_to_readthedoc_here -issues = "https://github.com/neutrons/python_project_template/issues" +issues = "https://github.com/reflectometry/refl1d_models/issues" [build-system] requires = [ @@ -38,7 +38,7 @@ dirty = "{version}+d{build_date:%Y%m%d}" distance-dirty = "{next_version}.dev{distance}+d{build_date:%Y%m%d%H%M}" [tool.versioningit.write] -file = "src/packagenamepy/_version.py" +file = "src/refl1d_models/_version.py" [tool.setuptools.packages.find] where = ["src"] @@ -47,12 +47,6 @@ exclude = ["tests*", "scripts*", "docs*", "notebooks*"] [tool.setuptools.package-data] "*" = ["*.yml","*.yaml","*.ini"] -[project.scripts] -packagename-cli = "packagenamepy.packagename:main" - -[project.gui-scripts] -packagenamepy = "packagenamepy.packagename:gui" - [tool.pytest.ini_options] pythonpath = [ ".", "src", "scripts" @@ -60,9 +54,6 @@ pythonpath = [ testpaths = ["tests"] python_files = ["test*.py"] norecursedirs = [".git", "tmp*", "_tmp*", "__pycache__", "*dataset*", "*data_set*"] -markers = [ - "mymarker: example markers goes here" -] [tool.ruff] line-length = 120 diff --git a/src/packagenamepy/__init__.py b/src/packagenamepy/__init__.py deleted file mode 100644 index 96e1d74..0000000 --- a/src/packagenamepy/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Contains the entry point for the application""" - -try: - from ._version import __version__ # noqa: F401 -except ImportError: - __version__ = "unknown" - - -def PackageName(): # noqa N802 - """This is needed for backward compatibility because mantid workbench does "from shiver import Shiver" """ - from .packagenamepy import PackageName as packagename # noqa N813 - - return packagename() diff --git a/src/packagenamepy/configuration.py b/src/packagenamepy/configuration.py deleted file mode 100644 index 2231ab6..0000000 --- a/src/packagenamepy/configuration.py +++ /dev/null @@ -1,105 +0,0 @@ -"""Module to load the the settings from SHOME/.packagename/configuration.ini file - -Will fall back to a default -""" - -import os -import shutil -from configparser import ConfigParser -from pathlib import Path - -from mantid.kernel import Logger - -logger = Logger("PACKAGENAME") - -# configuration settings file path -CONFIG_PATH_FILE = os.path.join(Path.home(), ".packagename", "configuration.ini") - - -class Configuration: - """Load and validate Configuration Data""" - - def __init__(self): - """Initialization of configuration mechanism""" - # capture the current state - self.valid = False - - # locate the template configuration file - project_directory = Path(__file__).resolve().parent - self.template_file_path = os.path.join(project_directory, "configuration_template.ini") - - # retrieve the file path of the file - self.config_file_path = CONFIG_PATH_FILE - logger.information(f"{self.config_file_path} will be used") - - # if template conf file path exists - if os.path.exists(self.template_file_path): - # file does not exist create it from template - if not os.path.exists(self.config_file_path): - # if directory structure does not exist create it - if not os.path.exists(os.path.dirname(self.config_file_path)): - os.makedirs(os.path.dirname(self.config_file_path)) - shutil.copy2(self.template_file_path, self.config_file_path) - - self.config = ConfigParser(allow_no_value=True, comment_prefixes="/") - # parse the file - try: - self.config.read(self.config_file_path) - # validate the file has the all the latest variables - self.validate() - except ValueError as err: - logger.error(str(err)) - logger.error(f"Problem with the file: {self.config_file_path}") - else: - logger.error(f"Template configuration file: {self.template_file_path} is missing!") - - def validate(self): - """Validates that the fields exist at the config_file_path and writes any missing fields/data - using the template configuration file: configuration_template.ini as a guide - """ - template_config = ConfigParser(allow_no_value=True, comment_prefixes="/") - template_config.read(self.template_file_path) - for section in template_config.sections(): - # if section is missing - if section not in self.config.sections(): - # copy the whole section - self.config.add_section(section) - - for item in template_config.items(section): - field, _ = item - if field not in self.config[section]: - # copy the field - self.config[section][field] = template_config[section][field] - with open(self.config_file_path, "w", encoding="utf8") as config_file: - self.config.write(config_file) - self.valid = True - - def is_valid(self): - """Returns the configuration state""" - return self.valid - - -def get_data(section, name=None): - """Retrieves the configuration data for a variable with name""" - # default file path location - config_file_path = CONFIG_PATH_FILE - if os.path.exists(config_file_path): - config = ConfigParser() - # parse the file - config.read(config_file_path) - try: - if name: - value = config[section][name] - # in case of boolean string value cast it to bool - if value in ("True", "False"): - return value == "True" - # in case of None - if value == "None": - return None - return value - return config[section] - except KeyError as err: - # requested section/field do not exist - logger.error(str(err)) - return None - return None diff --git a/src/packagenamepy/configuration_template.ini b/src/packagenamepy/configuration_template.ini deleted file mode 100644 index c8ea100..0000000 --- a/src/packagenamepy/configuration_template.ini +++ /dev/null @@ -1,2 +0,0 @@ -[global.other] -help_url = https://github.com/neutrons/python_project_template/blob/main/README.md diff --git a/src/packagenamepy/help/help_model.py b/src/packagenamepy/help/help_model.py deleted file mode 100644 index 636472d..0000000 --- a/src/packagenamepy/help/help_model.py +++ /dev/null @@ -1,12 +0,0 @@ -"""single help module""" - -import webbrowser - -from packagenamepy.configuration import get_data - - -def help_function(context): - """Open a browser with the appropriate help page""" - help_url = get_data("global.other", "help_url") - if context: - webbrowser.open(help_url) diff --git a/src/packagenamepy/home/home_model.py b/src/packagenamepy/home/home_model.py deleted file mode 100644 index e574925..0000000 --- a/src/packagenamepy/home/home_model.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Model for the Main tab""" - -from mantid.kernel import Logger - -logger = Logger("PACKAGENAME") - - -class HomeModel: # pylint: disable=too-many-public-methods - """Main model""" - - def __init__(self): - return diff --git a/src/packagenamepy/home/home_presenter.py b/src/packagenamepy/home/home_presenter.py deleted file mode 100644 index 1b55b34..0000000 --- a/src/packagenamepy/home/home_presenter.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Presenter for the Main tab""" - - -class HomePresenter: # pylint: disable=too-many-public-methods - """Main presenter""" - - def __init__(self, view, model): - self._view = view - self._model = model - - @property - def view(self): - """Return the view for this presenter""" - return self._view - - @property - def model(self): - """Return the model for this presenter""" - return self._model diff --git a/src/packagenamepy/home/home_view.py b/src/packagenamepy/home/home_view.py deleted file mode 100644 index 61a8e71..0000000 --- a/src/packagenamepy/home/home_view.py +++ /dev/null @@ -1,13 +0,0 @@ -"""PyQt widget for the main tab""" - -from qtpy.QtWidgets import QHBoxLayout, QWidget - - -class Home(QWidget): # pylint: disable=too-many-public-methods - """Main widget""" - - def __init__(self, parent=None): - super().__init__(parent) - - layout = QHBoxLayout() - self.setLayout(layout) diff --git a/src/packagenamepy/mainwindow.py b/src/packagenamepy/mainwindow.py deleted file mode 100644 index 16c8aec..0000000 --- a/src/packagenamepy/mainwindow.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Main Qt window""" - -from qtpy.QtWidgets import QHBoxLayout, QPushButton, QTabWidget, QVBoxLayout, QWidget - -from packagenamepy.help.help_model import help_function -from packagenamepy.home.home_model import HomeModel -from packagenamepy.home.home_presenter import HomePresenter -from packagenamepy.home.home_view import Home - - -class MainWindow(QWidget): - """Main widget""" - - def __init__(self, parent=None): - super().__init__(parent) - - ### Create tabs here ### - - ### Main tab - self.tabs = QTabWidget() - home = Home(self) - home_model = HomeModel() - self.home_presenter = HomePresenter(home, home_model) - self.tabs.addTab(home, "Home") - - ### Set tab layout - layout = QVBoxLayout() - layout.addWidget(self.tabs) - - ### Create bottom interface here ### - - # Help button - help_button = QPushButton("Help") - help_button.clicked.connect(self.handle_help) - - # Set bottom interface layout - hor_layout = QHBoxLayout() - hor_layout.addWidget(help_button) - - layout.addLayout(hor_layout) - - self.setLayout(layout) - - # register child widgets to make testing easier - self.home = home - - def handle_help(self): - """Get current tab type and open the corresponding help page""" - open_tab = self.tabs.currentWidget() - if isinstance(open_tab, Home): - context = "home" - else: - context = "" - help_function(context=context) diff --git a/src/packagenamepy/packagename.py b/src/packagenamepy/packagename.py deleted file mode 100644 index 15db777..0000000 --- a/src/packagenamepy/packagename.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Main Qt application""" - -import sys - -from mantid.kernel import Logger -from mantidqt.gui_helper import set_matplotlib_backend -from qtpy.QtWidgets import QApplication, QMainWindow - -# make sure matplotlib is correctly set before we import shiver -set_matplotlib_backend() - -# make sure the algorithms have been loaded so they are available to the AlgorithmManager -import mantid.simpleapi # noqa: F401, E402 pylint: disable=unused-import, wrong-import-position - -from packagenamepy import __version__ # noqa: E402 pylint: disable=wrong-import-position -from packagenamepy.configuration import Configuration # noqa: E402 pylint: disable=wrong-import-position -from packagenamepy.mainwindow import MainWindow # noqa: E402 pylint: disable=wrong-import-position - -logger = Logger("PACKAGENAME") - - -class PackageName(QMainWindow): - """Main Package window""" - - __instance = None - - def __new__(cls): - if PackageName.__instance is None: - PackageName.__instance = QMainWindow.__new__(cls) # pylint: disable=no-value-for-parameter - return PackageName.__instance - - def __init__(self, parent=None): - super().__init__(parent) - logger.information(f"PackageName version: {__version__}") - config = Configuration() - - if not config.is_valid(): - msg = ( - "Error with configuration settings!", - f"Check and update your file: {config.config_file_path}", - "with the latest settings found here:", - f"{config.template_file_path} and start the application again.", - ) - - print(" ".join(msg)) - sys.exit(-1) - self.setWindowTitle(f"PACKAGENAME - {__version__}") - self.main_window = MainWindow(self) - self.setCentralWidget(self.main_window) - - -def gui(): - """Main entry point for Qt application""" - input_flags = sys.argv[1::] - if "--v" in input_flags or "--version" in input_flags: - print(__version__) - sys.exit() - else: - app = QApplication(sys.argv) - window = PackageName() - window.show() - sys.exit(app.exec_()) diff --git a/src/refl1d_models/__init__.py b/src/refl1d_models/__init__.py new file mode 100644 index 0000000..5e1b623 --- /dev/null +++ b/src/refl1d_models/__init__.py @@ -0,0 +1,6 @@ +"""Contains the entry point for the application""" + +try: + from ._version import __version__ # noqa: F401 +except ImportError: + __version__ = "unknown"