Monday, December 5, 2022

Python - Project - Workout Tracking with Google Sheet (Day 38)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


Goal



Build a program which takes your text input (normal english sentence).
Then use Nutritionix API to turn that text into precise nutrition analysis.
In the end, use Sheety API to insert those analysis to your Google Sheets.




Ex:
import requests
import os
import datetime
from dotenv import load_dotenv

# Load Environmental Variables from .env
load_dotenv()

# Nutritionix
NUTRITIONIX_ENDPOINT = "https://trackapi.nutritionix.com/v2/natural/exercise"
NUTRITIONIX_APP_ID = os.environ.get("NUTRITIONIX_APP_ID")
NUTRITIONIX_API_KEY = os.environ.get("NUTRITIONIX_API_KEY")
GNEDER = 'male'
WEIGHT_KG = 88
HEIGHT_CM = 183
AGE = 18

# Sheety
SHEETY_ENDPOINT = os.environ.get("SHETTY_ENDPOINT")
SHETTY_TOKEN = os.environ.get("SHETTY_TOKEN")


def get_exercise_analysis(query_text):
    # Setup authentication parts in headers
    headers = {
        "x-app-id": NUTRITIONIX_APP_ID,
        "x-app-key": NUTRITIONIX_API_KEY,
    }

    # Prepare Request Payload for API
    payload = {
        "query": query_text,
        'gender': GNEDER,
        'weight_kg': WEIGHT_KG,
        'height_cm': HEIGHT_CM,
        'age': AGE
    }

    # Sends a POST request.
    response = requests.post(NUTRITIONIX_ENDPOINT,json=payload, headers=headers)
    # Raises HTTPError, if one occurred.
    response.raise_for_status()
    # Returns the json-encoded content of a response, if any.
    res = response.json()
    return res['exercises']


def insert_data_to_google_sheet(name: str, duration: float, calories: float):
    # Setup authentication parts in headers
    headers = {
        "Authorization": f"Bearer {SHETTY_TOKEN}",
    }

    # Prepare Request Payload for API
    payload = {
        "workout": {
            "date": datetime.datetime.now().strftime("%d/%m/%Y"),
            "time": datetime.datetime.now().strftime("%X"),
            "exercise": name.title(),
            "duration": duration,
            "calories": calories
        }
    }

    # Sends a POST request.
    response = requests.post(SHEETY_ENDPOINT, json=payload, headers=headers)
    # Raises HTTPError, if one occurred.
    response.raise_for_status()

# Ask users to input
exercise_text = input("Tell me which exercises you did: ")

# Call Nutritionix web API to get the analysis based on the exercise text
exercise_data_list = get_exercise_analysis(exercise_text)

# Loop through all exercise data
for exercise in exercise_data_list:
    # Call Sheety web API to insert rows to our Google Sheet
    insert_data_to_google_sheet(
        exercise['name'],
float(exercise['duration_min']),
float(exercise['nf_calories'])
)


Friday, December 2, 2022

Python - API Requests and Headers (Day 37)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


Goal



Using Pixela to practice all HTTP Methods and headers for authentication.


Ex: Create an account (POST request)
import requests
import os
from dotenv import load_dotenv

# Load Environmental Variables from .env
load_dotenv()

PIXELA_ENDPOINT = "https://pixe.la/v1/"
USERNAME = os.environ.get("USERNAME")
TOKEN = os.environ.get("TOKEN")

# Prepare Request Payload for API
payload = {
    "username": USERNAME,
    "token": TOKEN,
    "agreeTermsOfService": "yes",
    "notMinor": "yes"
}

# Sends a POST request.
response = requests.post(f"{PIXELA_ENDPOINT}users", json=payload)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Print the Content of Response
print(response.text)


Ex: Create a graph (POST request)
import requests
import os
from dotenv import load_dotenv

# Load Environmental Variables from .env
load_dotenv()

PIXELA_ENDPOINT = "https://pixe.la/v1/"
USERNAME = os.environ.get("USERNAME")
TOKEN = os.environ.get("TOKEN")
GRAPH_ID = os.environ.get("GRAPH_ID")
GRAPH_NAME = os.environ.get("GRAPH_NAME")

# Prepare headers for API
headers = {
    "X-USER-TOKEN": TOKEN
}

# Prepare Request Payload for API
payload = {
    "id": GRAPH_ID,
    "name": GRAPH_NAME,
    "unit": "kilogram",
    "type": "float",
    "color": "ajisai",
}

# Sends a POST request.
response = requests.post(f"{PIXELA_ENDPOINT}users/{USERNAME}/graphs",
json=payload, headers=headers)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Print the Content of Response
print(response.text)


Ex: Get the graph (GET request)
import requests
import os
from dotenv import load_dotenv

# Load Environmental Variables from .env
load_dotenv()

PIXELA_ENDPOINT = "https://pixe.la/v1/"
USERNAME = os.environ.get("USERNAME")
GRAPH_ID = os.environ.get("GRAPH_ID")

# Sends a GET request.
response = requests.get(f"{PIXELA_ENDPOINT}users/{USERNAME}/graphs/{GRAPH_ID}")
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Print the Content of Response
print(response.text)

# https://pixe.la/v1/users/frank-exp/graphs/graph-exp.html


Ex: Add a pixel to the graph (POST request)
import requests
import os
import datetime
from dotenv import load_dotenv

# Load Environmental Variables from .env
load_dotenv()

PIXELA_ENDPOINT = "https://pixe.la/v1/"
USERNAME = os.environ.get("USERNAME")
TOKEN = os.environ.get("TOKEN")
GRAPH_ID = os.environ.get("GRAPH_ID")

# Prepare headers for API
headers = {
    "X-USER-TOKEN": TOKEN
}

# Prepare Request Payload for API
payload = {
    "date":  datetime.datetime.now().strftime("%Y%m%d"),
    "quantity": "71.5"
}

# Sends a POST request.
response = requests.post(
    f"{PIXELA_ENDPOINT}users/{USERNAME}/graphs/{GRAPH_ID}",
json=payload, headers=headers)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Print the Content of Response
print(response.text)


Ex: Update the existing pixel of the graph (PUT request)
import requests
import os
import datetime
from dotenv import load_dotenv

# Load Environmental Variables from .env
load_dotenv()

PIXELA_ENDPOINT = "https://pixe.la/v1/"
USERNAME = os.environ.get("USERNAME")
TOKEN = os.environ.get("TOKEN")
GRAPH_ID = os.environ.get("GRAPH_ID")

# Prepare headers for API
headers = {
    "X-USER-TOKEN": TOKEN
}

# Prepare Request Payload for API
payload = {
    "quantity": "100.0"
}

now_in_pixela_format = datetime.datetime.now().strftime("%Y%m%d")

# Sends a PUT request.
response = requests.put(
    f"{PIXELA_ENDPOINT}users/{USERNAME}/graphs/{GRAPH_ID}/{now_in_pixela_format}",
json=payload, headers=headers)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Print the Content of Response
print(response.text)


Ex: Delete the existing pixel of the graph (DELETE request)
import requests
import os
import datetime
from dotenv import load_dotenv

# Load Environmental Variables from .env
load_dotenv()

PIXELA_ENDPOINT = "https://pixe.la/v1/"
USERNAME = os.environ.get("USERNAME")
TOKEN = os.environ.get("TOKEN")
GRAPH_ID = os.environ.get("GRAPH_ID")

# Prepare headers for API
headers = {
    "X-USER-TOKEN": TOKEN
}

now_in_pixela_format = datetime.datetime.now().strftime("%Y%m%d")

# Sends a DELETE request.
response = requests.delete(
    f"{PIXELA_ENDPOINT}users/{USERNAME}/graphs/{GRAPH_ID}/20221202",
headers=headers)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Print the Content of Response
print(response.text)


Wednesday, November 30, 2022

Python - Project - Stock Trading News App (Day 36)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


Goal



If the target stock price is up or down 5% between yesterday, then get the top three relating news and send those result to your phone number.




Web API List
    Twilio

Ex: 
import requests
import os
from twilio.rest import Client
from dotenv import load_dotenv

# Load Environmental Variables from .env
load_dotenv()

# Stock Price Web API
ALPHA_VANTAGE_URL = "https://www.alphavantage.co/query"
ALPHA_VANTAGE_API_KEY = os.environ.get("ALPHA_VANTAGE_API_KEY")

# News Web API
NEWS_API_URL = "https://newsapi.org/v2/everything"
NEWS_API_KEY = os.environ.get("NEWS_API_KEY")

# SMS Web API
TWILIO_ACCOUNT_SID = os.environ.get("TWILIO_ACCOUNT_SID")
TWILIO_AUTH_TOKEN = os.environ.get("TWILIO_AUTH_TOKEN")

# Company to monitor
COMPANY = "AXSM"

def get_stock_price_history_data_by_company(company):
    # Prepare parameters for API
    parameters = {
        "function": "TIME_SERIES_DAILY_ADJUSTED",
        "symbol": company,
        "apikey": ALPHA_VANTAGE_API_KEY
    }

    # Sends a GET request.
    response = requests.get(ALPHA_VANTAGE_URL, params=parameters)
    # Raises HTTPError, if one occurred.
    response.raise_for_status()
    # Returns the json-encoded content of a response, if any.
    res = response.json()

    # Get Daily Time Series Dictionary
    time_series_daily = res["Time Series (Daily)"]
    # Get all keys
    time_series_daily_key = list(time_series_daily.keys())
    # Get close price of yesterday
    yesterday_close_price = float(
        time_series_daily[time_series_daily_key[0]]["4. close"])
    # Get close price of the day before yesterday
    the_day_before_yesterday_close_price = float(
        time_series_daily[time_series_daily_key[1]]["4. close"])
    # Calculate
    return {
"yesterday": yesterday_close_price,
"the_day_before_yesterday": the_day_before_yesterday_close_price
}


def get_first_three_news_by_company(company):
    # Prepare parameters for API
    parameters = {
        "q": company,
        "apiKey": NEWS_API_KEY
    }

    # Sends a GET request.
    response = requests.get(NEWS_API_URL, params=parameters)
    # Raises HTTPError, if one occurred.
    response.raise_for_status()
    # Returns the json-encoded content of a response, if any.
    res = response.json()

    # Get the top three news
    return res["articles"][:3]


def send_sms(text: str):
    client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)

    message = client.messages.create(
        body=text,
        from_='twilio test number',
        to='your_phone_number'
    )

    # If there is a sid, SMS was sent successfully
    print(message.sid)

  
stock_price_historical_data = get_stock_price_history_data_by_company(COMPANY)
chagne_rate = round(
(stock_price_historical_data["yesterday"] -
                  stock_price_historical_data["the_day_before_yesterday"]
)/stock_price_historical_data["yesterday"] * 100, 2)

if abs(chagne_rate) >= 5:
    first_three_news = get_first_three_news_by_company(COMPANY)
    text = f"{COMPANY}: {'🔺' if chagne_rate > 0 else '🔻'} {chagne_rate}%\n\n"

    # Loop through news
    for news in first_three_news:
        text += f"Headine: {news['title']}\nBrief: {news['description']}\n\n"

    send_sms(text)


Monday, November 28, 2022

Python - API Keys, Authentication, and Environmental Variables (Day 35)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


What is API Keys



Previously, we are using some free web APIs and they are open to everyone without authentication.

But data/content is valuable in real world. Therefore there are plenty of data/content provider to provide web APIs to let subscribed users to use it. (Such as getting the weather historical data )

In order to know who is calling their web APIs, they need to authenticate users.

They will generate a unique API key to each end user, and that unique API key must be passed when making web API calls.


Weather web API




Ex: Get current weather of your city
import requests

OPEN_WEATHER_ENDPOINT = 'https://api.openweathermap.org/data/2.5/weather'
OPEN_WEATHER_API_KEY = 'your open weather api key'

# Prepare parameters for API
parameters = {
    "lat": 49.282730,
    "lon": -123.120735,
    "appid": OPEN_WEATHER_API_KEY,
}
# Sends a GET request.
response = requests.get(
    OPEN_WEATHER_ENDPOINT, params=parameters)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Returns the json-encoded content of a response, if any.
res = response.json()

print(f"Current Wearther: {res['weather'][0]['main']}")


Ex: Check if it will rain in your city in the next 12 hours (weather id > 700 means it will rain)
import requests

OPEN_WEATHER_ENDPOINT = 'https://api.openweathermap.org/data/2.5/forecast'
OPEN_WEATHER_API_KEY = 'your open weather api key'

# Prepare parameters for API
parameters = {
    "lat": 49.282730,
    "lon": -123.120735,
    # Next 4 * 3 hours
    "cnt": 4,
    "units": "metric",
    "appid": OPEN_WEATHER_API_KEY,
}
# Sends a GET request.
response = requests.get(
    OPEN_WEATHER_ENDPOINT, params=parameters)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Returns the json-encoded content of a response, if any.
res = response.json()

# Filter data if its weather id is larger than 700
raining_in_12_hours = [item for item in res["list"]
                       if item["weather"][0]["id"] > 700]

if len(raining_in_12_hours) > 0:
    print("It is going to rain. Remember to bring an Umbrella!")



Sending SMS



Ex: 
import requests
from twilio.rest import Client

OPEN_WEATHER_ENDPOINT = 'https://api.openweathermap.org/data/2.5/forecast'
OPEN_WEATHER_API_KEY = 'your open weather api key'
TWILIO_ACCOUNT_SID = 'your twilio account sid'
TWILIO_AUTH_TOKEN = 'your twilio auth token'

# Prepare parameters for API
parameters = {
    "lat": 49.282730,
    "lon": -123.120735,
    # Next 4 * 3 hours
    "cnt": 4,
    "units": "metric",
    "appid": OPEN_WEATHER_API_KEY,
}
# Sends a GET request.
response = requests.get(
    OPEN_WEATHER_ENDPOINT, params=parameters)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Returns the json-encoded content of a response, if any.
res = response.json()
# Filter data if its weather id is larger than 700
raining_in_12_hours = [item for item in res["list"]
                       if item["weather"][0]["id"] > 700]

if len(raining_in_12_hours) > 0:
    client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)

    message = client.messages.create(
        body='It is going to rain. Remember to bring an Umbrella!',
        from_='twilio test number',
        to='your phone number'
    )

    # If there is a sid, SMS was sent successfully
    print(message.sid)



Environmental Variables



Normally, we don't want to put the web API token or key in our code. Reference

    export YOUR_ENV_NAME=your_env_value

Or Using python-dotenv

Ex: 
import requests
import os
from twilio.rest import Client
from dotenv import load_dotenv

# Load Environmental Variables from .env
load_dotenv()

OPEN_WEATHER_ENDPOINT = 'https://api.openweathermap.org/data/2.5/forecast'
OPEN_WEATHER_API_KEY = os.environ.get("OPEN_WEATHER_API_KEY")
TWILIO_ACCOUNT_SID = os.environ.get("TWILIO_ACCOUNT_SID")
TWILIO_AUTH_TOKEN = os.environ.get("TWILIO_AUTH_TOKEN")

# Prepare parameters for API
parameters = {
    "lat": 49.282730,
    "lon": -123.120735,
    # Next 4 * 3 hours
    "cnt": 4,
    "units": "metric",
    "appid": OPEN_WEATHER_API_KEY,
}
# Sends a GET request.
response = requests.get(
    OPEN_WEATHER_ENDPOINT, params=parameters)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Returns the json-encoded content of a response, if any.
res = response.json()
# Filter data if its weather id is larger than 700
raining_in_12_hours = [item for item in res["list"]
                       if item["weather"][0]["id"] > 700]

if len(raining_in_12_hours) > 0:
    client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)

    message = client.messages.create(
        body='It is going to rain. Remember to bring an Umbrella!',
        from_='twilio test number',
        to='your phone number'
    )

    # If there is a sid, SMS was sent successfully
    print(message.sid)


Wednesday, November 23, 2022

Python - Project - Quiz App (Day 34)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


Project - Quiz App



Revise the Quiz Project in Day 17.
    
#1 Call Trivia API to get the question instead of hard-coded in txt file
    
#2 Use Tkinter to improve the User Experience

#3 Review OOP Design



Ex: data.py
import requests

# Prepare parameters for API
parameters = {
    "amount": 10,
    "category": 18, # Science - Computers
    "type": 'boolean',
}
# Sends a GET request.
response = requests.get("https://opentdb.com/api.php", params=parameters)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Returns the json-encoded content of a response, if any.
res= response.json()
question_data = res["results"]


Ex: question_model.py
class Question:

    def __init__(self, q_text, q_answer):
        self.text = q_text
        self.answer = q_answer


Ex: quiz_brain.py
import html

class QuizBrain:

    def __init__(self, q_list):
        self.question_number = 0
        self.score = 0
        self.question_list = q_list
        self.current_question = None

    def still_has_questions(self):
        return self.question_number < len(self.question_list)

    def next_question(self):
        self.current_question = self.question_list[self.question_number]
        self.question_number += 1
        question_text = html.unescape(self.current_question.text)

        return f"Q.{self.question_number}: {question_text} (True/False): "

    def check_answer(self, user_answer: str) -> bool:
        correct_answer = self.current_question.answer
       
        if user_answer.lower() == correct_answer.lower():
            self.score += 1
            return True

        return False


Ex: ui.py
import tkinter as tk
from quiz_brain import QuizBrain

THEME_COLOR = "#375362"


class QuizInterface:
    def __init__(self, quiz_brain: QuizBrain):
        self.quiz_brain = quiz_brain

        # Init Tkinter
        self.root = tk.Tk()
        self.root.title("Quizzler")
        self.root.config(padx=20, pady=20, background=THEME_COLOR)

        # Score Counter
        self.score_label = tk.Label(
            text="Score: 0",
            foreground='white',
            background=THEME_COLOR)
        self.score_label.grid(row=0, column=1)

        # Init canvas
        self.canvas = tk.Canvas(
            width=300, height=250, background='white', highlightthickness=0
        )
        # Question Text
        self.question_text = self.canvas.create_text(
            150, 125, width=280, text="",
            fill="black", font=('Arial', 15, "italic")
        )
        self.canvas.grid(row=1, column=0, columnspan=2, pady=50)

        # True Btn
        true_image = tk.PhotoImage(file="images/true.png")
        self.true_btn = tk.Button(
            image=true_image, command=self.true_btn_clicked)
        self.true_btn.config(
            background=THEME_COLOR,
            activebackground=THEME_COLOR)
        self.true_btn.grid(row=2, column=0)

        # False Btn
        false_image = tk.PhotoImage(file="images/false.png")
        self.false_btn = tk.Button(
            image=false_image, command=self.false_btn_clicked)
        self.false_btn.config(
            background=THEME_COLOR,
            activebackground=THEME_COLOR)
        self.false_btn.grid(row=2, column=1)

        # Get the next question
        self.get_next_question()

        # Start the Event Loop
        self.root.mainloop()

    def get_next_question(self):
        # Config white bg for canvas
        self.canvas.config(background='white')

        # No more questions
        if not self.quiz_brain.still_has_questions():
            # Show game over info
            self.canvas.itemconfig(self.question_text, text="No more questions")
            # Disable buttons
            self.true_btn.config(state='disabled')
            self.false_btn.config(state='disabled')
            return

        # Not finish all questions yet
        quiz_text = self.quiz_brain.next_question()
        self.canvas.itemconfig(self.question_text, text=quiz_text)

    def true_btn_clicked(self):
        self.show_feedback(self.quiz_brain.check_answer("true"))

    def false_btn_clicked(self):
        self.show_feedback(self.quiz_brain.check_answer("false"))

    def show_feedback(self, is_correct: bool):
        # Config bg of canvas based on the correction of answer
        if is_correct:
            self.canvas.config(background='green')
        else:
            self.canvas.config(background='red')

        # Update Score Label
        self.score_label.config(text=f"Score: {self.quiz_brain.score}")

        # 1 second delay show the next question
        self.root.after(1000, self.get_next_question)


Ex: main.py
from question_model import Question
from data import question_data
from quiz_brain import QuizBrain
from ui import QuizInterface

question_bank = []
for question in question_data:
    question_text = question["question"]
    question_answer = question["correct_answer"]
    new_question = Question(question_text, question_answer)
    question_bank.append(new_question)

quiz = QuizBrain(question_bank)
quiz_ui = QuizInterface(quiz)