diff --git a/pyproject.toml b/pyproject.toml index 624a1ee..65292dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,12 @@ docs = [ "sphinx_rtd_theme>1.2,<2", "enum-tools[sphinx]>=0.12,<0.13", ] +aws = [ + "boto3==1.34.145", + "boto3-stubs==1.34.145", + "botocore==1.34.145", + "botocore-stubs==1.34.145", +] # lint dependencies from github super-linter v5 # See https://github.com/super-linter/super-linter/tree/main/dependencies/python lint = [ diff --git a/requirements.txt b/requirements.txt index 794e8fb..78f3261 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --extra=docs --extra=lint --extra=test --output-file=requirements.txt pyproject.toml +# pip-compile --extra=aws --extra=docs --extra=lint --extra=test --output-file=requirements.txt pyproject.toml # alabaster==0.7.16 # via sphinx @@ -23,18 +23,25 @@ backcall==0.2.0 beautifulsoup4==4.12.3 # via sphinx-toolbox black[jupyter]==23.10.1 + # via baec (pyproject.toml) +boto3==1.34.145 + # via baec (pyproject.toml) +boto3-stubs==1.34.145 + # via baec (pyproject.toml) +botocore==1.34.145 # via # baec (pyproject.toml) - # black -cachecontrol[filecache]==0.14.0 - # via - # cachecontrol - # sphinx-toolbox -cems-nuclei[client]==0.5.5 + # boto3 + # s3transfer +botocore-stubs==1.34.145 # via # baec (pyproject.toml) - # cems-nuclei -certifi==2024.6.2 + # boto3-stubs +cachecontrol[filecache]==0.14.0 + # via sphinx-toolbox +cems-nuclei[client]==0.5.5 + # via baec (pyproject.toml) +certifi==2024.7.4 # via # pyproj # requests @@ -48,10 +55,8 @@ comm==0.2.2 # via ipywidgets contourpy==1.2.1 # via matplotlib -coverage[toml]==7.5.3 - # via - # coverage - # coveralls +coverage[toml]==7.6.0 + # via coveralls coveralls==4.0.1 # via baec (pyproject.toml) cssutils==2.11.1 @@ -67,28 +72,29 @@ docopt==0.6.2 docutils==0.18.1 # via # sphinx + # sphinx-prompt # sphinx-rtd-theme # sphinx-tabs # sphinx-toolbox -domdf-python-tools==3.8.1 +domdf-python-tools==3.9.0 # via # apeye # apeye-core # dict2css # sphinx-toolbox enum-tools[sphinx]==0.12.0 - # via - # baec (pyproject.toml) - # enum-tools + # via baec (pyproject.toml) +exceptiongroup==1.2.2 + # via pytest executing==2.0.1 # via stack-data -filelock==3.15.1 +filelock==3.15.4 # via # cachecontrol # sphinx-toolbox flake8==6.0.0 # via baec (pyproject.toml) -fonttools==4.53.0 +fonttools==4.53.1 # via matplotlib html5lib==1.1 # via sphinx-toolbox @@ -116,6 +122,10 @@ jinja2==3.1.4 # via # sphinx # sphinx-jinja2-compat +jmespath==1.0.1 + # via + # boto3 + # botocore jupyterlab-widgets==3.0.11 # via ipywidgets kiwisolver==1.4.5 @@ -124,7 +134,7 @@ markupsafe==2.1.5 # via # jinja2 # sphinx-jinja2-compat -matplotlib==3.9.0 +matplotlib==3.9.1 # via baec (pyproject.toml) matplotlib-inline==0.1.7 # via ipython @@ -153,7 +163,7 @@ numpy==1.26.4 # matplotlib # pandas # pandas-stubs -orjson==3.10.5 +orjson==3.10.6 # via cems-nuclei packaging==23.2 # via @@ -176,7 +186,7 @@ pexpect==4.9.0 # via ipython pickleshare==0.7.5 # via ipython -pillow==10.3.0 +pillow==10.4.0 # via matplotlib platformdirs==3.5.1 # via @@ -189,7 +199,7 @@ prompt-toolkit==3.0.47 # via ipython ptyprocess==0.7.0 # via pexpect -pure-eval==0.2.2 +pure-eval==0.2.3 # via stack-data pycodestyle==2.10.0 # via @@ -212,10 +222,11 @@ pyparsing==3.1.2 # via matplotlib pyproj==3.6.1 # via baec (pyproject.toml) -pytest==8.2.2 +pytest==8.3.1 # via baec (pyproject.toml) python-dateutil==2.9.0.post0 # via + # botocore # matplotlib # pandas pytz==2024.1 @@ -234,6 +245,8 @@ ruamel-yaml==0.18.6 # via sphinx-toolbox ruamel-yaml-clib==0.2.8 # via ruamel-yaml +s3transfer==0.10.2 + # via boto3 six==1.16.0 # via # asttokens @@ -259,29 +272,29 @@ sphinx-autodoc-typehints==1.22 # via # baec (pyproject.toml) # sphinx-toolbox -sphinx-jinja2-compat==0.2.0.post1 +sphinx-jinja2-compat==0.3.0 # via # enum-tools # sphinx-toolbox -sphinx-prompt==1.5.0 +sphinx-prompt==1.6.0 # via sphinx-toolbox sphinx-rtd-theme==1.3.0 # via baec (pyproject.toml) sphinx-tabs==3.4.5 # via sphinx-toolbox -sphinx-toolbox==3.5.0 +sphinx-toolbox==3.7.0 # via enum-tools sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 # via sphinx -sphinxcontrib-htmlhelp==2.0.5 +sphinxcontrib-htmlhelp==2.0.6 # via sphinx sphinxcontrib-jquery==4.1 # via sphinx-rtd-theme sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.7 +sphinxcontrib-qthelp==1.0.8 # via sphinx sphinxcontrib-serializinghtml==1.1.10 # via sphinx @@ -292,24 +305,33 @@ tabulate==0.9.0 tokenize-rt==5.2.0 # via black tomli==2.0.1 - # via baec (pyproject.toml) -tqdm[notebook]==4.66.4 # via # baec (pyproject.toml) - # tqdm + # black + # coverage + # mypy + # pytest +tqdm[notebook]==4.66.4 + # via baec (pyproject.toml) traitlets==5.14.3 # via # comm # ipython # ipywidgets # matplotlib-inline +types-awscrt==0.21.2 + # via botocore-stubs types-pytz==2024.1.0.20240417 # via pandas-stubs +types-s3transfer==0.10.1 + # via boto3-stubs types-tqdm==4.66.0.20240417 # via baec (pyproject.toml) typing-extensions==4.7.1 # via # baec (pyproject.toml) + # black + # boto3-stubs # domdf-python-tools # enum-tools # mypy @@ -317,7 +339,9 @@ typing-extensions==4.7.1 tzdata==2024.1 # via pandas urllib3==2.2.2 - # via requests + # via + # botocore + # requests wcwidth==0.2.13 # via prompt-toolkit webencodings==0.5.1 diff --git a/src/baec/measurements/basetime_measurements.py b/src/baec/measurements/basetime_measurements.py index 5351adc..49357c3 100644 --- a/src/baec/measurements/basetime_measurements.py +++ b/src/baec/measurements/basetime_measurements.py @@ -1,23 +1,28 @@ from __future__ import annotations + +import json +import re from datetime import datetime -from typing import List +from os import PathLike +from typing import Dict, List -import pandas as pd import boto3 import botocore -import json +import pandas as pd import pyproj -import re +from pandas._typing import ReadCsvBuffer -from baec.project import Project from baec.coordinates import CoordinateReferenceSystems from baec.measurements.measurement_device import MeasurementDevice from baec.measurements.settlement_rod_measurement import ( SettlementRodMeasurement, StatusMessage, - StatusMessageLevel + StatusMessageLevel, ) -from baec.measurements.settlement_rod_measurement_series import SettlementRodMeasurementSeries +from baec.measurements.settlement_rod_measurement_series import ( + SettlementRodMeasurementSeries, +) +from baec.project import Project class ProjectsIDs: @@ -25,7 +30,11 @@ class ProjectsIDs: Class object to get a list of projects and Point IDs or to import measurements as a SettlementRodMeasurementSeries. """ - def __init__(self, credentials: str | PathLike[str] | ReadCsvBuffer[bytes] | ReadCsvBuffer[str], s3bucket: str = 'baec'): + def __init__( + self, + credentials: str | PathLike[str] | ReadCsvBuffer[bytes] | ReadCsvBuffer[str], + s3bucket: str = "baec", + ): """ Initializes a ProjectsIDs object. @@ -67,46 +76,62 @@ def __init__(self, credentials: str | PathLike[str] | ReadCsvBuffer[bytes] | Rea # Read the credentials file try: - dict_credentials = pd.read_csv(credentials).to_dict('records')[0] + dict_credentials = pd.read_csv(credentials).to_dict("records")[0] except pd.errors.ParserError as e: - raise IOError(f"Errors encountered while parsing contents of the credentials file: \n {e}") + raise IOError( + f"Errors encountered while parsing contents of the credentials file: \n {e}" + ) except FileNotFoundError as e: raise FileNotFoundError(e) - except ValueError as e: - raise ValueError('Wrong type of credentials file given, str | PathLike[str] | ReadCsvBuffer[bytes] | ReadCsvBuffer[str], Any valid string path is acceptable.') + except ValueError: + raise ValueError( + "Wrong type of credentials file given, str | PathLike[str] | ReadCsvBuffer[bytes] | ReadCsvBuffer[str], Any valid string path is acceptable." + ) # Create boto3 client and resource for connecting to AWS S3 s3c = boto3.client( - service_name='s3', - region_name='ca-central-1', - aws_access_key_id=dict_credentials['Access key ID'], - aws_secret_access_key=dict_credentials['Secret access key'] + service_name="s3", + region_name="ca-central-1", + aws_access_key_id=dict_credentials["Access key ID"], + aws_secret_access_key=dict_credentials["Secret access key"], ) s3r = boto3.resource( - service_name='s3', - region_name='ca-central-1', - aws_access_key_id=dict_credentials['Access key ID'], - aws_secret_access_key=dict_credentials['Secret access key'] + service_name="s3", + region_name="ca-central-1", + aws_access_key_id=dict_credentials["Access key ID"], + aws_secret_access_key=dict_credentials["Secret access key"], ) # Create the dictionary to translate the error codes. Get the error_codes file from the AWS S3 bucket dict_errors = {} try: - for line in s3r.Object(s3bucket, 'error_codes.txt').get()['Body'].read().decode('utf-8').split('\n'): - error_line = line.split(',') + for line in ( + s3r.Object(s3bucket, "error_codes.txt") + .get()["Body"] + .read() + .decode("utf-8") + .split("\n") + ): + error_line = line.split(",") dict_errors[int(error_line[0])] = { - 'basetime error': error_line[1], - 'description': error_line[2], - 'status message level': error_line[3] + "basetime error": error_line[1], + "description": error_line[2], + "status message level": error_line[3], } - except botocore.exceptions.ClientError as e: - raise ValueError('The AWS Access Key ID you provided does not exist in our records.') + except botocore.exceptions.ClientError: + raise ValueError( + "The AWS Access Key ID you provided does not exist in our records." + ) # Test the AWS S3 connection, load the list objects for projects the credential file can access try: - list_projects = s3c.list_objects(Bucket=s3bucket, Prefix='', Delimiter='/')['CommonPrefixes'] - except botocore.exceptions.ClientError as e: - raise ValueError('The AWS Access Key ID or Access Key Password you provided does not exist in our records.') + list_projects = s3c.list_objects(Bucket=s3bucket, Prefix="", Delimiter="/")[ + "CommonPrefixes" + ] + except botocore.exceptions.ClientError: + raise ValueError( + "The AWS Access Key ID or Access Key Password you provided does not exist in our records." + ) """ Iterate through all the folders in the S3 environment. The environment has the following folder structure: @@ -118,23 +143,35 @@ def __init__(self, credentials: str | PathLike[str] | ReadCsvBuffer[bytes] | Rea Read each object and write down the Point ID and Project name. """ for project in list_projects: + dic_projects_ids: Dict[str, List[str]] dic_projects_ids = {} - year_folders = s3c.list_objects(Bucket=s3bucket, Prefix=project['Prefix'], Delimiter='/') - for year in year_folders['CommonPrefixes']: - day_folders = s3c.list_objects(Bucket=s3bucket, Prefix=year['Prefix'], Delimiter='/') - for day in day_folders['CommonPrefixes']: - files = s3c.list_objects(Bucket=s3bucket, Prefix=day['Prefix'], Delimiter='/') - for file in files['Contents']: - if file['Key'] == day['Prefix']: + year_folders = s3c.list_objects( + Bucket=s3bucket, Prefix=project["Prefix"], Delimiter="/" + ) + for year in year_folders["CommonPrefixes"]: + day_folders = s3c.list_objects( + Bucket=s3bucket, Prefix=year["Prefix"], Delimiter="/" + ) + for day in day_folders["CommonPrefixes"]: + files = s3c.list_objects( + Bucket=s3bucket, Prefix=day["Prefix"], Delimiter="/" + ) + for file in files["Contents"]: + if file["Key"] == day["Prefix"]: continue - obj = s3r.Object(s3bucket, file['Key']) - json_dict = json.load(obj.get()['Body']) - if json_dict['Project'] not in dic_projects_ids: - dic_projects_ids[json_dict['Project']] = [] - for measurement in json_dict['Measurements']: - if measurement['Point ID'] not in dic_projects_ids[json_dict['Project']]: - dic_projects_ids[json_dict['Project']].append(measurement['Point ID']) - dic_user_projects_points[project['Prefix'][:-1]] = dic_projects_ids + obj = s3r.Object(s3bucket, file["Key"]) + json_dict = json.load(obj.get()["Body"]) + if json_dict["Project"] not in dic_projects_ids: + dic_projects_ids[json_dict["Project"]] = [] + for measurement in json_dict["Measurements"]: + if ( + measurement["Point ID"] + not in dic_projects_ids[json_dict["Project"]] + ): + dic_projects_ids[json_dict["Project"]].append( + measurement["Point ID"] + ) + dic_user_projects_points[project["Prefix"][:-1]] = dic_projects_ids # Initialize all attributes self.dic_projects = dic_user_projects_points @@ -143,7 +180,7 @@ def __init__(self, credentials: str | PathLike[str] | ReadCsvBuffer[bytes] | Rea self.s3bucket = s3bucket self.dict_errors = dict_errors - def get_users_projects_ids(self): + def get_users_projects_ids(self) -> dict: """ Return the dictionary containing every User as a key, then the Project as key, the value is a list of all the Point IDs. - Company/user @@ -152,7 +189,9 @@ def get_users_projects_ids(self): """ return self.dic_projects - def make_SettlementRodMeasurementSeries(self, company: str, project: str, rod_id: str): + def make_SettlementRodMeasurementSeries( + self, company: str, project: str, rod_id: str + ) -> SettlementRodMeasurementSeries: """ Make a SettlementRodMeasurementSeries: @@ -169,102 +208,179 @@ def make_SettlementRodMeasurementSeries(self, company: str, project: str, rod_id - Split error codes into multiple StatusMessage classes - Add all values to the SettlementRodMeasurement class """ - if company in self.dic_projects and project in self.dic_projects[company] and rod_id in self.dic_projects[company][project]: + if ( + company in self.dic_projects + and project in self.dic_projects[company] + and rod_id in self.dic_projects[company][project] + ): list_SettlementRodMeasurement = [] - year_folders = self.s3c.list_objects(Bucket=self.s3bucket, Prefix=company + '/', Delimiter='/') - for year in year_folders['CommonPrefixes']: - day_folders = self.s3c.list_objects(Bucket=self.s3bucket, Prefix=year['Prefix'], Delimiter='/') - for day in day_folders['CommonPrefixes']: - files = self.s3c.list_objects(Bucket=self.s3bucket, Prefix=day['Prefix'], Delimiter='/') - for file in files['Contents']: - if project not in file['Key']: + year_folders = self.s3c.list_objects( + Bucket=self.s3bucket, Prefix=company + "/", Delimiter="/" + ) + for year in year_folders["CommonPrefixes"]: + day_folders = self.s3c.list_objects( + Bucket=self.s3bucket, Prefix=year["Prefix"], Delimiter="/" + ) + for day in day_folders["CommonPrefixes"]: + files = self.s3c.list_objects( + Bucket=self.s3bucket, Prefix=day["Prefix"], Delimiter="/" + ) + for file in files["Contents"]: + if project not in file["Key"]: continue - obj = self.s3r.Object(self.s3bucket, file['Key']) - json_dict = json.load(obj.get()['Body']) - for measurement in json_dict['Measurements']: - if measurement['Point ID'] == rod_id: - list_epsg_codes = self.convert_epsg_string_to_list_int(measurement['Coordinate projection']) + obj = self.s3r.Object(self.s3bucket, file["Key"]) + json_dict = json.load(obj.get()["Body"]) + for measurement in json_dict["Measurements"]: + if measurement["Point ID"] == rod_id: + list_epsg_codes = self.convert_epsg_string_to_list_int( + measurement["Coordinate projection"] + ) if len(list_epsg_codes) == 2: - coordinate_reference_systems = CoordinateReferenceSystems( - pyproj.CRS.from_user_input(list_epsg_codes[0]), - pyproj.CRS.from_user_input(list_epsg_codes[1]) + coordinate_reference_systems = ( + CoordinateReferenceSystems( + pyproj.CRS.from_user_input( + list_epsg_codes[0] + ), + pyproj.CRS.from_user_input( + list_epsg_codes[1] + ), + ) ) elif len(list_epsg_codes) == 1: - coordinate_reference_systems = CoordinateReferenceSystems( - pyproj.CRS.from_user_input(list_epsg_codes[0]), - pyproj.CRS.from_user_input(list_epsg_codes[0]) + coordinate_reference_systems = ( + CoordinateReferenceSystems( + pyproj.CRS.from_user_input( + list_epsg_codes[0] + ), + pyproj.CRS.from_user_input( + list_epsg_codes[0] + ), + ) ) else: - coordinate_reference_systems = CoordinateReferenceSystems(None) - - if measurement['Comments Project'] == 'No comment': - status_messages = [StatusMessage( - code=7000, - description='Measurement approved', - level=StatusMessageLevel.OK - )] + coordinate_reference_systems = ( + CoordinateReferenceSystems(None, None) + ) + + if measurement["Comments Project"] == "No comment": + status_messages = [ + StatusMessage( + code=7000, + description="Measurement approved", + level=StatusMessageLevel.OK, + ) + ] else: status_messages = [] - error_string_list = measurement['Comments Project'].split(',') - error_integer_list = [int(num) for num in error_string_list] + error_string_list = measurement[ + "Comments Project" + ].split(",") + error_integer_list = [ + int(num) for num in error_string_list + ] for error_code in error_integer_list: - if self.dict_errors[error_code]['status message level'] == 'INFO': - status_messages.append(StatusMessage( - code=error_code, - description=self.dict_errors[error_code]['description'], - level=StatusMessageLevel.INFO - )) - elif self.dict_errors[error_code]['status message level'] == 'WARNING': - status_messages.append(StatusMessage( - code=error_code, - description=self.dict_errors[error_code]['description'], - level=StatusMessageLevel.WARNING - )) - elif self.dict_errors[error_code]['status message level'] == 'ERROR': - status_messages.append(StatusMessage( - code=error_code, - description=self.dict_errors[error_code]['description'], - level=StatusMessageLevel.ERROR - )) + if ( + self.dict_errors[error_code][ + "status message level" + ] + == "INFO" + ): + status_messages.append( + StatusMessage( + code=error_code, + description=self.dict_errors[ + error_code + ]["description"], + level=StatusMessageLevel.INFO, + ) + ) + elif ( + self.dict_errors[error_code][ + "status message level" + ] + == "WARNING" + ): + status_messages.append( + StatusMessage( + code=error_code, + description=self.dict_errors[ + error_code + ]["description"], + level=StatusMessageLevel.WARNING, + ) + ) + elif ( + self.dict_errors[error_code][ + "status message level" + ] + == "ERROR" + ): + status_messages.append( + StatusMessage( + code=error_code, + description=self.dict_errors[ + error_code + ]["description"], + level=StatusMessageLevel.ERROR, + ) + ) test_measurement = SettlementRodMeasurement( project=Project( - id_=json_dict['Project_uuid'], - name=json_dict['Project'] + id_=json_dict["Project_uuid"], + name=json_dict["Project"], ), device=MeasurementDevice( - id_=measurement['Locator One ID'], - qr_code=measurement['QR Code'] + id_=measurement["Locator One ID"], + qr_code=measurement["QR Code"], + ), + object_id=measurement["Point ID"], + date_time=datetime.strptime( + json_dict["Date"][:19], "%Y-%m-%d %H:%M:%S" ), - object_id=measurement['Point ID'], - date_time=datetime.strptime(json_dict['Date'][:19], '%Y-%m-%d %H:%M:%S'), coordinate_reference_systems=coordinate_reference_systems, - rod_top_x=measurement['Coordinates ARP']['X'] or float('nan'), - rod_top_y=measurement['Coordinates ARP']['Y'] or float('nan'), - rod_top_z=measurement['Coordinates ARP']['Z'] or float('nan'), - rod_length=measurement['Vertical Offset (Meter)'] or float('nan'), - rod_bottom_z=measurement['Height ground level']['Zuncorrected'] or float('nan'), - ground_surface_z=measurement['Coordinates ARP']['Soil level'] or float('nan'), + rod_top_x=measurement["Coordinates ARP"]["X"] + or float("nan"), + rod_top_y=measurement["Coordinates ARP"]["Y"] + or float("nan"), + rod_top_z=measurement["Coordinates ARP"]["Z"] + or float("nan"), + rod_length=measurement["Vertical Offset (Meter)"] + or float("nan"), + rod_bottom_z=measurement["Height ground level"][ + "Zuncorrected" + ] + or float("nan"), + ground_surface_z=measurement["Coordinates ARP"][ + "Soil level" + ] + or float("nan"), status_messages=status_messages, - temperature=measurement['Temperature (Celsius)'] or float('nan'), - voltage=measurement['Voltage Locator One (mV)'] or float('nan') + temperature=measurement["Temperature (Celsius)"] + or float("nan"), + voltage=measurement["Voltage Locator One (mV)"] + or float("nan"), ) list_SettlementRodMeasurement.append(test_measurement) elif company in self.dic_projects and project in self.dic_projects[company]: - raise ValueError(f"{company} is in the user list and {project} is in the project list, but not rod_id: {rod_id}") + raise ValueError( + f"{company} is in the user list and {project} is in the project list, but not rod_id: {rod_id}" + ) elif company in self.dic_projects: - raise ValueError(f"{company} is in the user list, but not the project: {project}") + raise ValueError( + f"{company} is in the user list, but not the project: {project}" + ) else: raise ValueError(f"{company} is not in the user list") return SettlementRodMeasurementSeries(list_SettlementRodMeasurement) @staticmethod - def convert_epsg_string_to_list_int(epsg_string: str): + def convert_epsg_string_to_list_int(epsg_string: str) -> list: """ Converts a Basetime coordinate projection to a list of string containing the EPSG codes. @@ -276,7 +392,7 @@ def convert_epsg_string_to_list_int(epsg_string: str): If the list has a length of 1, only the XY projection is present. If the list is empty, no projection could be transformed. """ - pattern = r'\((\d+)(?:,(\d+))?\)' + pattern = r"\((\d+)(?:,(\d+))?\)" matches = re.findall(pattern, epsg_string) if matches: diff --git a/tests/measurements/io/test_basetime_measurement.py b/tests/measurements/io/test_basetime_measurement.py index 0958405..87892af 100644 --- a/tests/measurements/io/test_basetime_measurement.py +++ b/tests/measurements/io/test_basetime_measurement.py @@ -1,23 +1,21 @@ import pandas as pd -import sys -sys.path.insert(0,'D:/BAEC_CEMS/BAEC/src') - from baec.measurements.basetime_measurements import ProjectsIDs -''' +""" Test script for the Basetime connection. Credentials file is not loaded in env, but stored locally testing: -Output when calling company, projects, object IDs -Output when calling a series of an object ID -''' - -with open('C:/Temp/test_class/cems_accessKeys.csv') as credfile: +""" +with open("cems_accessKeys.csv") as credfile: manage_project = ProjectsIDs(credfile) print(manage_project.get_users_projects_ids()) -test_series = manage_project.make_SettlementRodMeasurementSeries(company='Demo',project='Hansweert',rod_id='277-2') -print(test_series.to_dataframe()) \ No newline at end of file +test_series = manage_project.make_SettlementRodMeasurementSeries( + company="Demo", project="Hansweert", rod_id="277-2" +) +print(test_series.to_dataframe())