Skip to content

Commit

Permalink
Use pep8 standards. Format code with flake8 and isort
Browse files Browse the repository at this point in the history
  • Loading branch information
lawrencefoley committed Sep 18, 2021
1 parent df39912 commit 3d4beaa
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 67 deletions.
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
max-line-length=127
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pip install git+git://github.com/lawrencefoley/evergy.git
```

## Usage

```python
# Import the package
from kcpl.kcpl import KCPL
Expand All @@ -17,10 +18,26 @@ kcpl.login()

# Get a list of daily readings
# Note, there is more data available such as 'cost' and 'avgTemp'
data = kcpl.getUsage()
data = kcpl.get_usage()
logging.info("Last usage reading: " + str(data[-1]))
logging.info("Last usage reading: " + str(data[-1]["usage"]))

# End your session by logging out
kcpl.logout()
```

## Development
### Code Formatting
Install the dev dependencies and run `isort` and `flake8` to properly format the code.
```bash
pip install -r requirements_dev.txt
isort kcpl/
flake8 kcpl/
```

### Release New Version
```bash
git commit -m "Bump version"
git tag -a v1.0.1 -m "v1.0.1"
git push --tags
```
2 changes: 1 addition & 1 deletion credentials.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"username": "<username>",
"password": "<password>"
}
}
98 changes: 60 additions & 38 deletions kcpl/kcpl.py
Original file line number Diff line number Diff line change
@@ -1,83 +1,105 @@
import json
import logging
from datetime import date, timedelta

import requests
from bs4 import BeautifulSoup
from datetime import date, timedelta
import logging

logging.basicConfig(format="%(asctime)s - %(levelname)s - %(name)s - - %(message)s", level=logging.INFO)
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(name)s - - %(message)s", level=logging.INFO
)

day_before_yesterday = date.today() - timedelta(days=2)
yesterday = date.today() - timedelta(days=1)
today = date.today()

class KCPL():

class KCPL:
def __init__(self, username, password):
self.loggedIn = False
self.logged_in = False
self.session = None
self.username = username
self.password = password
self.accountNumber = None
self.premiseId = None
self.loginUrl = "https://www.evergy.com/log-in"
self.logoutUrl = "https://www.evergy.com/logout"
self.accountSummaryUrl = "https://www.evergy.com/ma/my-account/account-summary"
self.accountDashboardUrl = "https://www.evergy.com/api/account/{accountNum}/dashboard/current"
self.usageDataUrl = "https://www.evergy.com/api/report/usage/{premiseId}?interval={query_scale}&from={start}&to={end}"
self.account_number = None
self.premise_id = None
self.login_url = "https://www.evergy.com/log-in"
self.logout_url = "https://www.evergy.com/logout"
self.account_summary_url = (
"https://www.evergy.com/ma/my-account/account-summary"
)
self.account_dashboard_url = (
"https://www.evergy.com/api/account/{accountNum}/dashboard/current"
)
self.usageDataUrl = "https://www.evergy.com/api/report/usage/{premise_id}?interval={query_scale}&from={start}&to={end}"

def login(self):
self.session = requests.Session()
logging.info("Logging in with username: " + self.username)
login_form = self.session.get(self.loginUrl)
login_form_soup = BeautifulSoup(login_form.text, 'html.parser')
login_form = self.session.get(self.login_url)
login_form_soup = BeautifulSoup(login_form.text, "html.parser")
csrf_token = login_form_soup.select(".login-form > input")[0]["value"]
csrf_token_name = login_form_soup.select(".login-form > input")[0]["name"]
loginPayload = {"Username": str(self.username), "Password": str(self.password), csrf_token_name: csrf_token}
r = self.session.post(url=self.loginUrl, data=loginPayload, allow_redirects=False)
login_payload = {
"Username": str(self.username),
"Password": str(self.password),
csrf_token_name: csrf_token,
}
r = self.session.post(
url=self.login_url, data=login_payload, allow_redirects=False
)
logging.debug("Login response: " + str(r.status_code))
r = self.session.get(self.accountSummaryUrl)
soup = BeautifulSoup(r.text, 'html.parser')
accountData = soup.find_all('script', id='account-landing-data')
if len(accountData) == 0:
self.loggedIn = False
r = self.session.get(self.account_summary_url)
soup = BeautifulSoup(r.text, "html.parser")
account_data = soup.find_all("script", id="account-landing-data")
if len(account_data) == 0:
self.logged_in = False
else:
self.accountNumber = json.loads(accountData[0].contents[0])["accountNumber"]
dashboardData = self.session.get(self.accountDashboardUrl.format(accountNum=self.accountNumber)).json()
self.premiseId = dashboardData["addresses"][0]["premiseId"]
self.loggedIn = self.accountNumber is not None and self.premiseId is not None
self.account_number = json.loads(account_data[0].contents[0])[
"accountNumber"
]
dashboard_data = self.session.get(
self.account_dashboard_url.format(accountNum=self.account_number)
).json()
self.premise_id = dashboard_data["addresses"][0]["premiseId"]
self.logged_in = (
self.account_number is not None and self.premise_id is not None
)

def logout(self):
logging.info("Logging out")
self.session.get(url=self.logoutUrl)
self.session.get(url=self.logout_url)
self.session = None
self.loggedIn = False

self.logged_in = False

def getUsage(self, start=day_before_yesterday, end=yesterday, query_scale="d"):
def get_usage(self, start=day_before_yesterday, end=yesterday, query_scale="d"):
"""Fetches all usage data from the given period."""
if not self.loggedIn:
if not self.logged_in:
logging.error("Must login first")
return
url = self.usageDataUrl.format(premiseId=self.premiseId, query_scale=query_scale, start=start, end=end)
url = self.usageDataUrl.format(
premise_id=self.premise_id, query_scale=query_scale, start=start, end=end
)
logging.debug("fetching {}".format(url))
usageData = self.session.get(url).json()
return usageData["data"]
usage_data = self.session.get(url).json()
return usage_data["data"]


def getCreds():
with open("../credentials.json", 'r') as f:
def get_creds():
with open("../credentials.json", "r") as f:
return json.loads(f.read())


if __name__ == "__main__":
# Read the credentials.json file
creds = getCreds()
creds = get_creds()
username = creds["username"]
password = creds["password"]

kcpl = KCPL(username, password)
kcpl.login()

# Get a list of daily readings

data = kcpl.getUsage()
data = kcpl.get_usage()
logging.info("Last usage data: " + str(data[-1]))
logging.info("Last usage reading: " + str(data[-1]["usage"]))

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
beautifulsoup4==4.7.1
requests==2.21.0
requests==2.21.0
2 changes: 2 additions & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
isort==5.9.3
flake8==3.9.2
45 changes: 19 additions & 26 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# Always prefer setuptools over distutils
from setuptools import setup, find_packages
# To use a consistent encoding
from codecs import open
from os import path

from setuptools import find_packages, setup

here = path.abspath(path.dirname(__file__))

# Get the long description from the README file
with open(path.join(here, 'README.md'), encoding='utf-8') as f:
with open(path.join(here, "README.md"), encoding="utf-8") as f:
long_description = f.read()

setup(
name='kcpl',
version='0.0.2',
description='A utility that reads electric utility meter data from KCPL.com',
name="kcpl",
version="0.0.2",
description="A utility that reads electric utility meter data from KCPL.com",
long_description=long_description,
url='https://github.com/lawrencefoley/kcpl',
author='Lawrence Foley',
author_email='[email protected]',

url="https://github.com/lawrencefoley/kcpl",
author="Lawrence Foley",
author_email="[email protected]",
# Classifiers help users find your project by categorizing it.
#
# For a list of valid classifiers, see
Expand All @@ -28,26 +28,21 @@
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
'Development Status :: 3 - Alpha',

"Development Status :: 3 - Alpha",
# Indicate who your project is intended for
'Intended Audience :: Developers',
'Topic :: Utilities',

"Intended Audience :: Developers",
"Topic :: Utilities",
# Pick your license as you wish
'License :: OSI Approved :: MIT License',

"License :: OSI Approved :: MIT License",
# Specify the Python versions you support here. In particular, ensure
# that you indicate whether you support Python 2, Python 3 or both.
'Programming Language :: Python :: 3.9',
"Programming Language :: Python :: 3.9",
],

# This field adds keywords for your project which will appear on the
# project page. What does your project relate to?
#
# Note that this is a string of words separated by whitespace, not a list.
keywords='kcpl evergy api utilities kansas-city', # Optional

keywords="kcpl evergy api utilities kansas-city", # Optional
# You can just specify package directories manually here if your project is
# simple. Or you can use find_packages().
#
Expand All @@ -58,21 +53,19 @@
# py_modules=["my_module"],
#
packages=find_packages(exclude=[]), # Required

# This field lists other packages that your project depends on to run.
# Any package you put here will be installed by pip when your project is
# installed, so they must be valid existing projects.
#
# For an analysis of "install_requires" vs pip's requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=['beautifulsoup4', 'requests'], # Optional

install_requires=["beautifulsoup4", "requests"], # Optional
# If there are data files included in your packages that need to be
# installed, specify them here.
#
# If using Python 2.6 or earlier, then these have to be included in
# MANIFEST.in as well.
package_data={ # Optional
'credentials': ['credentials.json'],
}
)
"credentials": ["credentials.json"],
},
)

0 comments on commit 3d4beaa

Please sign in to comment.