diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 3361601..b77b704 100755 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/nemreader/cli.py b/nemreader/cli.py index 8fdd950..2092b8d 100644 --- a/nemreader/cli.py +++ b/nemreader/cli.py @@ -1,7 +1,6 @@ import logging import os from pathlib import Path -from typing import Optional import typer @@ -12,6 +11,13 @@ LOG_FORMAT = "%(asctime)s %(levelname)-8s %(message)s" app = typer.Typer() DEFAULT_DIR = Path(".") +DEFAULT_DIR_OPTION = typer.Option( + DEFAULT_DIR, + exists=True, + file_okay=False, + dir_okay=True, + writable=True, +) def version_callback(value: bool): @@ -22,9 +28,7 @@ def version_callback(value: bool): @app.callback() def callback( - version: Optional[bool] = typer.Option( - None, "--version", callback=version_callback - ), + version: bool = typer.Option(False, "--version", callback=version_callback), ) -> None: """nemreader @@ -34,9 +38,7 @@ def callback( @app.command() -def list_nmis( - nemfile: Path, verbose: bool = typer.Option(False, "--verbose", "-v") -) -> None: +def list_nmis(nemfile: Path, verbose: bool = False) -> None: log_level = "DEBUG" if verbose else "WARNING" logging.basicConfig(level=log_level, format=LOG_FORMAT) @@ -50,15 +52,9 @@ def list_nmis( @app.command() def output_csv( nemfile: Path, - verbose: bool = typer.Option(False, "--verbose", "-v"), - set_interval: Optional[int] = None, - outdir: Path = typer.Option( - DEFAULT_DIR, - exists=True, - file_okay=False, - dir_okay=True, - writable=True, - ), + verbose: bool = False, + set_interval: int = 0, + outdir: Path = DEFAULT_DIR_OPTION, ) -> None: """Output NEM file to transposed CSV. @@ -72,15 +68,7 @@ def output_csv( @app.command() def output_csv_daily( - nemfile: Path, - verbose: bool = typer.Option(False, "--verbose", "-v"), - outdir: Path = typer.Option( - DEFAULT_DIR, - exists=True, - file_okay=False, - dir_okay=True, - writable=True, - ), + nemfile: Path, verbose: bool = False, outdir: Path = DEFAULT_DIR_OPTION ) -> None: """Output NEM file to transposed CSV. @@ -95,16 +83,10 @@ def output_csv_daily( @app.command() def output_sqlite( nemfile: Path, - outdir: Path = typer.Option( - DEFAULT_DIR, - exists=True, - file_okay=True, - dir_okay=True, - writable=True, - ), + outdir: Path = DEFAULT_DIR_OPTION, output_file: str = "nemdata.db", - set_interval: Optional[int] = None, - verbose: bool = typer.Option(False, "--verbose", "-v"), + set_interval: int | None = None, + verbose: bool = False, ) -> None: """Output NEM file to SQLite DB. diff --git a/nemreader/nem_objects.py b/nemreader/nem_objects.py index 39c684c..3452e47 100644 --- a/nemreader/nem_objects.py +++ b/nemreader/nem_objects.py @@ -1,12 +1,12 @@ from datetime import datetime -from typing import NamedTuple, Optional +from typing import NamedTuple class HeaderRecord(NamedTuple): """Header record (100)""" version_header: str - creation_date: Optional[datetime] + creation_date: datetime | None from_participant: str to_participant: str file_name: str @@ -24,7 +24,7 @@ class NmiDetails(NamedTuple): meter_serial_number: str uom: str interval_length: int - next_scheduled_read_date: Optional[datetime] + next_scheduled_read_date: datetime | None class Reading(NamedTuple): @@ -35,12 +35,12 @@ class Reading(NamedTuple): read_value: float uom: str meter_serial_number: str - quality_method: Optional[str] - event_code: Optional[str] - event_desc: Optional[str] + quality_method: str | None + event_code: str | None + event_desc: str | None # Below attributes relevant for NEM13 only - val_start: Optional[float] - val_end: Optional[float] + val_start: float | None + val_end: float | None class BasicMeterData(NamedTuple): @@ -54,33 +54,33 @@ class BasicMeterData(NamedTuple): meter_serial_number: str direction_indicator: str previous_register_read: str - previous_register_read_datetime: Optional[datetime] + previous_register_read_datetime: datetime | None previous_quality_method: str previous_reason_code: int previous_reason_description: str current_register_read: str - current_register_read_datetime: Optional[datetime] + current_register_read_datetime: datetime | None current_quality_method: str current_reason_code: int current_reason_description: str quantity: float uom: str - next_scheduled_read_date: Optional[datetime] - update_datetime: Optional[datetime] - msats_load_datetime: Optional[datetime] + next_scheduled_read_date: datetime | None + update_datetime: datetime | None + msats_load_datetime: datetime | None class IntervalRecord(NamedTuple): """Interval data record (300)""" - interval_date: Optional[datetime] + interval_date: datetime | None interval_values: list[Reading] quality_method: str meter_serial_number: str reason_code: str reason_description: str - update_datetime: Optional[datetime] - msats_load_datatime: Optional[datetime] + update_datetime: datetime | None + msats_load_datatime: datetime | None class EventRecord(NamedTuple): @@ -98,7 +98,7 @@ class B2BDetails12(NamedTuple): trans_code: str ret_service_order: str - read_datetime: Optional[datetime] + read_datetime: datetime | None index_read: str diff --git a/nemreader/nem_reader.py b/nemreader/nem_reader.py index cd65652..082ef8e 100644 --- a/nemreader/nem_reader.py +++ b/nemreader/nem_reader.py @@ -5,7 +5,7 @@ from collections.abc import Generator, Iterable from datetime import datetime, timedelta from itertools import chain, islice -from typing import Any, Optional +from typing import Any import pandas as pd @@ -131,8 +131,8 @@ def nem_data(self) -> NEMData: ) def get_data_frame( - self, split_days: bool = False, set_interval: Optional[int] = None - ) -> Optional[pd.DataFrame]: + self, split_days: bool = False, set_interval: int = 0 + ) -> pd.DataFrame | None: """Return NEMData as a DataFrame""" nd = self.nem_data() frames = [] @@ -165,9 +165,9 @@ def get_data_frame( def get_pivot_data_frame( self, split_days: bool = False, - set_interval: Optional[int] = None, + set_interval: int = 0, include_serno: bool = False, - ) -> Optional[pd.DataFrame]: + ) -> pd.DataFrame | None: """Return NEMData as a DataFrame with suffix columns""" df = self.get_data_frame(split_days, set_interval) if df is None: @@ -224,7 +224,7 @@ def get_pivot_data_frame( def get_per_nmi_dfs( self, split_days: bool = False, - set_interval: Optional[int] = None, + set_interval: int | None = None, include_serno: bool = False, ) -> Generator[tuple[str, pd.DataFrame], None, None]: df = self.get_pivot_data_frame(split_days, set_interval, include_serno) @@ -579,7 +579,7 @@ def parse_interval_records( ] -def parse_reading(val: str) -> Optional[float]: +def parse_reading(val: str) -> float | None: """Convert reading value to float (if possible)""" if val == "": return None @@ -656,7 +656,7 @@ def parse_550_row(row: list) -> tuple: return B2BDetails13(row[1], row[2], row[3], row[4]) -def parse_datetime(record: str) -> Optional[datetime]: +def parse_datetime(record: str) -> datetime | None: """Parse a datetime string into a python datetime object""" # NEM defines Date8, DateTime12 and DateTime14 format_strings = {8: "%Y%m%d", 12: "%Y%m%d%H%M", 14: "%Y%m%d%H%M%S"} diff --git a/nemreader/output_db.py b/nemreader/output_db.py index fcc9b17..51cae11 100644 --- a/nemreader/output_db.py +++ b/nemreader/output_db.py @@ -3,7 +3,7 @@ from collections import defaultdict from datetime import datetime from pathlib import Path -from typing import NamedTuple, Optional +from typing import NamedTuple from dateutil.parser import isoparse from sqlite_utils import Database @@ -19,7 +19,7 @@ def output_as_sqlite( output_dir: str = ".", output_file: str = "nemdata.db", split_days: bool = False, - set_interval: Optional[int] = None, + set_interval: int | None = None, replace: bool = False, ) -> Path: """Export all channels to sqlite file""" @@ -84,7 +84,7 @@ def output_folder_as_sqlite( output_dir: str = ".", output_file: str = "nemdata.db", split_days: bool = False, - set_interval: Optional[int] = None, + set_interval: int | None = None, replace: bool = False, skip_errors: bool = False, ) -> Path: diff --git a/nemreader/outputs.py b/nemreader/outputs.py index 2b35571..151aed2 100644 --- a/nemreader/outputs.py +++ b/nemreader/outputs.py @@ -3,7 +3,7 @@ import os from collections.abc import Generator from pathlib import Path -from typing import Any, Optional +from typing import Any import pandas as pd @@ -24,7 +24,7 @@ def nmis_in_file(file_name) -> Generator[tuple[str, list[str]], None, None]: def output_as_data_frames( file_name, split_days: bool = True, - set_interval: Optional[int] = None, + set_interval: int | None = None, strict: bool = False, ) -> list[tuple[str, pd.DataFrame]]: """Return list of data frames for each NMI""" @@ -38,7 +38,7 @@ def output_as_data_frames( return data_frames -def output_as_csv(file_name, output_dir=".", set_interval: Optional[int] = None): +def output_as_csv(file_name, output_dir=".", set_interval: int = 0): """ Transpose all channels and output a csv that is easier to read and do charting on diff --git a/nemreader/version.py b/nemreader/version.py index d69d16e..a2fecb4 100644 --- a/nemreader/version.py +++ b/nemreader/version.py @@ -1 +1 @@ -__version__ = "0.9.1" +__version__ = "0.9.2" diff --git a/pyproject.toml b/pyproject.toml index 00a4275..07b555e 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,13 +10,13 @@ license = { file = "LICENSE" } classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.9", "Operating System :: OS Independent", ] keywords = ["energy", "NEM12", "NEM13"] -requires-python = ">=3.9" +requires-python = ">=3.10" dynamic = ["version", "description"] dependencies = ["pandas", "sqlite_utils", "typer"] @@ -30,9 +30,6 @@ Documentation = "https://nem-reader.readthedocs.io/en/latest/" [project.scripts] nemreader = "nemreader.cli:app" -[tool.isort] -profile = "black" - [tool.pytest.ini_options] addopts = "-ra --failed-first --showlocals --durations=3 --cov=nemreader"