california-midasapi 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+
2
+
3
+ MIT License
4
+
5
+ Copyright (c) 2024 MattDahEpic
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.1
2
+ Name: california-midasapi
3
+ Version: 0.0.2
4
+ Summary: Python wrapper for California's energy price API MIDAS.
5
+ Author-email: MattDahEpic <matt+california-midasapipypi@mattdahepic.com>
6
+ License: MIT
7
+ Keywords: california,api,energy,midas,price
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Scientific/Engineering
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE.md
15
+ Requires-Dist: requests
16
+ Requires-Dist: PyJWT
17
+ Requires-Dist: python-dateutil
18
+
19
+ # california-midasapi
20
+ A Python wrapper for the California Energy Comission (CEC)'s Market Informed Demand Automation Server (MIDAS) energy price API.
21
+ This API lets you get info about energy prices in California from utilities regulated by the CEC, which is all the big ones (PG&E, SCE, SDG&E, SMUD, etc.) and some smaller municipal utilities. If you have a Rate Identification Number (RIN) QA Code on your electric bill you can use this API to get your electricity price in real time.
22
+
23
+
24
+ ## Usage
25
+ 1. Start by registering an account with MIDAS. There is no webpage for this so I have provided a helper. You only have to register an account once.
26
+ ```python
27
+ from midas.authentication import Midas as Auth
28
+ response = Auth.register("username", "password", "email@email.email", "Full Name")
29
+ print(response)
30
+ ```
31
+ You should see `User account for username was successfully created. A verification email has been sent to email@email.email. Please click the link in the email in order to start using the API.`.
32
+ Click the link in the email, then you can use the rest of the API as described below.
33
+
34
+ 2. Create a `Midas` object:
35
+ ```python
36
+ from midas import Midas
37
+ midas = Midas("username", "password")
38
+ ```
39
+
40
+ 3. Access the API methods using this object:
41
+ ```python
42
+ # Get basic info about all rates
43
+ from midas.ratelist import RINFilter
44
+ rates = midas.GetAvailableRates(RINFilter.TARIFF)
45
+ print(rates) # ~40k+ items at writing
46
+
47
+ # Get specific info about one rate
48
+ ratedata = midas.GetRateInfo('USCA-SMSM-AD00-0000')
49
+ print(ratedata)
50
+ # Get the currently applicable value from a rate (local filtering)
51
+ print(ratedata.GetCurrentTariffs())
52
+ ```
53
+
54
+
55
+ ## Contributing
56
+ Contributions are welcome, please submit a PR!
57
+
58
+
59
+ ## More Information & Thanks
60
+ More info about MIDAS can be found at https://midasapi.energy.ca.gov/
61
+ Thank you to the CEC for providing example code at https://github.com/morganmshep/MIDAS-Python-Repository
62
+
63
+ ## Projects using this library
64
+
65
+
66
+ If you have a project using this library, we'd love to hear about it! Let us know and we'll add it to this list.
@@ -0,0 +1,11 @@
1
+ midas/Midas.py,sha256=J4oRLls86UgxTKQgmnLhVEeM0l4UE2vHhBuuZeJQHzc,269
2
+ midas/__init__.py,sha256=4N0thIuwM51ikPwMLbrVGes_zYK8HWwML-OBPMDLYBI,55
3
+ midas/authentication.py,sha256=DlwpcCp8Y1dLRf90FspmERR4ccpgsVq-ojO_yUpV5F8,1902
4
+ midas/internal.py,sha256=93YtA3Zps-AWORc3461B5C2LuE6BPcrpUJ7TZzK69k4,1690
5
+ midas/ratelist.py,sha256=u_-Zq4g1lm_cYUUUS23TVAKbGgQwlYaoVOiep6osJtE,1644
6
+ midas/types.py,sha256=vReS6-kIxCJHL_qoz4y05v2PPWhNevTL57RHGvJS7Is,1478
7
+ california_midasapi-0.0.2.dist-info/LICENSE.md,sha256=NZkJaGFEUwQ-PTuFGV-5FL9nwGnjecf5ET_yvp8iiHg,1070
8
+ california_midasapi-0.0.2.dist-info/METADATA,sha256=iM_N4pE5cmpinRb4Slo6Zx4AfKGtcXCd8ZnmjJqZejc,2671
9
+ california_midasapi-0.0.2.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
10
+ california_midasapi-0.0.2.dist-info/top_level.txt,sha256=n8dkPfm_amUh86KWKdyQFSehtTdf2SnUDp54AX6RcE0,6
11
+ california_midasapi-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (72.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ midas
midas/Midas.py ADDED
@@ -0,0 +1,9 @@
1
+ from midas.authentication import Midas as MidasAuth
2
+ from midas.ratelist import Midas as MidasRateList
3
+
4
+ class Midas(MidasAuth, MidasRateList):
5
+ """
6
+ Python API for California's energy price database MIDAS.
7
+
8
+ Main class, imports most things from modules.
9
+ """
midas/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from midas.Midas import Midas, MidasAuth, MidasRateList
@@ -0,0 +1,49 @@
1
+ import requests
2
+ import json
3
+ import base64
4
+ from midas.internal import Midas as Internal
5
+
6
+ class Midas(Internal):
7
+ def __init__(self, username: str, password: str):
8
+ """
9
+ Create a new API wrapper instance using the given credentials.
10
+
11
+ Credentials are required, if you don't have an account use the static `register` method
12
+ """
13
+ self.username = username
14
+ self.password = password
15
+ self.auth_token: str = None
16
+
17
+ @staticmethod
18
+ def register(username: str, password: str, email: str, fullname: str, organization: str = None) -> str:
19
+ """
20
+ Create a new account with the MIDAS server.
21
+
22
+
23
+ """
24
+ username64 = str(base64.b64encode(username.encode("utf-8")), "utf-8")
25
+ password64 = str(base64.b64encode(password.encode("utf-8")), "utf-8")
26
+ email64 = str(base64.b64encode(email.encode("utf-8")), "utf-8")
27
+ fullname64 = str(base64.b64encode(fullname.encode("utf-8")), "utf-8")
28
+
29
+ registration_info = {
30
+ "username":username64,
31
+ "password":password64,
32
+ "emailaddress":email64,
33
+ "fullname":fullname64
34
+ }
35
+
36
+ if (organization is not None):
37
+ organization64 = str(base64.b64encode(organization.encode("utf-8")), "utf-8")
38
+ registration_info["organization"] = organization64
39
+
40
+ url = 'https://midasapi.energy.ca.gov/api/registration'
41
+ headers = {"Content-Type":"application/json"}
42
+
43
+ response = requests.post(url, data=json.dumps(registration_info), headers=headers)
44
+
45
+ #Prints below will return 200 response for successful call
46
+ print(response)
47
+ #Response text should be: 'User account for your_user_name was successfully created. A verification email has been sent to your_email. Please click the link in the email in order to start using the API.'
48
+ print(response.text)
49
+
midas/internal.py ADDED
@@ -0,0 +1,41 @@
1
+ from typing import Literal
2
+ import requests
3
+ import base64
4
+ import jwt
5
+ import time
6
+
7
+ class Midas():
8
+ def __request(self, method: Literal['GET', 'POST'], url: str):
9
+ """Preform a request with the stored auth token and return the body."""
10
+ if (self.auth_token is None or not Midas.__isTokenValid(self.auth_token)):
11
+ self.__loginAndStore(self.username, self.password)
12
+ headers = {
13
+ 'Accept': 'application/json',
14
+ 'User-Agent': 'california-midasapi.py',
15
+ 'Authorization': "Bearer " + self.auth_token,
16
+ }
17
+ response = requests.request(method, url, headers=headers)
18
+ if (not response.ok):
19
+ raise f"Error preforming request: {response.status_code} {response.text}"
20
+ return response.text
21
+
22
+ def __loginAndStore(self, username: str, password: str):
23
+ '''
24
+ Logs in with a username and password, storing the JWT token for use in future calls.
25
+ '''
26
+ credentials = username + ":" + password
27
+ credentials_encodedBytes = base64.b64encode(credentials.encode("utf-8"))
28
+
29
+ headers = {b'Authorization': b'BASIC ' + credentials_encodedBytes}
30
+ url = 'https://midasapi.energy.ca.gov/api/token'
31
+
32
+ response = requests.get(url,headers=headers)
33
+
34
+ self.auth_token = response.headers['Token']
35
+
36
+ @staticmethod
37
+ def __isTokenValid(token: str) -> bool:
38
+ """Return if the provided token is still valid"""
39
+ decoded = jwt.decode(token, algorithms=["HS256"], options={"verify_signature": False})
40
+ future = time.time() + 120 # 2 minutes from now
41
+ return decoded["exp"] > future # expires more than set time from now
midas/ratelist.py ADDED
@@ -0,0 +1,46 @@
1
+ from midas.internal import Midas as Internal
2
+ from midas.types import RateListItem, ValueInfoItem, RateInfo
3
+ from typing import Literal
4
+ from enum import Enum
5
+ import json
6
+
7
+ class RINFilter(Enum):
8
+ """Filter options for the RIN list."""
9
+ ALL = 0
10
+ TARIFF = 1
11
+ GHG_EMISSION = 2
12
+ FLEX_ALERT = 3
13
+
14
+ class Midas(Internal):
15
+
16
+ def GetAvailableRates(self, signaltype: RINFilter) -> 'list[RateListItem]':
17
+ """
18
+ Get all the available rates.
19
+ """
20
+
21
+ def __rateListItemHook(dict):
22
+ return RateListItem(**dict)
23
+
24
+ url = 'https://midasapi.energy.ca.gov/api/valuedata?signaltype=' + str(signaltype.value)
25
+ response = self.__request('GET', url)
26
+ return (json.loads(response, object_hook=__rateListItemHook))
27
+
28
+ def GetRateInfo(self, rateID: str, queryType: Literal['alldata', 'realtime'] = 'alldata') -> RateInfo:
29
+ """
30
+ Returns data about a given a rate.
31
+ """
32
+
33
+ def __rateInfoObjectHook(dict):
34
+ # this has to handle both the parent object and its children
35
+ if "DateStart" in dict:
36
+ return ValueInfoItem(**dict)
37
+ elif "RateID" in dict:
38
+ return RateInfo(**dict)
39
+ else:
40
+ raise "Invalid object type for __rateInfoObjectHook"
41
+
42
+ # TODO what does queryType=realtime even do? all properties are None
43
+ url = 'https://midasapi.energy.ca.gov/api/valuedata?id=' + rateID + '&querytype=' + queryType
44
+ pricing_response = self.__request('GET', url)
45
+
46
+ return (json.loads(pricing_response, object_hook=__rateInfoObjectHook))
midas/types.py ADDED
@@ -0,0 +1,55 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Optional
3
+ from datetime import datetime
4
+ from dateutil import parser
5
+
6
+ @dataclass
7
+ class RateListItem():
8
+ RateID: str
9
+ SignalType: str
10
+ Description: str
11
+ LastUpdated: Optional[str] = None
12
+
13
+ @dataclass
14
+ class ValueInfoItem:
15
+ ValueName: str
16
+ DateStart: str
17
+ DateEnd: str
18
+ DayStart: str
19
+ DayEnd: str
20
+ TimeStart: str
21
+ TimeEnd: str
22
+ value: float
23
+ Unit: str
24
+
25
+ def GetStart(self) -> datetime:
26
+ """Get the start of this tariff as a python datetime"""
27
+ return parser.parse(f"{self.DateStart} {self.TimeStart} -07:00")
28
+
29
+
30
+ def GetEnd(self) -> datetime:
31
+ """Get the end of this tariff as a python datetime"""
32
+ return parser.parse(f"{self.DateEnd} {self.TimeEnd} -07:00")
33
+
34
+ @dataclass
35
+ class RateInfo:
36
+ RateID: str
37
+ SystemTime_UTC: str
38
+ RateName: str
39
+ RateType: str
40
+ Sector: str
41
+ API_Url: str
42
+ RatePlan_Url: str
43
+ EndUse: str
44
+ AltRateName1: str
45
+ AltRateName2: str
46
+ SignupCloseDate: str
47
+ ValueInformation: list[ValueInfoItem] = field(default_factory=list)
48
+ """The list of tariffs"""
49
+
50
+ def GetCurrentTariffs(self) -> list[ValueInfoItem]:
51
+ """Gets all tariffs that are currently active"""
52
+
53
+ # ValueInformation where getstart < now < getend
54
+ now = datetime.now().timestamp()
55
+ return list(filter(lambda t: t.GetStart().timestamp() < now and t.GetEnd().timestamp() > now, self.ValueInformation))