Skip to content

Commit

Permalink
Release 0.5.0 (#48)
Browse files Browse the repository at this point in the history
* Merge main into develop after release (#21)

* Release 0.3.0 (#20)

* precommit

* Fix delete cycle progress key and race condition (#24)

* Release 0.3.0 (#20)

* precommit

* add progress key to delete cycles

* Remove deprecated APIs, fix typos (#23)

* Update README.rst

* update license (#26)

* Updates to support 179d, includes creating property, downloading property reports from ESPM, and updating building search(#22)

* adding create_building and update_building methods
* modify search_buildings to provide appropriate cycle id
* adding client methods for creating extra data columns
* add pass throughs for file downloads
* updates for audit template workflow
* method to download property xlsx
---------

Co-authored-by: Alex Swindler <[email protected]>
Co-authored-by: Nicholas Long <[email protected]>
Co-authored-by: Nicholas Long <[email protected]>

* prep release

* increase timeout for server start

* increase timeout for server start (#31)

* cleanup before release (#32)

* configure seed to load small EEEJ dataset for integration test (#33)

* add analysis retrieve methods (#34)

* Added multiple cycle upload argument (#27)

* Added multiple cycle upload argument

* Responded to comments and fixed failing tests

* Converted to using kwargs

* Pre-commit

* Cleanup README.rst  (#35)

* cleanup readme

* populate description in pypi

* precommit fix

* Endpoint to download an Audit Template Report Submission and store in SEED (#36)

* start of AT report download method

* adding endpoint to download AT report submission and store in SEED

* add analysis retrieve methods (#34)

* Added multiple cycle upload argument (#27)

* Added multiple cycle upload argument

* Responded to comments and fixed failing tests

* Converted to using kwargs

* Pre-commit

* Cleanup README.rst  (#35)

* cleanup readme

* populate description in pypi

* precommit fix

* Endpoint to download an Audit Template Report Submission and store in SEED (#36)

* start of AT report download method

* adding endpoint to download AT report submission and store in SEED

* prep 0.4.1 (#37)

* Update CHANGELOG.rst

* update release instruction

* Update README.rst

* Update README.rst

* add new method to get AT submission metadata (#39)

* Update CHANGELOG.rst

* Update setup.cfg

* Added ESPM functions to py-seed client (#28)

* Added ESPM functions to py-seed client

* Cleaned up the code and put the downloads into its own
folder called "reports"

* Added openpyxl to requirements

* Fixing tests

* First pass at adding test

* First draft of finishing the test

* Finished get report template names test

* Started troubleshooting mypy errors

* Fixed remaining conflict

* Fixed integration test errors

* Fix mypy errors

* Fixed mypy for Python 3.10

* Precommit

* Fix unnecessary typing

* Fixed integration tests

---------

Co-authored-by: Nicholas Long <[email protected]>
Co-authored-by: Alex Swindler <[email protected]>

* prep version 0.4.3 (#41)

* Add PyPi release action (#42)

* add python 3.12

* update test versions

* add release connection and update instructions

* update precommit versions (#44)

* Add create organization and retrieve property cross cycle data (#45)

* add methods to support cross cycle property api

* cleanup

* cleanup

* remove prints

* fix mypy

* comment cleanup

* Add is_omitted column to column mapping profiles (#46)

* add code to enable client to read the isOmitted field in the CSV file, if present, and to pass it to the backend

* isOmitted -> is_omitted

* linter

* prep version 0.5.0 and added compatibility matrix to readme (#47)

* prep version 0.4.4 and added compatibility matrix to readme

* fix compatibility table

* fix title

* Update README.rst

* Update setup.cfg

---------

Co-authored-by: Nicholas Long <[email protected]>

* Update CHANGELOG.rst

---------

Co-authored-by: Alex Swindler <[email protected]>
Co-authored-by: Katherine Fleming <[email protected]>
Co-authored-by: Alex Chapin <[email protected]>
Co-authored-by: Caleb Rutan <[email protected]>
  • Loading branch information
5 people authored Sep 30, 2024
1 parent 9f6b851 commit 8daebec
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 27 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ exclude: |
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: check-added-large-files
Expand Down Expand Up @@ -35,12 +35,12 @@ repos:
"--ignore=E501,E402,W503,W504,E731",
]
- repo: https://github.com/pycqa/flake8
rev: 6.1.0
rev: 7.0.0
hooks:
- id: flake8
args: ["--ignore=E501,E402,W503,W504,E731,F401"]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
rev: v4.0.0-alpha.8
hooks:
- id: prettier
types_or: [css, yaml, markdown, html, scss, javascript]
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
Changelog
=========

0.5.0
-----

## What's Changed

* Add PyPi release action by @nllong in https://github.com/SEED-platform/py-seed/pull/42
* Update precommit versions by @nllong in https://github.com/SEED-platform/py-seed/pull/44
* Add create organization and retrieve property cross cycle data by @nllong in https://github.com/SEED-platform/py-seed/pull/45
* Add is_omitted column to column mapping profiles by @crutan in https://github.com/SEED-platform/py-seed/pull/46

**Full Changelog**: https://github.com/SEED-platform/py-seed/compare/v0.4.3...v0.5.0


0.4.3
-----

Expand Down
15 changes: 15 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ More information can be found here:
* https://github.com/SEED-platform/pyseed-examples


Compatibility Matrix
-------------

.. list-table::
:widths: 50 50
:header-rows: 1

* - py-SEED Version
- SEED Version
* - 0.5.0
- 3.1.0
* - 0.4.3
- 2.21.0 - 3.0.0

Stakeholders
-------------

Expand Down Expand Up @@ -133,5 +147,6 @@ This project is configured with GitHub Actions to automatically release to PyPi
* Once deployed to main, create a new tag in GitHub against main and copy the change log notes into the tag description
* GitHub Actions will automatically prepare the release the new version to PyPi
* Go to GitHub actions to approve the release
* After merging into main, then in the command line with the develop branch run `git merge origin main` and push the changes. This might have to be done with a person with elevated privileges to bypass the protected branch settings.

The GitHub Action required updates to the GitHub repo to only release on tags (https://github.com/SEED-platform/py-seed/settings/environments) after approval and on PyPi to add an authorized publisher (https://pypi.org/manage/project/py-SEED/settings/publishing/).
124 changes: 113 additions & 11 deletions pyseed/seed_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ def instance_information(self) -> dict:
info["username"] = self.client.username
return info

def get_users(self) -> dict:
"""Get a list of users visible to the current user
Returns:
dict: { "users": [{
"email": "[email protected]",
"user_id": 1
}]}
"""
users = self.client.list(endpoint="users")
return users

def get_organizations(self, brief: bool = True) -> dict:
"""Get a list organizations (that one is allowed to view)
Expand All @@ -179,6 +191,67 @@ def get_organizations(self, brief: bool = True) -> dict:
)
return orgs

def get_user_id(self, username: str) -> Union[None, int]:
"""Get the user ID for the given username
Args:
username (str): username to get the ID for
Returns:
int: user ID
"""
for user in self.get_users()['users']:
# compare string case insensitive
if user["email"].lower() == username.lower():
return user["user_id"]

return None

def create_organization(self, org_name: str) -> dict:
"""Create an organization with the given name
Args:
org_name (str): name of the organization to create
Returns:
dict: {
'status': 'success',
'message': 'Organization created',
'organization': {
'name': 'NEW ORG',
'org_id': 17,
'id': 17,
'number_of_users': 1,
'user_is_owner': True,
'user_role': 'owner',
'owners': [...],
'sub_orgs': [...],
'is_parent': True,
'parent_id': 17,
...
'display_units_eui': 'kBtu/ft**2/year',
'cycles': [...],
'created': '2024-06-13',
'mapquest_api_key': '',
}
}
"""
# see if the organization already exists
orgs = self.get_organizations()
for org in orgs:
if org["name"].lower() == org_name.lower():
raise Exception(f"Organization '{org_name}' already exists")

user_id = self.get_user_id(self.client.username)

payload = {
"user_id": user_id,
"organization_name": org_name,
}
org = self.client.post(endpoint="organizations", json=payload)
return org

def get_buildings(self) -> list[dict]:
total_qry = self.client.list(endpoint="properties", data_name="pagination", per_page=100)

Expand Down Expand Up @@ -584,13 +657,14 @@ def get_or_create_cycle(
end_date: date,
set_cycle_id: bool = False,
) -> dict:
"""Get or create a new cycle. If the cycle_name already exists, then it simply returns the existing cycle. However, if the cycle_name does not exist, then it will create a new cycle.
"""Get or create a new cycle. If the cycle_name already exists, then it simply returns
the existing cycle. However, if the cycle_name does not exist, then it will create a new cycle.
Args:
cycle_name (str): name of the cycle to get or create
start_date (date): MM/DD/YYYY of start date cycle
end_date (date): MM/DD/YYYY of end data for cycle
set_cycle_id (str): Set the object's cycle_id to the resulting cycle that is returned (either existing or newly created)
set_cycle_id (str): Set the object's cycle_id to the resulting cycle that is returned (either existing or newly created)
Returns:
dict: {
Expand Down Expand Up @@ -850,6 +924,7 @@ def create_or_update_column_mapping_profile(
"from_units": null,
"to_table_name": "PropertyState"
"to_field": "address_line_1",
"is_omitted": False
},
{
"from_field": "address1",
Expand All @@ -860,6 +935,7 @@ def create_or_update_column_mapping_profile(
...
]
The is_omitted mapping may be absent - it is treated as False if it is not present.
Returns:
dict: {
'id': 1
Expand Down Expand Up @@ -898,9 +974,9 @@ def create_or_update_column_mapping_profile_from_file(
) -> dict:
"""creates or updates a mapping profile. The format of the mapping file is a CSV with the following format:
Raw Columns, units, SEED Table, SEED Columns\n
PM Property ID, , PropertyState, pm_property_id\n
Building ID, , PropertyState, custom_id_1\n
Raw Columns, units, SEED Table, SEED Columns, Omit\n
PM Property ID, , PropertyState, pm_property_id, False\n
Building ID, , PropertyState, custom_id_1, False\n
...\n
This only works for 'Normal' column mapping profiles, that is, it does not work for
Expand Down Expand Up @@ -949,7 +1025,7 @@ def set_import_file_column_mappings(
)

def get_columns(self) -> dict:
"""get the list of columns.
"""Get the list of columns
Returns:
dict: {
Expand All @@ -961,7 +1037,8 @@ def get_columns(self) -> dict:
return result

def create_extra_data_column(self, column_name: str, display_name: str, inventory_type: str, column_description: str, data_type: str) -> dict:
""" create an extra data column. If column exists, skip
"""Create an extra data column. If column exists, skip
Args:
'column_name': 'project_type',
'display_name': 'Project Type',
Expand All @@ -979,7 +1056,6 @@ def create_extra_data_column(self, column_name: str, display_name: str, inventor
}
}
"""

# get extra data columns (only)
result = self.client.list(endpoint="columns")
columns = result['columns']
Expand All @@ -1005,7 +1081,8 @@ def create_extra_data_column(self, column_name: str, display_name: str, inventor
return result

def create_extra_data_columns_from_file(self, columns_csv_filepath: str) -> list:
""" create extra data columns from a csv file. if column exist, skip.
"""Create extra data columns from a csv file. if column exist, skip.
Args:
'columns_csv_filepath': 'path/to/file'
file is expected to have headers: column_name, display_name, column_description,
Expand Down Expand Up @@ -1156,11 +1233,13 @@ def get_meter_data(self, property_id, interval: str = 'Exact', excluded_meter_id

def start_save_data(self, import_file_id: int, multiple_cycle_upload: bool = False) -> dict:
"""start the background process to save the data file to the database.
This is the state before the mapping.
This is the state before the mapping. If multiple_cycle_upload is set to True, then the
importing file's year_ending column will be used to determine the cycle. Note that the
cycles must be created in SEED for the multiple cycle upload to work correctly
Args:
import_file_id (int): id of the import file to save
multiple_cycle_upload (bool): whether to use multiple cycle upload
multiple_cycle_upload (bool): whether to use multiple cycle upload.
Returns:
dict: progress key
Expand Down Expand Up @@ -1435,8 +1514,12 @@ def upload_and_match_datafile(
column_mapping_profile_name (str): Name of the column mapping profile to use
column_mappings_file (str): Mapping that will be uploaded to the column_mapping_profile_name
import_meters_if_exist (bool): If true, will import meters from the meter tab if they exist in the datafile. Defaults to False.
Kwargs:
datafile_type (str): Type of datafile
multiple_cycle_upload (bool): Whether to use multiple cycle upload. Defaults to False.
Returns:
dict: {
matching summary
Expand Down Expand Up @@ -1707,3 +1790,22 @@ def retrieve_analysis_result(self, analysis_id: int, analysis_view_id: int) -> d
url_args={"PK": analysis_id, "ANALYSIS_VIEW_PK": analysis_view_id},
include_org_id_query_param=True,
)

def get_cross_cycle_data(self, property_view_id: int) -> dict:
"""Retrieve the cross cycle data for a property. This is the data that
is shared across all the cycles used to populate a property's cross
cycle view.
Args:
property_view_id (int): Property view id
Returns:
dict: Cross cycle data for the property view
"""
return self.client.get(
None,
required_pk=False,
endpoint="properties_cross_cycle_data",
url_args={"PK": property_view_id},
include_org_id_query_param=True,
)
3 changes: 2 additions & 1 deletion pyseed/seed_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
'audit_template_building_xml': '/api/v3/audit_template/PK/get_building_xml',
'audit_template_submission': '/api/v3/audit_template/PK/get_submission',
'import_files_matching_results': '/api/v3/import_files/PK/matching_and_geocoding_results/',
'properties_cross_cycle_data': '/api/v3/properties/PK/links/',
'progress': '/api/v3/progress/PROGRESS_KEY/',
'properties_analyses': '/api/v3/properties/PK/analyses/',
'properties_meter_usage': '/api/v3/properties/PK/meter_usage/',
Expand Down Expand Up @@ -250,7 +251,7 @@ def _check_response(self, response, *args, **kwargs):
# this is a system matching response, which is okay. return the success flag of this
status_flag = response.json()['progress_data'].get('status', None)
error = status_flag not in ['not-started', 'success', 'parsing']
elif not any(key in ['results', 'readings', 'data', 'status', 'id', 'organizations', 'sha'] for key in response.json().keys()):
elif not any(key in ['results', 'readings', 'data', 'status', 'id', 'organizations', 'sha', 'users'] for key in response.json().keys()):
# In some cases there is not a 'status' field, so check if there are
# any other keys in the response that depict a success:
# readings - this comes from meters
Expand Down
21 changes: 12 additions & 9 deletions pyseed/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,18 @@ def read_map_file(mapfile_path):

# Open the mapping file and fill list
maplist = list()

for rowitem in map_reader:
maplist.append(
{
'from_field': rowitem[0],
'from_units': rowitem[1],
'to_table_name': rowitem[2],
'to_field': rowitem[3],
}
)
data = {
"from_field": rowitem[0],
"from_units": rowitem[1],
"to_table_name": rowitem[2],
"to_field": rowitem[3],
}
try:
data["is_omitted"] = True if rowitem[4].lower().strip() == "true" else False
except IndexError:
data["is_omitted"] = False

maplist.append(data)

return maplist
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name=py-seed
version=0.4.3
version=0.5.0
description=A Python API client for the SEED Platform
author=Nicholas Long, Katherine Fleming, Fable Turas, Paul Munday
author_email[email protected], [email protected], [email protected]
Expand Down
2 changes: 1 addition & 1 deletion tests/data/test-seed-data-mappings.csv
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Sq. Ft,ft**2,PropertyState,gross_floor_area
Total GHG Emissions Intensity,kgCO2e/ft**2/year,PropertyState,total_ghg_emissions_intensity
Site EUI,kBtu/ft**2/year,PropertyState,site_eui
PM Release Date,,PropertyState,release_date
Year Ending,,PropertyState,Year Ending Excel
Year Ending,,PropertyState,year_ending
GHGI Target,,PropertyState,GHGI Target
GHGI Target Year,,PropertyState,GHGI Target Year
EUI Target,,PropertyState,EUI Target
Expand Down
Binary file not shown.
Loading

0 comments on commit 8daebec

Please sign in to comment.