From e6c7ea07a52b4b3ae605b5f526609803d93db7b7 Mon Sep 17 00:00:00 2001 From: Ross Whitfield Date: Mon, 2 Dec 2024 11:20:54 +1100 Subject: [PATCH] Add ArtemisDataCollector --- .github/workflows/package.yml | 73 ------ .github/workflows/unittest.yml | 14 +- environment.yml | 34 +-- notebooks/example.ipynb | 27 --- pyproject.toml | 22 +- scripts/myscripts.py | 2 - .../artemis_data_collector.py | 220 ++++++++++++++++++ .../sql/report_statusqueue.sql | 90 +++++++ .../sql/report_statusqueuemessagecount.sql | 91 ++++++++ src/packagenamepy/__init__.py | 13 -- src/packagenamepy/configuration.py | 105 --------- src/packagenamepy/configuration_template.ini | 2 - src/packagenamepy/help/help_model.py | 12 - src/packagenamepy/home/home_model.py | 12 - src/packagenamepy/home/home_presenter.py | 19 -- src/packagenamepy/home/home_view.py | 13 -- src/packagenamepy/mainwindow.py | 54 ----- src/packagenamepy/packagename.py | 62 ----- tests/test_version.py | 4 +- 19 files changed, 418 insertions(+), 451 deletions(-) delete mode 100644 .github/workflows/package.yml delete mode 100644 notebooks/example.ipynb delete mode 100644 scripts/myscripts.py create mode 100644 src/artemis_data_collector/artemis_data_collector.py create mode 100644 src/artemis_data_collector/sql/report_statusqueue.sql create mode 100644 src/artemis_data_collector/sql/report_statusqueuemessagecount.sql delete mode 100644 src/packagenamepy/__init__.py delete mode 100644 src/packagenamepy/configuration.py delete mode 100644 src/packagenamepy/configuration_template.ini delete mode 100644 src/packagenamepy/help/help_model.py delete mode 100644 src/packagenamepy/home/home_model.py delete mode 100644 src/packagenamepy/home/home_presenter.py delete mode 100644 src/packagenamepy/home/home_view.py delete mode 100644 src/packagenamepy/mainwindow.py delete mode 100644 src/packagenamepy/packagename.py diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml deleted file mode 100644 index f20761e..0000000 --- a/.github/workflows/package.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: conda packaging and deployment - -on: - workflow_dispatch: - push: - branches: [qa, main] - tags: ['v*'] - -jobs: - linux: - runs-on: ubuntu-latest - defaults: - run: - shell: bash -l {0} - steps: - - uses: actions/checkout@v4 - - uses: conda-incubator/setup-miniconda@v3 - with: - auto-update-conda: true - channels: conda-forge,defaults - use-mamba: true - environment-file: environment.yml - activate-environment: test - - name: install additional dependencies - run: | - echo "installing additional dependencies from environment_development.yml" - - name: build conda package - run: | - # set up environment - cd conda.recipe - echo "versioningit $(versioningit ../)" - # build the package - VERSION=$(versioningit ../) conda mambabuild --channel conda-forge --output-folder . . - conda verify noarch/mypackagename*.tar.bz2 - - name: upload conda package to anaconda - shell: bash -l {0} - if: startsWith(github.ref, 'refs/tags/v') - env: - ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_TOKEN }} - IS_RC: ${{ contains(github.ref, 'rc') }} - run: | - # label is main or rc depending on the tag-name - 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 - - pypi-publish: - name: upload release to PyPI - runs-on: ubuntu-latest - defaults: - run: - shell: bash -l {0} - permissions: - # IMPORTANT: this permission is mandatory for trusted publishing - id-token: write - steps: - - uses: actions/checkout@v4 - - uses: conda-incubator/setup-miniconda@v3 - with: - auto-update-conda: true - channels: conda-forge,defaults - use-mamba: true - environment-file: environment.yml - activate-environment: test - - name: build pypi package - run: | - # build the package - VERSION=$(versioningit .) python -m build - # publish your distributions here (need to setup on PyPI first) - - name: Publish package distributions to PyPI - if: startsWith(github.ref, 'refs/tags/v') - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index f76ee42..c78bfca 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -22,9 +22,9 @@ jobs: use-mamba: true environment-file: environment.yml activate-environment: test - - name: install additional dependencies + - name: install run: | - echo "installing additional dependencies if cannot be installed from conda" + python -m pip install -e . - name: run unit tests run: | echo "running unit tests" @@ -35,13 +35,3 @@ jobs: github.actor != 'dependabot[bot]' with: token: ${{ secrets.CODECOV_TOKEN }} - - name: build conda package - run: | - # test that the conda package builds - cd conda.recipe - echo "versioningit $(versioningit ../)" - # conda channels could have been defined in the conda-incubator, but you can copy/paste the lines - # below to build the conda package in your local machine - CHANNELS="--channel mantid/label/main --channel conda-forge" - VERSION=$(versioningit ../) conda mambabuild $CHANNELS --output-folder . . - conda verify noarch/mypackagename*.tar.bz2 diff --git a/environment.yml b/environment.yml index 2a1abfd..66afad4 100644 --- a/environment.yml +++ b/environment.yml @@ -1,44 +1,20 @@ -name: mypythonapp +name: artemis_data_collector channels: - conda-forge dependencies: # -- Runtime dependencies # base: list all base dependencies here - - python>=3.8 # please specify the minimum version of python here + - python>=3.12 # please specify the minimum version of python here - versioningit # compute: list all compute dependencies here - - numpy - - pandas - # plot: list all plot dependencies here, if applicable - - matplotlib - # jupyter: list all jupyter dependencies here, if applicable - - jupyterlab - - ipympl + - psycopg>=3.2 + - requests + - stomp.py # -- Development dependencies # utils: - pre-commit # package building: - - libmamba - - libarchive - - anaconda-client - - boa - - conda-build < 4 # conda-build 24.x has a bug, missing update_index from conda_build.index - - conda-verify - python-build - - twine # for uploading to pypi and testpypi - # docs - - sphinx - - sphinx_rtd_theme - - myst-parser # required for parsing markdown files # test: list all test dependencies here - pytest - pytest-cov - - pytest-xdist - # -------------------------------------------------- - # add additional sections such as Qt, etc. if needed - # -------------------------------------------------- - # if packages are not available on conda, list them here - - pip - - pip: - - bm3d-streak-removal # example - - pytest-playwright diff --git a/notebooks/example.ipynb b/notebooks/example.ipynb deleted file mode 100644 index 7fa8701..0000000 --- a/notebooks/example.ipynb +++ /dev/null @@ -1,27 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Overview\n", - "\n", - "This folder is used to store notebooks that demonstrate how to use the library in an interactive environment like Jupyter." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pyproject.toml b/pyproject.toml index 3943d8b..c3aad9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,8 @@ [project] -name = "examplepyapp" -description = "Example Python repo for neutrons" +name = "artemis_data_collector" +description = "Artemis Data Collector" dynamic = ["version"] -requires-python = ">=3.10" +requires-python = ">=3.12" dependencies = [ # list all runtime dependencies here ] @@ -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/neutrons/artemis_data_collector/" # if no homepage, use repo url +repository = "https://github.com/neutrons/artemis_data_collector/" # documentation = add_url_to_readthedoc_here -issues = "https://github.com/neutrons/python_project_template/issues" +issues = "https://github.com/neutrons/artemis_data_collector/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/artemis_data_collector/_version.py" [tool.setuptools.packages.find] where = ["src"] @@ -48,10 +48,7 @@ exclude = ["tests*", "scripts*", "docs*", "notebooks*"] "*" = ["*.yml","*.yaml","*.ini"] [project.scripts] -packagename-cli = "packagenamepy.packagename:main" - -[project.gui-scripts] -packagenamepy = "packagenamepy.packagename:gui" +artemis_data_collector = "artemis_data_collector.artemis_data_collector:main" [tool.pytest.ini_options] pythonpath = [ @@ -60,9 +57,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/scripts/myscripts.py b/scripts/myscripts.py deleted file mode 100644 index fce7406..0000000 --- a/scripts/myscripts.py +++ /dev/null @@ -1,2 +0,0 @@ -"""This is a script that use the package as a module.""" -#!/usr/bin/env python diff --git a/src/artemis_data_collector/artemis_data_collector.py b/src/artemis_data_collector/artemis_data_collector.py new file mode 100644 index 0000000..d4ec7cb --- /dev/null +++ b/src/artemis_data_collector/artemis_data_collector.py @@ -0,0 +1,220 @@ +import argparse +import logging +import sys +import time +from importlib.resources import files + +import psycopg +import requests + +logger = logging.getLogger("AtremisDataCollector") + + +def initialize_database_tables(db_hostname, db_port, db_user, db_password, db_name): + """Initializes the tables in the database from sql files. This will fail if the tables already exist. + + WebMon should have already created the tables so this is mostly for testing.""" + logger.info("Initializing tables") + with psycopg.connect(dbname=db_name, host=db_hostname, port=db_port, user=db_user, password=db_password) as conn: + with conn.cursor() as cur: + cur.execute(files("artemis_data_collector.sql").joinpath("report_statusqueue.sql").read_text()) + conn.commit() + cur.execute(files("artemis_data_collector.sql").joinpath("report_statusqueuemessagecount.sql").read_text()) + conn.commit() + + +class ArtemisDataCollector: + def __init__(self, config): + logger.info("Initializing ArtemisDataCollector") + self.config = config + self._conn = None + + # common session for all requests + self._session = self._session = requests.Session() + self._session.auth = (self.config.artemis_user, self.config.artemis_password) + self._session.headers.update({"Origin": "localhost"}) + + self.base_url = f"{self.config.artemis_url}/console/jolokia/read/org.apache.activemq.artemis:broker=%22{self.config.broker_name}%22" # noqa: E501 + + database_statusqueues = self.get_database_statusqueues() + amq_queues = self.get_activemq_queues() + if amq_queues is None: + raise ValueError("Failed to get queues from ActiveMQ Artemis") + + # validate requested queues exist in database and activemq. + # If queue_list is not specified, monitor all queues from the database + queue_list = self.config.queue_list if self.config.queue_list is not None else database_statusqueues.keys() + + self.monitored_queue = {} + for queue in queue_list: + if queue not in database_statusqueues: + logger.error(f"Queue {queue} not found in database, skipping") + elif queue not in amq_queues: + logger.error(f"Queue {queue} not found in ActiveMQ Artemis, skipping") + else: + self.monitored_queue[queue] = database_statusqueues[queue] + + if not self.monitored_queue: + raise ValueError("No queues to monitor") + + logger.info(f"Monitoring queues: {" ".join(self.monitored_queue.keys())}") + + @property + def conn(self): + """Connect to the database if not already connected""" + logger.debug("Getting database connection") + if self._conn is None or self._conn.closed: + logger.debug("Connecting to database %s at %s", self.config.database_name, self.config.database_hostname) + self._conn = psycopg.connect( + dbname=self.config.database_name, + host=self.config.database_hostname, + port=self.config.database_port, + user=self.config.database_user, + password=self.config.database_password, + ) + return self._conn + + @property + def session(self): + return self._session + + def run(self): + """Main loop to collect data and add to database""" + while True: + data = self.collect_data() + if data is not None: + self.add_to_database(data) + time.sleep(self.config.interval) + + def request_activemq(self, query): + """Make a request to ActiveMQ Artemis Jolokia API""" + try: + response = self.session.get(self.base_url + query) + except requests.exceptions.Timeout as e: + logger.error(f"Timeout: {e}") + return None + except requests.exceptions.RequestException as e: + logger.error(f"Error: {e}") + return None + + if response.status_code != 200: + logger.error(f"Error: {response.text}") + return None + + try: + if response.json()["status"] != 200: + logger.error(f"Error: {response.json()}") + return None + except requests.exceptions.JSONDecodeError: + logger.error(f"JSON decode Error: {response.text}") + return None + + return response.json()["value"] + + def get_activemq_queues(self): + """Returns a list of queues from the Artemis""" + return self.request_activemq("/AddressNames") + + def collect_data(self): + # get all queue lengths in one call + values = self.request_activemq(",address=%22*%22,component=addresses/MessageCount,Address") + if values is None: + return None + + queue_message_counts = [] + + for counts in values.values(): + if counts["Address"] in self.monitored_queue: + queue_message_counts.append( + ( + self.monitored_queue[counts["Address"]], + counts["MessageCount"], + ) + ) + + if queue_message_counts: + logger.info(f"Successfully collected data for {len(queue_message_counts)} queues") + return queue_message_counts + + def add_to_database(self, data): + try: + with self.conn.cursor() as cur: + cur.executemany( + "INSERT INTO report_statusqueuemessagecount (queue_id, message_count, created_on) VALUES(%s,%s, now())", # noqa: E501 + data, + ) + self.conn.commit() + except psycopg.errors.DatabaseError as e: + # We want to catch any database errors and log them but continue running + logger.error(e) + else: + logger.info("Successfully added records to the database") + + def get_database_statusqueues(self): + """Returns maps of status queues to id from the database""" + with self.conn.cursor() as cur: + cur.execute("SELECT id, name FROM report_statusqueue") + queues = cur.fetchall() + + # make map from name to id + queue_map = {} + for queue in queues: + queue_map[queue[1]] = queue[0] + + return queue_map + + +def main(): + parser = argparse.ArgumentParser(description="Collect data from Artemis") + parser.add_argument("--version", action="version", version="%(prog)s 1.0") + parser.add_argument( + "--initialize_db", + action="store_true", + help="Initialize the database table and exit. Will fail if tables already exist", + ) + parser.add_argument("--artemis_url", default="http://localhost:8161", help="URL of the Artemis instance") + parser.add_argument("--artemis_user", default="artemis", help="User of the Artemis instance") + parser.add_argument("--artemis_password", default="artemis", help="Password of the Artemis instance") + parser.add_argument("--broker_name", default="0.0.0.0", help="Name of the Artemis broker") + parser.add_argument("--database_hostname", default="localhost", help="Hostname of the database") + parser.add_argument("--database_port", type=int, default=5432, help="Port of the database") + parser.add_argument("--database_user", default="workflow", help="User of the database") + parser.add_argument("--database_password", default="workflow", help="Password of the database") + parser.add_argument("--database_name", default="workflow", help="Name of the database") + parser.add_argument( + "--queue_list", nargs="*", help="List of queues to monitor. If not specified, monitor all queues from database" + ) + parser.add_argument("--interval", type=int, default=600, help="Interval to collect data (seconds)") + parser.add_argument("--log_level", default="INFO", help="Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)") + parser.add_argument("--log_file", help="Log file. If not specified, log to stdout") + config = parser.parse_args() + + # setup logging + logging.basicConfig( + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=config.log_level, filename=config.log_file + ) + + try: + if config.initialize_db: + initialize_database_tables( + config.database_hostname, + config.database_port, + config.database_user, + config.database_password, + config.database_name, + ) + return 0 + + adc = ArtemisDataCollector(config) + adc.run() + except KeyboardInterrupt: + logger.info("Exiting") + return 0 + except Exception as e: + # catch any unhandled exception and log it before exiting + logger.exception(f"Error: {e}") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/artemis_data_collector/sql/report_statusqueue.sql b/src/artemis_data_collector/sql/report_statusqueue.sql new file mode 100644 index 0000000..7b62acb --- /dev/null +++ b/src/artemis_data_collector/sql/report_statusqueue.sql @@ -0,0 +1,90 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 14.12 (Debian 14.12-1.pgdg120+1) +-- Dumped by pg_dump version 14.5 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: report_statusqueue; Type: TABLE; Schema: public; Owner: workflow +-- + +CREATE TABLE public.report_statusqueue ( + id integer NOT NULL, + name character varying(100) NOT NULL, + is_workflow_input boolean NOT NULL +); + + +ALTER TABLE public.report_statusqueue OWNER TO workflow; + +-- +-- Name: report_statusqueue_id_seq; Type: SEQUENCE; Schema: public; Owner: workflow +-- + +CREATE SEQUENCE public.report_statusqueue_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.report_statusqueue_id_seq OWNER TO workflow; + +-- +-- Name: report_statusqueue_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: workflow +-- + +ALTER SEQUENCE public.report_statusqueue_id_seq OWNED BY public.report_statusqueue.id; + + +-- +-- Name: report_statusqueue id; Type: DEFAULT; Schema: public; Owner: workflow +-- + +ALTER TABLE ONLY public.report_statusqueue ALTER COLUMN id SET DEFAULT nextval('public.report_statusqueue_id_seq'::regclass); + + +-- +-- Name: report_statusqueue report_statusqueue_name_key; Type: CONSTRAINT; Schema: public; Owner: workflow +-- + +ALTER TABLE ONLY public.report_statusqueue + ADD CONSTRAINT report_statusqueue_name_key UNIQUE (name); + + +-- +-- Name: report_statusqueue report_statusqueue_pkey; Type: CONSTRAINT; Schema: public; Owner: workflow +-- + +ALTER TABLE ONLY public.report_statusqueue + ADD CONSTRAINT report_statusqueue_pkey PRIMARY KEY (id); + + +-- +-- Name: report_statusqueue_name_acb47977_like; Type: INDEX; Schema: public; Owner: workflow +-- + +CREATE INDEX report_statusqueue_name_acb47977_like ON public.report_statusqueue USING btree (name varchar_pattern_ops); + + +-- +-- PostgreSQL database dump complete +-- diff --git a/src/artemis_data_collector/sql/report_statusqueuemessagecount.sql b/src/artemis_data_collector/sql/report_statusqueuemessagecount.sql new file mode 100644 index 0000000..70f555f --- /dev/null +++ b/src/artemis_data_collector/sql/report_statusqueuemessagecount.sql @@ -0,0 +1,91 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 14.12 (Debian 14.12-1.pgdg120+1) +-- Dumped by pg_dump version 14.5 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: report_statusqueuemessagecount; Type: TABLE; Schema: public; Owner: workflow +-- + +CREATE TABLE public.report_statusqueuemessagecount ( + id integer NOT NULL, + queue_id integer NOT NULL, + message_count integer NOT NULL, + created_on timestamp with time zone NOT NULL +); + + +ALTER TABLE public.report_statusqueuemessagecount OWNER TO workflow; + +-- +-- Name: report_statusqueuemessagecount_id_seq; Type: SEQUENCE; Schema: public; Owner: workflow +-- + +CREATE SEQUENCE public.report_statusqueuemessagecount_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.report_statusqueuemessagecount_id_seq OWNER TO workflow; + +-- +-- Name: report_statusqueuemessagecount_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: workflow +-- + +ALTER SEQUENCE public.report_statusqueuemessagecount_id_seq OWNED BY public.report_statusqueuemessagecount.id; + + +-- +-- Name: report_statusqueuemessagecount id; Type: DEFAULT; Schema: public; Owner: workflow +-- + +ALTER TABLE ONLY public.report_statusqueuemessagecount ALTER COLUMN id SET DEFAULT nextval('public.report_statusqueuemessagecount_id_seq'::regclass); + + +-- +-- Name: report_statusqueuemessagecount report_statusqueuemessagecount_pkey; Type: CONSTRAINT; Schema: public; Owner: workflow +-- + +ALTER TABLE ONLY public.report_statusqueuemessagecount + ADD CONSTRAINT report_statusqueuemessagecount_pkey PRIMARY KEY (id); + + +-- +-- Name: report_statusqueuemessagecount_queue_id_6b0ea71c; Type: INDEX; Schema: public; Owner: workflow +-- + +CREATE INDEX report_statusqueuemessagecount_queue_id_6b0ea71c ON public.report_statusqueuemessagecount USING btree (queue_id); + + +-- +-- Name: report_statusqueuemessagecount report_statusqueueme_queue_id_6b0ea71c_fk_report_st; Type: FK CONSTRAINT; Schema: public; Owner: workflow +-- + +ALTER TABLE ONLY public.report_statusqueuemessagecount + ADD CONSTRAINT report_statusqueueme_queue_id_6b0ea71c_fk_report_st FOREIGN KEY (queue_id) REFERENCES public.report_statusqueue(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- PostgreSQL database dump complete +-- 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/tests/test_version.py b/tests/test_version.py index 5b09eca..2c15277 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,5 +1,5 @@ -from packagenamepy import __version__ +from artemis_data_collector import __version__ def test_version(): - assert __version__ == "unknown" + assert __version__