diff --git a/.archive/old_version/.gitignore b/.archive/old_version/.gitignore deleted file mode 100644 index 5d381cc..0000000 --- a/.archive/old_version/.gitignore +++ /dev/null @@ -1,162 +0,0 @@ -# ---> Python -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - diff --git a/.archive/old_version/README.md b/.archive/old_version/README.md deleted file mode 100644 index ab7a363..0000000 --- a/.archive/old_version/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# IndustrialEnergyTracker - -Periodically read values of air and energy consumption from Siemens PLC and PACs meters. -Yes \ No newline at end of file diff --git a/.archive/old_version/pyS7_test.py b/.archive/old_version/pyS7_test.py deleted file mode 100644 index 8e4f057..0000000 --- a/.archive/old_version/pyS7_test.py +++ /dev/null @@ -1,20 +0,0 @@ -import pprint -import time - -from pyS7 import S7Client - -LOOPS = 3 -counter = 0 -client = S7Client(address="172.16.3.231", rack=0, slot=2) -while counter < LOOPS: - try: - client.connect() - tags = ["DB9,DBD0"] - data = client.read(tags=tags) - print(data) - except Exception as e: - pprint.pprint(e) - finally: - client.disconnect() - counter += 1 - time.sleep(1) diff --git a/.archive/old_version/snap7_test.py b/.archive/old_version/snap7_test.py deleted file mode 100644 index f02ccca..0000000 --- a/.archive/old_version/snap7_test.py +++ /dev/null @@ -1,23 +0,0 @@ -import pprint -import time - -import snap7 -from snap7.util.getters import get_dint, get_lreal - -LOOPS = 20 -counter = 0 -while counter < LOOPS: - try: - plc = snap7.client.Client() - plc.connect("172.16.3.231", 0, 2) # fagor 6 - plc.connect("172.16.3.230", 0, 2) # fagor 5 - # pprint.pprint(plc.get_cpu_state()) - data = plc.db_read(9, 0, 4) # Define range of bytes to read - energy_value = get_dint(data, 0) # Read energy value - pprint.pprint(energy_value) - except Exception as e: - pprint.pprint(e) - finally: - plc.disconnect() - counter += 1 - time.sleep(3) diff --git a/README.md b/README.md new file mode 100644 index 0000000..1cb116b --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# IndustrialEnergyTracker + +Industrial energy and resource tracking system for PLCs and PACs. + +## Getting Started + +### Prerequisites +- `Python 3.12` or higher +- `uv` package manager + +### Installation + +1. Clone the repository + `git clone gitea.bigoscloud.com/biggy/IndustrialEnergyTracker` + +2. Create and activate virtual environment + `uv venv && source .venv/bin/activate` + +3. Install project dependencies + `uv pip install -e .` + +4. Install development dependencies (optional) + `uv pip install -e ".[dev]"` + +### Configuration + +1. Create `config.ini` file based on template +2. Configure database connections and other settings + +### Development + +Update dependencies to latest compatible versions: + `uv pip compile pyproject.toml` + +## Features + +- Multi-database support (MSSQL, PostgreSQL) +- PLC/PAC communication +- Scheduled data collection +- Energy and resource tracking +- Configurable via database + +## Project Structure + +- `src/` - Source code +- `main.py` - Application entry point +- `scheduler.py` - Scheduling service diff --git a/chat-memory.md b/chat-memory.md deleted file mode 100644 index e69de29..0000000 diff --git a/.archive/old_version/energy_monitor.py b/legacy/energy_monitor.py similarity index 83% rename from .archive/old_version/energy_monitor.py rename to legacy/energy_monitor.py index 76fa581..b09e7f2 100644 --- a/.archive/old_version/energy_monitor.py +++ b/legacy/energy_monitor.py @@ -1,135 +1,144 @@ -import os -import platform -import pprint -import socket -from dataclasses import dataclass -from datetime import datetime, timedelta -from typing import List, Tuple - -import pyodbc -import snap7 -from dotenv import load_dotenv -from snap7.util.getters import get_bool, get_lreal - -load_dotenv() -# Determine the correct driver based on OS -SQL_DRIVER = ( - "ODBC Driver 18 for SQL Server" if platform.system() == "Linux" else "SQL Server" -) -# Configuration -CONN_STR = ( - f"Driver={{{SQL_DRIVER}}};" - f"Server={os.getenv('DB_SERVER')};" - f"Database={os.getenv('DB_NAME')};" - f"UID={os.getenv('DB_USER')};" - f"PWD={os.getenv('DB_PASSWORD')};" - "TrustServerCertificate=yes;" -) - -@dataclass -class PlcConfig: - id: int - ip: str - db_number: int - is_enabled: bool - air_offset: int - energy_offset: int - location: str - last_energy_read: float - last_air_read: float - runstop_status_offset: int - -@dataclass -class SchedulerConfig: - interval: int - next_read: datetime - -class DatabaseManager: - def __init__(self, connection_string: str): - self.conn_str = connection_string - def get_plc_configs(self) -> List[PlcConfig]: - with pyodbc.connect(self.conn_str) as conn: - with conn.cursor() as cursor: - cursor.execute(""" - SELECT Id, Ip, DbNumber, IsEnable, AirDbOffset, EnergyDbOffset, Location, LastEnergyRead, LastAirRead, RunStopStatusDbOffset - FROM sch.Plc - WHERE IsEnable = 1 - """) - return [PlcConfig(*row) for row in cursor.fetchall()] - def get_scheduler_config(self) -> SchedulerConfig: - with pyodbc.connect(self.conn_str) as conn: - with conn.cursor() as cursor: - cursor.execute(""" - SELECT Interval, NextRead - FROM sch.SchedulerParameters - WHERE Name = 'Python_Energy_Scheduler' - """) - row = cursor.fetchone() - return ( - SchedulerConfig(row[0], row[1]) - if row - else SchedulerConfig(30, datetime.now()) - ) - def save_energy_data(self, plc_id: int, energy: float, air: float, state: bool): - with pyodbc.connect(self.conn_str) as conn: - with conn.cursor() as cursor: - cursor.execute( - """ - INSERT INTO sch.Energy (Energy, PlcId, Air, State, CreatedAt) - VALUES (?, ?, ?, ?, ?) - """, - (energy, plc_id, air, state, datetime.now()), - ) - conn.commit() - def update_next_read(self, interval_seconds: int): - next_read = datetime.now() + timedelta(seconds=interval_seconds) - with pyodbc.connect(self.conn_str) as conn: - with conn.cursor() as cursor: - cursor.execute( - """ - UPDATE sch.SchedulerParameters - SET NextRead = ? - WHERE Name = 'Python_Energy_Scheduler' - """, - (next_read,), - ) - conn.commit() - -class PlcManager: - def check_connection(self, ip: str, port: int = 102, timeout: int = 1) -> bool: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.settimeout(timeout) - return sock.connect_ex((ip, port)) == 0 - def read_plc_data(self, config: PlcConfig) -> Tuple[float, float, bool]: - plc = snap7.client.Client() - try: - plc.connect(config.ip, 0, 1) - data = plc.db_read(config.db_number, 0, config.runstop_status_offset) # Define range of bytes to read - energy_value = get_lreal(data, config.energy_offset) # Read energy value - air_value = get_lreal(data, config.air_offset) # Read air value - # run_status_value = get_bool(data, config.runstop_status_offset, 1) # Read run start value - return air_value, energy_value, True - except Exception as e: - pprint.pp( - { - "msg": "Failed to read PLC data", - "plc_config": { - "id": config.id, - "ip": config.ip, - "db_number": config.db_number, - "air_offset": config.air_offset, - "energy_offset": config.energy_offset, - "runstop_status_offset": config.runstop_status_offset, - "location": config.location, - "ene_val": energy_value, - "air_val": air_value, - "run_status_val": run_status_value, - "timestamp": datetime.now().isoformat() - }, - "error": str(e), - "timestamp": datetime.now().isoformat() - } - ) - raise - finally: - plc.disconnect() \ No newline at end of file +import configparser +import platform +import pprint +import socket +from dataclasses import dataclass +from datetime import datetime, timedelta +from typing import List, Tuple + +import pyodbc +import snap7 +from dotenv import load_dotenv +from snap7.util.getters import get_bool, get_lreal + +# Load config.ini +config = configparser.ConfigParser() +config.read("config.ini") +# Determine the correct driver based on OS +SQL_DRIVER = "ODBC Driver 18 for SQL Server" if platform.system() == "Linux" else "SQL Server" +# Configuration +CONN_STR = ( + f"Driver={{{SQL_DRIVER}}};" + f"Server={config['mssql']['host']};" + f"Database={config['mssql']['name']};" + f"UID={config['mssql']['user']};" + f"PWD={config['mssql']['password']};" + "TrustServerCertificate=yes;" +) + + +@dataclass +class PlcConfig: + id: int + ip: str + db_number: int + is_enabled: bool + air_offset: int + energy_offset: int + location: str + last_energy_read: float + last_air_read: float + runstop_status_offset: int + + +@dataclass +class SchedulerConfig: + interval: int + next_read: datetime + + +class DatabaseManager: + def __init__(self, connection_string: str): + self.conn_str = connection_string + + def get_plc_configs(self) -> List[PlcConfig]: + with pyodbc.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(""" + SELECT Id, Ip, DbNumber, IsEnable, AirDbOffset, EnergyDbOffset, Location, LastEnergyRead, LastAirRead, RunStopStatusDbOffset + FROM sch.Plc + WHERE IsEnable = 1 + """) + return [PlcConfig(*row) for row in cursor.fetchall()] + + def get_scheduler_config(self) -> SchedulerConfig: + with pyodbc.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(""" + SELECT Interval, NextRead + FROM sch.SchedulerParameters + WHERE Name = 'Python_Energy_Scheduler' + """) + row = cursor.fetchone() + return ( + SchedulerConfig(row[0], row[1]) if row else SchedulerConfig(30, datetime.now()) + ) + + def save_energy_data(self, plc_id: int, energy: float, air: float, state: bool): + with pyodbc.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute( + """ + INSERT INTO sch.Energy (Energy, PlcId, Air, State, CreatedAt) + VALUES (?, ?, ?, ?, ?) + """, + (energy, plc_id, air, state, datetime.now()), + ) + conn.commit() + + def update_next_read(self, interval_seconds: int): + next_read = datetime.now() + timedelta(seconds=interval_seconds) + with pyodbc.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute( + """ + UPDATE sch.SchedulerParameters + SET NextRead = ? + WHERE Name = 'Python_Energy_Scheduler' + """, + (next_read,), + ) + conn.commit() + + +class PlcManager: + def check_connection(self, ip: str, port: int = 102, timeout: int = 1) -> bool: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.settimeout(timeout) + return sock.connect_ex((ip, port)) == 0 + + def read_plc_data(self, config: PlcConfig) -> Tuple[float, float, bool]: + plc = snap7.client.Client() + try: + plc.connect(config.ip, 0, 1) + data = plc.db_read( + config.db_number, 0, config.runstop_status_offset + ) # Define range of bytes to read + energy_value = get_lreal(data, config.energy_offset) # Read energy value + air_value = get_lreal(data, config.air_offset) # Read air value + # run_status_value = get_bool(data, config.runstop_status_offset, 1) # Read run start value + return air_value, energy_value, True + except Exception as e: + pprint.pp( + { + "msg": "Failed to read PLC data", + "plc_config": { + "id": config.id, + "ip": config.ip, + "db_number": config.db_number, + "air_offset": config.air_offset, + "energy_offset": config.energy_offset, + "runstop_status_offset": config.runstop_status_offset, + "location": config.location, + "ene_val": energy_value, + "air_val": air_value, + "run_status_val": True, + "timestamp": datetime.now().isoformat(), + }, + "error": str(e), + "timestamp": datetime.now().isoformat(), + } + ) + raise + finally: + plc.disconnect() diff --git a/.archive/old_version/logger_setup.py b/legacy/logger_setup.py similarity index 99% rename from .archive/old_version/logger_setup.py rename to legacy/logger_setup.py index 4b0b519..972175f 100644 --- a/.archive/old_version/logger_setup.py +++ b/legacy/logger_setup.py @@ -1,6 +1,7 @@ +import json import logging import os -import json + import seqlog diff --git a/.archive/old_version/main.py b/legacy/main.py similarity index 90% rename from .archive/old_version/main.py rename to legacy/main.py index 9d5379b..3e0f43b 100644 --- a/.archive/old_version/main.py +++ b/legacy/main.py @@ -1,122 +1,126 @@ -import os -import pprint -import time -from datetime import datetime - -from energy_monitor import CONN_STR, DatabaseManager, PlcManager -from logger_setup import setup_logger - -# Globals -DEVICE_DELAY = 1 # Delay between device data fetches -POOL_RATE = 2 # 2s polling rate reading of NextRead value from database -FALLBACK_INTERVAL = 20 # 1h = 3600s interval in case db read issue - -# Logger setup -logger = setup_logger() - - -def process_plc_devices(db_manager: DatabaseManager, plc_manager: PlcManager): - plc_configs = db_manager.get_plc_configs() - # If debug print out plc_configs to logs - logger.debug( - { - "msg": "Retrieved PLC configurations", - "plc_configs": [ - { - "id": plc.id, - "ip": plc.ip, - "location": plc.location, - "is_enabled": plc.is_enabled, - "db_number": plc.db_number, - } - for plc in plc_configs - ], - "timestamp": datetime.now().isoformat(), - }, - ) - for plc_config in plc_configs: - try: - if not plc_manager.check_connection(plc_config.ip): - logger.error( - { - "msg": "PLC connection failed", - "PlcId": plc_config.id, - "PlcIp": plc_config.ip, - "timestamp": datetime.now().isoformat(), - } - ) - continue - - air_value, energy_value, run_status = plc_manager.read_plc_data(plc_config) - db_manager.save_energy_data(plc_config.id, energy_value, air_value, True) - - logger.info( - { - "msg": "✅ Data successfully read and saved", - "PlcId": plc_config.id, - "PlcIp": plc_config.ip, - "energy_value": energy_value, - "air_value": air_value, - "timestamp": datetime.now().isoformat(), - }, - ) - - time.sleep(DEVICE_DELAY) # n-second delay between devices - - except Exception as e: - logger.error( - { - "msg": "Error reading PLC data", - "PlcId": plc_config.id, - "PlcIp": plc_config.ip, - "error": str(e), - "timestamp": datetime.now().isoformat(), - }, - ) - - -def main(): - db_manager = DatabaseManager(CONN_STR) - plc_manager = PlcManager() - - pprint.pp( - { - "SQL_Config": { - "Server": os.getenv("DB_SERVER"), - "Database": os.getenv("DB_NAME"), - "User": os.getenv("DB_USER"), - # Masking password for security - "Password": "****", - }, - "SEQ_Config": { - "URL": os.getenv("SEQ_URL"), - # Masking API key for security - "API_Key": "****", - }, - "timestamp": datetime.now().isoformat(), - } - ) - - while True: - try: - scheduler_config = db_manager.get_scheduler_config() - - if datetime.now() >= scheduler_config.next_read: - process_plc_devices(db_manager, plc_manager) - db_manager.update_next_read(scheduler_config.interval) - - time.sleep(POOL_RATE) - - except Exception as e: - logger.error( - { - "msg": "Main loop error! Falling back to 1h interval.", - "error": str(e), - "timestamp": datetime.now().isoformat(), - }, - ) - time.sleep(FALLBACK_INTERVAL) # Default fallback interval - - -if __name__ == "__main__": - main() +import configparser +import pprint +import time +from datetime import datetime + +from energy_monitor import CONN_STR, DatabaseManager, PlcManager +from logger_setup import setup_logger + +# Globals +DEVICE_DELAY = 1 # Delay between device data fetches +POOL_RATE = 2 # 2s polling rate reading of NextRead value from database +FALLBACK_INTERVAL = 20 # 1h = 3600s interval in case db read issue + +# Logger setup +logger = setup_logger() + +# Load config.ini +config = configparser.ConfigParser() +config.read("config.ini") + + +def process_plc_devices(db_manager: DatabaseManager, plc_manager: PlcManager): + plc_configs = db_manager.get_plc_configs() + # If debug print out plc_configs to logs + logger.debug( + { + "msg": "Retrieved PLC configurations", + "plc_configs": [ + { + "id": plc.id, + "ip": plc.ip, + "location": plc.location, + "is_enabled": plc.is_enabled, + "db_number": plc.db_number, + } + for plc in plc_configs + ], + "timestamp": datetime.now().isoformat(), + }, + ) + for plc_config in plc_configs: + try: + if not plc_manager.check_connection(plc_config.ip): + logger.error( + { + "msg": "PLC connection failed", + "PlcId": plc_config.id, + "PlcIp": plc_config.ip, + "timestamp": datetime.now().isoformat(), + } + ) + continue + + air_value, energy_value, run_status = plc_manager.read_plc_data(plc_config) + db_manager.save_energy_data(plc_config.id, energy_value, air_value, True) + + logger.info( + { + "msg": ":white_check_mark: Data successfully read and saved", + "PlcId": plc_config.id, + "PlcIp": plc_config.ip, + "energy_value": energy_value, + "air_value": air_value, + "timestamp": datetime.now().isoformat(), + }, + ) + + time.sleep(DEVICE_DELAY) # n-second delay between devices + + except Exception as e: + logger.error( + { + "msg": "Error reading PLC data", + "PlcId": plc_config.id, + "PlcIp": plc_config.ip, + "error": str(e), + "timestamp": datetime.now().isoformat(), + }, + ) + + +def main(): + db_manager = DatabaseManager(CONN_STR) + plc_manager = PlcManager() + + pprint.pp( + { + "SQL_Config": { + "Server": config["mssql"]["host"], + "Database": config["mssql"]["name"], + "User": config["mssql"]["user"], + # Masking password for security + "Password": "****", + }, + "SEQ_Config": { + "URL": config["seq"]["url"], + # Masking API key for security + "API_Key": "****", + }, + "timestamp": datetime.now().isoformat(), + } + ) + + while True: + try: + scheduler_config = db_manager.get_scheduler_config() + + if datetime.now() >= scheduler_config.next_read: + process_plc_devices(db_manager, plc_manager) + db_manager.update_next_read(scheduler_config.interval) + + time.sleep(POOL_RATE) + + except Exception as e: + logger.error( + { + "msg": "Main loop error! Falling back to 1h interval.", + "error": str(e), + "timestamp": datetime.now().isoformat(), + }, + ) + time.sleep(FALLBACK_INTERVAL) # Default fallback interval + + +if __name__ == "__main__": + main() diff --git a/notes.md b/notes.md index ea9ea28..6c5b582 100644 --- a/notes.md +++ b/notes.md @@ -51,16 +51,30 @@ PAC object used to store configuration of PAC ## Workflow +Entire application should be configurable via databse. +Main config of dbs connections, seq connection should be in `config.ini` file. +Maintain support to multiple dbs providers (mssql, postgresql) +> WARNING: `config.ini` contains sensitive data, so it should be excluded from git repository. + ### Main process `main.py` -Is infinite loop that: -- checks scheduler settings from database (Scheduler) -- checks if it's time to run scheduler -- if yes, runs scheduler then updates next_run date following schema date.now() + interval_seconds +Place where: +- logger is initialized +- database service is initialized +- read service is initialized +- scheduler service is initialized ### Scheduler process/service -`scheduler_service.py` +`scheduler.py` + +Module that is responsible for scheduled operations. +Scheduler should be able to: +- have name - set on object creation, it will be used as identifier of scheduler +- run multiple instances of itself +- Raw class of `Scheduler` should have methods for controlling other schedulers. +- `Scheduler` object should have methods for controlling scheduler itself. +- Function that it will be calling should be somehow passed to scheduler. When scheduler is run, it: - gets all devices from database (Device) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5743760 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[project] +name = "industrialenergytracker" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "pymodbus>=3.8.1", + "pyodbc>=5.2.0", + "pys7", + "python-dotenv>=1.0.1", + "python-snap7>=2.0.2", + "seqlog>=0.4.0", + "sqlalchemy>=2.0.36", +] + +[project.optional-dependencies] +dev = ["pytest>=8.3.4", "ruff>=0.8.3", "pyinstaller>=6.11.1"] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src"] + +[tool.hatch.metadata] +package-dir = "src" + +[tool.ruff] +line-length = 100 +target-version = "py38" +src = ["src"] + +[tool.uv.sources] +pys7 = { git = "https://github.com/FiloCara/pyS7", rev = "761c785799106a04ccbc9e19d6201f728165231d" } diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index e782696..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,3 +0,0 @@ --r requirements.txt -pytest -ruff \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b5a4189..0000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -sqlalchemy -python-snap7 -pyodbc -python-dotenv -seqlog -pymodbus -pyS7 @ git+https://github.com/FiloCara/pyS7@761c785799106a04ccbc9e19d6201f728165231d \ No newline at end of file diff --git a/src/models/__init__.py b/src/models/__init__.py deleted file mode 100644 index c4b3840..0000000 --- a/src/models/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy.ext.declarative import declarative_base - -from .device import Device, DeviceType -from .pac import PAC -from .plc import PLC -from .reading import Reading -from .scheduler import Scheduler - -Base = declarative_base() - -__all__ = ["Base", "Device", "DeviceType", "PLC", "PAC", "Reading", "Scheduler"] diff --git a/src/models/device.py b/src/models/device.py deleted file mode 100644 index f2a020b..0000000 --- a/src/models/device.py +++ /dev/null @@ -1,28 +0,0 @@ -from sqlalchemy import Column, ForeignKey, Integer, String -from sqlalchemy.orm import relationship - -from models import Base - - -class DeviceType(Base): - __tablename__ = "device_type" - - id = Column(Integer, primary_key=True) - name = Column(String(100), nullable=False) - - devices = relationship("Device", back_populates="type") - - -class Device(Base): - __tablename__ = "device" - - id = Column(Integer, primary_key=True) - name = Column(String(100), nullable=False) - type_id = Column(Integer, ForeignKey("device_type.id"), nullable=False) - enabled = Column(Integer, default=True, nullable=False) - - # Relationships - type = relationship("DeviceType", back_populates="devices") - plc_config = relationship("PLC", back_populates="device", uselist=False) - pac_config = relationship("PAC", back_populates="device", uselist=False) - readings = relationship("Reading", back_populates="device") diff --git a/src/models/pac.py b/src/models/pac.py deleted file mode 100644 index a2f5d1c..0000000 --- a/src/models/pac.py +++ /dev/null @@ -1,15 +0,0 @@ -from sqlalchemy import Column, ForeignKey, Integer, String -from sqlalchemy.orm import relationship - -from models import Base - - -class PAC(Base): - __tablename__ = "pac" - - id = Column(Integer, primary_key=True) - device_id = Column(Integer, ForeignKey("device.id"), nullable=False) - ip = Column(String(15), nullable=False) - port = Column(Integer, default=102) - - device = relationship("Device", back_populates="pac_config") diff --git a/src/models/plc.py b/src/models/plc.py deleted file mode 100644 index d37cb2c..0000000 --- a/src/models/plc.py +++ /dev/null @@ -1,19 +0,0 @@ -from sqlalchemy import Column, ForeignKey, Integer, String -from sqlalchemy.orm import relationship - -from models import Base - - -class PLC(Base): - __tablename__ = "plc" - - id = Column(Integer, primary_key=True) - device_id = Column(Integer, ForeignKey("device.id"), nullable=False) - ip = Column(String(15), nullable=False) - port = Column(Integer, default=102) - db_number = Column(Integer, nullable=False) - energy_offset = Column(Integer) - air_offset = Column(Integer) - running_offset = Column(Integer) - - device = relationship("Device", back_populates="plc_config") diff --git a/src/models/reading.py b/src/models/reading.py deleted file mode 100644 index 094c1df..0000000 --- a/src/models/reading.py +++ /dev/null @@ -1,19 +0,0 @@ -import datetime - -from sqlalchemy import Boolean, Column, DateTime, Float, ForeignKey, Integer -from sqlalchemy.orm import relationship - -from models import Base - - -class Reading(Base): - __tablename__ = "reading" - - id = Column(Integer, primary_key=True) - device_id = Column(Integer, ForeignKey("device.id"), nullable=False) - reading_time = Column(DateTime, default=datetime.now(datetime.timezone.utc)) - energy = Column(Float) - air = Column(Float) - running = Column(Boolean) - - device = relationship("Device", back_populates="readings") diff --git a/src/models/scheduler.py b/src/models/scheduler.py deleted file mode 100644 index dd1c505..0000000 --- a/src/models/scheduler.py +++ /dev/null @@ -1,12 +0,0 @@ -from sqlalchemy import Column, DateTime, Integer, String - -from models import Base - - -class Scheduler(Base): - __tablename__ = "scheduler" - - id = Column(Integer, primary_key=True) - name = Column(String(100), nullable=False) - interval_seconds = Column(Integer, nullable=False) - next_run = Column(DateTime, nullable=False) diff --git a/src/readers/__init__.py b/src/readers/__init__.py deleted file mode 100644 index ab001a6..0000000 --- a/src/readers/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .device_reader import DeviceReader -from .pac_reader import PACReader -from .plc_reader import PLCReader - -__all__ = ["DeviceReader", "PLCReader", "PACReader"] diff --git a/src/readers/device_reader.py b/src/readers/device_reader.py deleted file mode 100644 index dc902ca..0000000 --- a/src/readers/device_reader.py +++ /dev/null @@ -1,20 +0,0 @@ -from abc import ABC, abstractmethod - -from src.models import Device, Reading - - -# Abstract base class for device readers -# This class defines a common interface that all device readers must implement -# Using abstract classes ensures consistency across different device types -class DeviceReader(ABC): - @abstractmethod - def is_accessible(self, device: Device) -> bool: - # This abstract method forces all child classes to implement their own - # device accessibility check logic - pass - - @abstractmethod - def collect_reading(self, device: Device) -> Reading: - # This abstract method forces all child classes to implement their own - # data collection logic specific to the device type - pass diff --git a/src/readers/pac_reader.py b/src/readers/pac_reader.py deleted file mode 100644 index 5d29c61..0000000 --- a/src/readers/pac_reader.py +++ /dev/null @@ -1,16 +0,0 @@ -from .device_reader import DeviceReader -from src.models import Device, Reading - - -# Concrete implementation for PAC (Programmable Automation Controller) devices -# Inherits from DeviceReader and must implement all abstract methods -class PACReader(DeviceReader): - def is_accessible(self, device: Device) -> bool: - # Implement PAC ping check - # This method will contain specific logic for checking PAC connectivity - pass - - def collect_reading(self, device: Device) -> Reading: - # Implement PAC data collection - # This method will contain specific logic for reading data from PAC devices - pass diff --git a/src/readers/plc_reader.py b/src/readers/plc_reader.py deleted file mode 100644 index cfe2303..0000000 --- a/src/readers/plc_reader.py +++ /dev/null @@ -1,16 +0,0 @@ -from .device_reader import DeviceReader -from src.models import Device, Reading - - -# Concrete implementation for PLC (Programmable Logic Controller) devices -# Inherits from DeviceReader and must implement all abstract methods -class PLCReader(DeviceReader): - def is_accessible(self, device: Device) -> bool: - # Implement PLC ping check - # This method will contain specific logic for checking PLC connectivity - pass - - def collect_reading(self, device: Device) -> Reading: - # Implement PLC data collection - # This method will contain specific logic for reading data from PLC devices - pass diff --git a/src/scripts/test_db.py b/src/scripts/test_db.py deleted file mode 100644 index 38542b5..0000000 --- a/src/scripts/test_db.py +++ /dev/null @@ -1,46 +0,0 @@ -from datetime import datetime, timedelta -from services.database_service import DatabaseService - -def test_database_operations(): - # Initialize database service - db = DatabaseService() - - # Test getting device types - print("\n=== Testing Device Types ===") - device_types = db.get_device_types() - print(f"Found {len(device_types)} device types") - for dt in device_types: - print(f"- {dt.name}") - - # Test getting enabled devices - print("\n=== Testing Enabled Devices ===") - enabled_devices = db.get_enabled_devices() - print(f"Found {len(enabled_devices)} enabled devices") - for device in enabled_devices: - print(f"- {device.name} (Type: {device.type.name})") - - # Test scheduler operations - print("\n=== Testing Scheduler ===") - scheduler = db.get_scheduler("main") - if scheduler: - print(f"Current scheduler: {scheduler.name}") - print(f"Current next_run: {scheduler.next_run}") - - # Test updating scheduler - new_next_run = datetime.now() + timedelta(minutes=5) - db.update_scheduler_next_run(scheduler, new_next_run) - print(f"Updated next_run to: {new_next_run}") - - # Test getting readings for a device - print("\n=== Testing Readings ===") - if enabled_devices: - test_device = enabled_devices[0] - from_date = datetime.now() - timedelta(days=1) - to_date = datetime.now() - readings = db.get_readings_by_device(test_device.id, from_date, to_date) - print(f"Found {len(readings)} readings for device {test_device.name}") - for reading in readings[:5]: # Show first 5 readings - print(f"- Time: {reading.reading_time}, Energy: {reading.energy}") - -if __name__ == "__main__": - test_database_operations() \ No newline at end of file diff --git a/src/services/__init__.py b/src/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/services/database_service.py b/src/services/database_service.py deleted file mode 100644 index 47875f9..0000000 --- a/src/services/database_service.py +++ /dev/null @@ -1,79 +0,0 @@ -from datetime import datetime -from typing import List, Optional - -from sqlalchemy import create_engine -from sqlalchemy.orm import Session - -from src.models import PAC, PLC, Device, DeviceType, Reading, Scheduler -from utils.config import Config - - -class SingletonMeta(type): - _instances = {} - - def __call__(cls, *args, **kwargs): - """ - This is a singleton metaclass implementation that ensures only one instance of a class is created. - When a class with this metaclass is instantiated: - 1. It checks if the class already has an instance in _instances dictionary - 2. If no instance exists, it creates one using super().__call__ and stores it - 3. If instance exists, it returns the stored instance - This way, multiple calls to create an instance will always return the same object - """ - if cls not in cls._instances: - cls._instances[cls] = super().__call__(*args, **kwargs) - return cls._instances[cls] - -class DatabaseService(metaclass=SingletonMeta): - def __init__(self): - config = Config() - engine = create_engine(config.get_database_url()) - self.session = Session(engine) - - def get_enabled_devices(self) -> List[Device]: - return self.session.query(Device).filter(Device.enabled.is_(True)).all() - - def get_scheduler(self, name: str) -> Optional[Scheduler]: - return self.session.query(Scheduler).filter(Scheduler.name == name).first() - - def update_scheduler_next_run( - self, scheduler: Scheduler, next_run: datetime - ) -> None: - scheduler.next_run = next_run - self.session.commit() - - def save_reading(self, reading: Reading) -> None: - self.session.add(reading) - self.session.commit() - - def save_readings(self, readings: List[Reading]) -> None: - self.session.add_all(readings) - self.session.commit() - - def get_device_types(self) -> List[DeviceType]: - return self.session.query(DeviceType).all() - - def get_plc_config(self, device_id: int) -> Optional[PLC]: - return self.session.query(PLC).filter(PLC.id == device_id).first() - - def get_pac_config(self, device_id: int) -> Optional[PAC]: - return self.session.query(PAC).filter(PAC.id == device_id).first() - - def get_device(self, device_id: int) -> Optional[Device]: - return self.session.query(Device).filter(Device.id == device_id).first() - - def get_device_by_name(self, name: str) -> Optional[Device]: - return self.session.query(Device).filter(Device.name == name).first() - - def get_readings_by_device( - self, device_id: int, from_date: datetime, to_date: datetime - ) -> List[Reading]: - return ( - self.session.query(Reading) - .filter( - Reading.device_id == device_id, - Reading.reading_time >= from_date, - Reading.reading_time <= to_date, - ) - .all() - ) diff --git a/src/services/scheduler_service.py b/src/services/scheduler_service.py deleted file mode 100644 index d29348b..0000000 --- a/src/services/scheduler_service.py +++ /dev/null @@ -1,28 +0,0 @@ -from datetime import datetime, timedelta - -from sqlalchemy.orm import Session - -from src.models import Scheduler - - -class SchedulerService: - def __init__(self, db_session: Session): - # Initialize the scheduler service with a database session - self.session = db_session - - def run_main_loop(self): - # Main loop that continuously checks and executes scheduled tasks - while True: - # Get all scheduler entries from the database - schedulers = self.session.query(Scheduler).all() - for scheduler in schedulers: - # Check if it's time to execute the scheduler - if datetime.now(datetime.timezone.utc) >= scheduler.next_run: - # Execute the scheduled task - self.execute_scheduler(scheduler) - # Calculate and set the next run time based on the interval - scheduler.next_run = datetime.now( - datetime.timezone.utc - ) + timedelta(seconds=scheduler.interval_seconds) - # Save changes to the database - self.session.commit() diff --git a/src/utils/config.py b/src/utils/config.py deleted file mode 100644 index de6fe11..0000000 --- a/src/utils/config.py +++ /dev/null @@ -1,34 +0,0 @@ -from configparser import ConfigParser -from pathlib import Path -import platform - - -class Config: - _instance = None - - def __new__(cls): - if cls._instance is None: - cls._instance = super().__new__(cls) - config = ConfigParser() - config_path = Path("config.ini") - config.read(config_path) - cls._instance.config = config - return cls._instance - - def get_mssql_url(self) -> str: - mssql = self.config["mssql"] - driver = "ODBC Driver 18 for SQL Server" if platform.platform.system() == "Linux" else "SQL Server" - driver = driver.replace(" ", "+") - return f"mssql+pyodbc://{mssql['user']}:{mssql['password']}@{mssql['host']}/{mssql['name']}?driver={driver}" - - def get_postgres_url(self) -> str: - db = self.config["postgres"] - return f"postgresql://{db['user']}:{db['password']}@{db['host']}/{db['name']}" - - def get_seq_url(self) -> str: - seq = self.config["seq"] - return seq["url"] - - def get_seq_api_key(self) -> str: - seq = self.config["seq"] - return seq["api_key"] diff --git a/src/utils/helpers.py b/src/utils/helpers.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/utils/logger.py b/src/utils/logger.py deleted file mode 100644 index e69de29..0000000 diff --git a/test-server.sh b/test-server.sh deleted file mode 100644 index de0d95b..0000000 --- a/test-server.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!.venv/bin/python -python -m snap7.server \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..68bca67 --- /dev/null +++ b/uv.lock @@ -0,0 +1,472 @@ +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "altgraph" +version = "0.17.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/a8/7145824cf0b9e3c28046520480f207df47e927df83aa9555fb47f8505922/altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406", size = 48418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff", size = 21212 }, +] + +[[package]] +name = "certifi" +version = "2024.12.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "greenlet" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 }, + { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 }, + { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 }, + { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 }, + { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 }, + { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077 }, + { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 }, + { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 }, + { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 }, + { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 }, + { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 }, + { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 }, + { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 }, + { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 }, + { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583 }, + { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 }, + { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 }, + { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 }, + { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 }, + { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 }, + { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 }, + { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 }, + { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753 }, + { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 }, + { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "industrialenergytracker" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "pymodbus" }, + { name = "pyodbc" }, + { name = "pys7" }, + { name = "python-dotenv" }, + { name = "python-snap7" }, + { name = "seqlog" }, + { name = "sqlalchemy" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pyinstaller" }, +] + +[package.metadata] +requires-dist = [ + { name = "pymodbus", specifier = ">=3.8.1" }, + { name = "pyodbc", specifier = ">=5.2.0" }, + { name = "pys7", git = "https://github.com/FiloCara/pyS7?rev=761c785799106a04ccbc9e19d6201f728165231d" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.4" }, + { name = "python-dotenv", specifier = ">=1.0.1" }, + { name = "python-snap7", specifier = ">=2.0.2" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.3" }, + { name = "seqlog", specifier = ">=0.4.0" }, + { name = "sqlalchemy", specifier = ">=2.0.36" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pyinstaller", specifier = ">=6.11.1" }] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "macholib" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/ee/af1a3842bdd5902ce133bd246eb7ffd4375c38642aeb5dc0ae3a0329dfa2/macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30", size = 59309 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c", size = 38094 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pefile" +version = "2023.2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/c5/3b3c62223f72e2360737fd2a57c30e5b2adecd85e70276879609a7403334/pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc", size = 74854 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pyinstaller" +version = "6.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, + { name = "macholib", marker = "sys_platform == 'darwin'" }, + { name = "packaging" }, + { name = "pefile", marker = "sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/d4/54f5f5c73b803e6256ea97ffc6ba8a305d9a5f57f85f9b00b282512bf18a/pyinstaller-6.11.1.tar.gz", hash = "sha256:491dfb4d9d5d1d9650d9507daec1ff6829527a254d8e396badd60a0affcb72ef", size = 4249772 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/15/b0f1c0985ee32fcd2f6ad9a486ef94e4db3fef9af025a3655e76cb708009/pyinstaller-6.11.1-py3-none-macosx_10_13_universal2.whl", hash = "sha256:44e36172de326af6d4e7663b12f71dbd34e2e3e02233e181e457394423daaf03", size = 991780 }, + { url = "https://files.pythonhosted.org/packages/fd/0f/9f54cb18abe2b1d89051bc9214c0cb40d7b5f4049c151c315dacc067f4a2/pyinstaller-6.11.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:6d12c45a29add78039066a53fb05967afaa09a672426072b13816fe7676abfc4", size = 711739 }, + { url = "https://files.pythonhosted.org/packages/32/f7/79d10830780eff8339bfa793eece1df4b2459e35a712fc81983e8536cc29/pyinstaller-6.11.1-py3-none-manylinux2014_i686.whl", hash = "sha256:ddc0fddd75f07f7e423da1f0822e389a42af011f9589e0269b87e0d89aa48c1f", size = 714053 }, + { url = "https://files.pythonhosted.org/packages/25/f7/9961ef02cdbd2dbb1b1a215292656bd0ea72a83aafd8fb6373513849711e/pyinstaller-6.11.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:0d6475559c4939f0735122989611d7f739ed3bf02f666ce31022928f7a7e4fda", size = 719133 }, + { url = "https://files.pythonhosted.org/packages/6f/4d/7f854842a1ce798de762a0b0bc5d5a4fc26ad06164a98575dc3c54abed1f/pyinstaller-6.11.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:e21c7806e34f40181e7606926a14579f848bfb1dc52cbca7eea66eccccbfe977", size = 709591 }, + { url = "https://files.pythonhosted.org/packages/7f/e0/00d29fc90c3ba50620c61554e26ebb4d764569507be7cd1c8794aa696f9a/pyinstaller-6.11.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:32c742a24fe65d0702958fadf4040f76de85859c26bec0008766e5dbabc5b68f", size = 710068 }, + { url = "https://files.pythonhosted.org/packages/3e/57/d14b44a69f068d2caaee49d15e45f9fa0f37c6a2d2ad778c953c1722a1ca/pyinstaller-6.11.1-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:208c0ef6dab0837a0a273ea32d1a3619a208e3d1fe3fec3785eea71a77fd00ce", size = 714439 }, + { url = "https://files.pythonhosted.org/packages/88/01/256824bb57ca208099c86c2fb289f888ca7732580e91ced48fa14e5903b2/pyinstaller-6.11.1-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:ad84abf465bcda363c1d54eafa76745d77b6a8a713778348377dc98d12a452f7", size = 710457 }, + { url = "https://files.pythonhosted.org/packages/7c/f0/98c9138f5f0ff17462f1ad6d712dcfa643b9a283d6238d464d8145bc139d/pyinstaller-6.11.1-py3-none-win32.whl", hash = "sha256:2e8365276c5131c9bef98e358fbc305e4022db8bedc9df479629d6414021956a", size = 1280261 }, + { url = "https://files.pythonhosted.org/packages/7d/08/f43080614b3e8bce481d4dfd580e579497c7dcdaf87656d9d2ad912e5796/pyinstaller-6.11.1-py3-none-win_amd64.whl", hash = "sha256:7ac83c0dc0e04357dab98c487e74ad2adb30e7eb186b58157a8faf46f1fa796f", size = 1340482 }, + { url = "https://files.pythonhosted.org/packages/ed/56/953c6594cb66e249563854c9cc04ac5a055c6c99d1614298feeaeaa9b87e/pyinstaller-6.11.1-py3-none-win_arm64.whl", hash = "sha256:35e6b8077d240600bb309ed68bb0b1453fd2b7ab740b66d000db7abae6244423", size = 1267519 }, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2024.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/df/9fa06adfc7ac4fe07fd796a7bd402ec91ada4d360329a90d17e0275beaba/pyinstaller_hooks_contrib-2024.11.tar.gz", hash = "sha256:84399af6b4b902030958063df25f657abbff249d0f329c5344928355c9833ab4", size = 141622 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/af/965f81a65f4d9bcb337dd0e87845fd2e081c4ab5a1c0b3e0cf20abeac423/pyinstaller_hooks_contrib-2024.11-py3-none-any.whl", hash = "sha256:2781d121a1ee961152ba7287a262c65a1078da30c9ef7621cb8c819326884fd5", size = 339452 }, +] + +[[package]] +name = "pymodbus" +version = "3.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/c7/95ad9774e7ba8547f52d657b09fee7fa02d87532866f561c6d2bb9068b8c/pymodbus-3.8.3.tar.gz", hash = "sha256:d886186bf7c3cc140f17e830919c23e4cc88334fbb91d6e5e1d7dd05860fe2c8", size = 158992 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/93/e444458244d41d0050c481cd35d3adca14ccd8a45c5eba9b0b39b04a9ca2/pymodbus-3.8.3-py3-none-any.whl", hash = "sha256:68df4a8d32e14a9eca2277141e0e129c019b0c5ad3540c2fd69ae7df2b13a2fb", size = 160740 }, +] + +[[package]] +name = "pyodbc" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/36/a1ac7d23a1611e7ccd4d27df096f3794e8d1e7faa040260d9d41b6fc3185/pyodbc-5.2.0.tar.gz", hash = "sha256:de8be39809c8ddeeee26a4b876a6463529cd487a60d1393eb2a93e9bcd44a8f5", size = 116908 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/26/104525b728fedfababd3143426b9d0008c70f0d604a3bf5d4773977d83f4/pyodbc-5.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be43d1ece4f2cf4d430996689d89a1a15aeb3a8da8262527e5ced5aee27e89c3", size = 73014 }, + { url = "https://files.pythonhosted.org/packages/4f/7d/bb632488b603bcd2a6753b858e8bc7dd56146dd19bd72003cc09ae6e3fc0/pyodbc-5.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9f7badd0055221a744d76c11440c0856fd2846ed53b6555cf8f0a8893a3e4b03", size = 72515 }, + { url = "https://files.pythonhosted.org/packages/ab/38/a1b9bfe5a7062672268553c2d6ff93676173b0fb4bd583e8c4f74a0e296f/pyodbc-5.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad633c52f4f4e7691daaa2278d6e6ebb2fe4ae7709e610e22c7dd1a1d620cf8b", size = 348561 }, + { url = "https://files.pythonhosted.org/packages/71/82/ddb1c41c682550116f391aa6cab2052910046a30d63014bbe6d09c4958f4/pyodbc-5.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d086a8f7a302b74c9c2e77bedf954a603b19168af900d4d3a97322e773df63", size = 353962 }, + { url = "https://files.pythonhosted.org/packages/e5/29/fec0e739d0c1cab155843ed71d0717f5e1694effe3f28d397168f48bcd92/pyodbc-5.2.0-cp312-cp312-win32.whl", hash = "sha256:0e4412f8e608db2a4be5bcc75f9581f386ed6a427dbcb5eac795049ba6fc205e", size = 63050 }, + { url = "https://files.pythonhosted.org/packages/21/7f/3a47e022a97b017ffb73351a1061e4401bcb5aa4fc0162d04f4e5452e4fc/pyodbc-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1f5686b142759c5b2bdbeaa0692622c2ebb1f10780eb3c174b85f5607fbcf55", size = 69485 }, + { url = "https://files.pythonhosted.org/packages/90/be/e5f8022ec57a7ea6aa3717a3f307a44c3b012fce7ad6ec91aad3e2a56978/pyodbc-5.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:26844d780045bbc3514d5c2f0d89e7fda7df7db0bd24292eb6902046f5730885", size = 72982 }, + { url = "https://files.pythonhosted.org/packages/5c/0e/71111e4f53936b0b99731d9b6acfc8fc95660533a1421447a63d6e519112/pyodbc-5.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:26d2d8fd53b71204c755abc53b0379df4e23fd9a40faf211e1cb87e8a32470f0", size = 72515 }, + { url = "https://files.pythonhosted.org/packages/a5/09/3c06bbc1ebb9ae15f53cefe10774809b67da643883287ba1c44ba053816a/pyodbc-5.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a27996b6d27e275dfb5fe8a34087ba1cacadfd1439e636874ef675faea5149d9", size = 347470 }, + { url = "https://files.pythonhosted.org/packages/a4/35/1c7efd4665e7983169d20175014f68578e0edfcbc4602b0bafcefa522c4a/pyodbc-5.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaf42c4bd323b8fd01f1cd900cca2d09232155f9b8f0b9bcd0be66763588ce64", size = 353025 }, + { url = "https://files.pythonhosted.org/packages/6d/c9/736d07fa33572abdc50d858fd9e527d2c8281f3acbb90dff4999a3662edd/pyodbc-5.2.0-cp313-cp313-win32.whl", hash = "sha256:207f16b7e9bf09c591616429ebf2b47127e879aad21167ac15158910dc9bbcda", size = 63052 }, + { url = "https://files.pythonhosted.org/packages/73/2a/3219c8b7fa3788fc9f27b5fc2244017223cf070e5ab370f71c519adf9120/pyodbc-5.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:96d3127f28c0dacf18da7ae009cd48eac532d3dcc718a334b86a3c65f6a5ef5c", size = 69486 }, +] + +[[package]] +name = "pys7" +version = "0.2.0" +source = { git = "https://github.com/FiloCara/pyS7?rev=761c785799106a04ccbc9e19d6201f728165231d#761c785799106a04ccbc9e19d6201f728165231d" } + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] + +[[package]] +name = "python-snap7" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/dd/0cc58674f7cd718804424bfb80bac84c8dc5fb40adc3a7630f3ea6bb6f4e/python_snap7-2.0.2.tar.gz", hash = "sha256:10714da0d198f2c51b521c79dc00a0e273ae42c0abc5d44605a298266960d016", size = 53415 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/db/c1ec4701b1ade7cd017ab5c16aa66b8c9de8300252d1f82bd3ad39507825/python_snap7-2.0.2-py3-none-macosx_10_9_universal2.whl", hash = "sha256:ef46a030597014c6bd82dd7ec5b5b40817cb60f649d7370cab0c327636843e7d", size = 192555 }, + { url = "https://files.pythonhosted.org/packages/1d/2a/512579993f491cb4924602a2e834f3cc4193a7e83a541454224c1faf0568/python_snap7-2.0.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:b0238f71b83d94d5196437091fec7a0ce635baf7a43a0664561f5b2ecaf5a7f8", size = 349565 }, + { url = "https://files.pythonhosted.org/packages/bb/3c/8c09fe93282c1d094641b6a567a9dea3053c6d05c942c22aa956a0bdb29d/python_snap7-2.0.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:f893f363fdecaa21cf4b0f2648d2c71e30d2924e4f58f0993a6a59a818bdf96e", size = 145147 }, + { url = "https://files.pythonhosted.org/packages/b1/bf/c3e14d59fdd8e430798d667d881adc661a89e77a80ed6e670d3c692fd779/python_snap7-2.0.2-py3-none-win_amd64.whl", hash = "sha256:686e62b087a57c121758cba724ece2fb178d86a8e79f512bb40bf5160a6be833", size = 155574 }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "ruff" +version = "0.8.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/00/089db7890ea3be5709e3ece6e46408d6f1e876026ec3fd081ee585fef209/ruff-0.8.6.tar.gz", hash = "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5", size = 3473116 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/28/aa07903694637c2fa394a9f4fe93cf861ad8b09f1282fa650ef07ff9fe97/ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3", size = 10628735 }, + { url = "https://files.pythonhosted.org/packages/2b/43/827bb1448f1fcb0fb42e9c6edf8fb067ca8244923bf0ddf12b7bf949065c/ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1", size = 10386758 }, + { url = "https://files.pythonhosted.org/packages/df/93/fc852a81c3cd315b14676db3b8327d2bb2d7508649ad60bfdb966d60738d/ruff-0.8.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807", size = 10007808 }, + { url = "https://files.pythonhosted.org/packages/94/e9/e0ed4af1794335fb280c4fac180f2bf40f6a3b859cae93a5a3ada27325ae/ruff-0.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25", size = 10861031 }, + { url = "https://files.pythonhosted.org/packages/82/68/da0db02f5ecb2ce912c2bef2aa9fcb8915c31e9bc363969cfaaddbc4c1c2/ruff-0.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d", size = 10388246 }, + { url = "https://files.pythonhosted.org/packages/ac/1d/b85383db181639019b50eb277c2ee48f9f5168f4f7c287376f2b6e2a6dc2/ruff-0.8.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75", size = 11424693 }, + { url = "https://files.pythonhosted.org/packages/ac/b7/30bc78a37648d31bfc7ba7105b108cb9091cd925f249aa533038ebc5a96f/ruff-0.8.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315", size = 12141921 }, + { url = "https://files.pythonhosted.org/packages/60/b3/ee0a14cf6a1fbd6965b601c88d5625d250b97caf0534181e151504498f86/ruff-0.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188", size = 11692419 }, + { url = "https://files.pythonhosted.org/packages/ef/d6/c597062b2931ba3e3861e80bd2b147ca12b3370afc3889af46f29209037f/ruff-0.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf", size = 12981648 }, + { url = "https://files.pythonhosted.org/packages/68/84/21f578c2a4144917985f1f4011171aeff94ab18dfa5303ac632da2f9af36/ruff-0.8.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117", size = 11251801 }, + { url = "https://files.pythonhosted.org/packages/6c/aa/1ac02537c8edeb13e0955b5db86b5c050a1dcba54f6d49ab567decaa59c1/ruff-0.8.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe", size = 10849857 }, + { url = "https://files.pythonhosted.org/packages/eb/00/020cb222252d833956cb3b07e0e40c9d4b984fbb2dc3923075c8f944497d/ruff-0.8.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d", size = 10470852 }, + { url = "https://files.pythonhosted.org/packages/00/56/e6d6578202a0141cd52299fe5acb38b2d873565f4670c7a5373b637cf58d/ruff-0.8.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a", size = 10972997 }, + { url = "https://files.pythonhosted.org/packages/be/31/dd0db1f4796bda30dea7592f106f3a67a8f00bcd3a50df889fbac58e2786/ruff-0.8.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76", size = 11317760 }, + { url = "https://files.pythonhosted.org/packages/d4/70/cfcb693dc294e034c6fed837fa2ec98b27cc97a26db5d049345364f504bf/ruff-0.8.6-py3-none-win32.whl", hash = "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764", size = 8799729 }, + { url = "https://files.pythonhosted.org/packages/60/22/ae6bcaa0edc83af42751bd193138bfb7598b2990939d3e40494d6c00698c/ruff-0.8.6-py3-none-win_amd64.whl", hash = "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905", size = 9673857 }, + { url = "https://files.pythonhosted.org/packages/91/f8/3765e053acd07baa055c96b2065c7fab91f911b3c076dfea71006666f5b0/ruff-0.8.6-py3-none-win_arm64.whl", hash = "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162", size = 9149556 }, +] + +[[package]] +name = "seqlog" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/08/adfa151a391c6ffe8a5af1f29a48d77b7701272bade7f538ed5017f426f4/seqlog-0.4.0.tar.gz", hash = "sha256:769af0e6629cd04f5e3633f52d2c0ac19cfd40ebe5516819b40ff93c77c40d0b", size = 32503 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/2c/be0667f2fac60a9dafdbf9da6a577e3a1941ad0bc4279bdddce49ca62c46/seqlog-0.4.0-py2.py3-none-any.whl", hash = "sha256:4c34442238dcfe0257082bb0808c1d0e270e83743b6d2f2cac5fe75f10512f36", size = 15540 }, +] + +[[package]] +name = "setuptools" +version = "75.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/57/e6f0bde5a2c333a32fbcce201f906c1fd0b3a7144138712a5e9d9598c5ec/setuptools-75.7.0.tar.gz", hash = "sha256:886ff7b16cd342f1d1defc16fc98c9ce3fde69e087a4e1983d7ab634e5f41f4f", size = 1338616 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/6e/abdfaaf5c294c553e7a81cf5d801fbb4f53f5c5b6646de651f92a2667547/setuptools-75.7.0-py3-none-any.whl", hash = "sha256:84fb203f278ebcf5cd08f97d3fb96d3fbed4b629d500b29ad60d11e00769b183", size = 1224467 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.36" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/65/9cbc9c4c3287bed2499e05033e207473504dc4df999ce49385fb1f8b058a/sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5", size = 9574485 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/bf/005dc47f0e57556e14512d5542f3f183b94fde46e15ff1588ec58ca89555/SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4", size = 2092378 }, + { url = "https://files.pythonhosted.org/packages/94/65/f109d5720779a08e6e324ec89a744f5f92c48bd8005edc814bf72fbb24e5/SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855", size = 2082778 }, + { url = "https://files.pythonhosted.org/packages/60/f6/d9aa8c49c44f9b8c9b9dada1f12fa78df3d4c42aa2de437164b83ee1123c/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53", size = 3232191 }, + { url = "https://files.pythonhosted.org/packages/8a/ab/81d4514527c068670cb1d7ab62a81a185df53a7c379bd2a5636e83d09ede/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a", size = 3243044 }, + { url = "https://files.pythonhosted.org/packages/35/b4/f87c014ecf5167dc669199cafdb20a7358ff4b1d49ce3622cc48571f811c/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686", size = 3178511 }, + { url = "https://files.pythonhosted.org/packages/ea/09/badfc9293bc3ccba6ede05e5f2b44a760aa47d84da1fc5a326e963e3d4d9/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588", size = 3205147 }, + { url = "https://files.pythonhosted.org/packages/c8/60/70e681de02a13c4b27979b7b78da3058c49bacc9858c89ba672e030f03f2/SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e", size = 2062709 }, + { url = "https://files.pythonhosted.org/packages/b7/ed/f6cd9395e41bfe47dd253d74d2dfc3cab34980d4e20c8878cb1117306085/SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5", size = 2088433 }, + { url = "https://files.pythonhosted.org/packages/78/5c/236398ae3678b3237726819b484f15f5c038a9549da01703a771f05a00d6/SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef", size = 2087651 }, + { url = "https://files.pythonhosted.org/packages/a8/14/55c47420c0d23fb67a35af8be4719199b81c59f3084c28d131a7767b0b0b/SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8", size = 2078132 }, + { url = "https://files.pythonhosted.org/packages/3d/97/1e843b36abff8c4a7aa2e37f9bea364f90d021754c2de94d792c2d91405b/SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b", size = 3164559 }, + { url = "https://files.pythonhosted.org/packages/7b/c5/07f18a897b997f6d6b234fab2bf31dccf66d5d16a79fe329aefc95cd7461/SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2", size = 3177897 }, + { url = "https://files.pythonhosted.org/packages/b3/cd/e16f3cbefd82b5c40b33732da634ec67a5f33b587744c7ab41699789d492/SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf", size = 3111289 }, + { url = "https://files.pythonhosted.org/packages/15/85/5b8a3b0bc29c9928aa62b5c91fcc8335f57c1de0a6343873b5f372e3672b/SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c", size = 3139491 }, + { url = "https://files.pythonhosted.org/packages/a1/95/81babb6089938680dfe2cd3f88cd3fd39cccd1543b7cb603b21ad881bff1/SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436", size = 2060439 }, + { url = "https://files.pythonhosted.org/packages/c1/ce/5f7428df55660d6879d0522adc73a3364970b5ef33ec17fa125c5dbcac1d/SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88", size = 2084574 }, + { url = "https://files.pythonhosted.org/packages/b8/49/21633706dd6feb14cd3f7935fc00b60870ea057686035e1a99ae6d9d9d53/SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e", size = 1883787 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +]