Skip to content

Commit

Permalink
Moved contents, added new sections for course update.
Browse files Browse the repository at this point in the history
  • Loading branch information
jslvtr committed May 9, 2019
1 parent 456c938 commit 3ef8c11
Show file tree
Hide file tree
Showing 199 changed files with 800 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ __pycache__/
*.pyc
*.screenflow
*.mp4
*.mov
*.cmproj
exports/
videos/
*.numbers
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
12 changes: 12 additions & 0 deletions course_contents/12_browser_automation_selenium/code/Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
selenium = "*"

[requires]
python_version = "3.7"
36 changes: 36 additions & 0 deletions course_contents/12_browser_automation_selenium/code/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions course_contents/12_browser_automation_selenium/code/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Browser automation with Selenium

The code is very similar to last section, but now we're launching a browser instead of requesting the page with Python. We will be controlling the browser, instead of just getting HTML.

The browser will work like a normal browser: it will run JavaScript, it will have cookies, etc...

## Requirements

- Selenium library
- A webdriver

### Selenium

Install `selenium` using PyCharm's preferences panel or in your virtual environment.

### Webdriver

Each of the major browsers releases a webdriver for their browser. It essentially allows other applications to interact with the browser. In this course we use the Chrome webdriver.

Download it from http://chromedriver.chromium.org/. Make sure to download the version for your browser (e.g. v74 if you're using Chrome v74).

Place the de-compressed executable, `chromedriver`, into a folder. Remember the folder's path, as you'll need it after.

## Recap

33 changes: 33 additions & 0 deletions course_contents/12_browser_automation_selenium/code/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from pages.quotes_page import QuotesPage, InvalidTagForAuthorError
from selenium import webdriver

# chrome = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver')
# chrome.get('http://quotes.toscrape.com/search.aspx')
# page = QuotesPage(chrome)

# author = input("Enter the author you'd like quotes from: ")
# page.select_author(author)

# tags = page.get_available_tags()
# print("Select one of these tags: [{}]".format(" | ".join(tags)))
# selected_tag = input("Enter your tag: ")

# page.select_tag(selected_tag)
# page.search_button.click()
# print(page.quotes)

# --

try:
author = input("Enter the author you'd like quotes from: ")
tag = input("Enter your tag: ")

chrome = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver')
chrome.get('http://quotes.toscrape.com/search.aspx')
page = QuotesPage(chrome)

print(page.search_for_quotes(author, tag))
except InvalidTagForAuthorError as e:
print(e)
except Exception:
print("An unknown error occurred. Please try again.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class QuoteLocators:
CONTENT_LOCATOR = 'span.content'
AUTHOR_LOCATOR = 'span.author'
TAGS_LOCATOR = 'span.tag'
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class QuotesPageLocators:
QUOTE = 'div.quote'
AUTHOR_DROPDOWN = 'select#author'
TAG_DROPDOWN = 'select#tag'
SEARCH_BUTTON = 'input[name="submit_button"]'
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from typing import List
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException

from locators.quotes_page_locators import QuotesPageLocators
from parsers.quote import QuoteParser


class QuotesPage:
def __init__(self, browser):
self.browser = browser

@property
def quotes(self) -> List[QuoteParser]:
return [QuoteParser(e) for e in self.browser.find_elements_by_css_selector(QuotesPageLocators.QUOTE)]

@property
def author_dropdown(self) -> Select:
element = self.browser.find_element_by_css_selector(QuotesPageLocators.AUTHOR_DROPDOWN)
return Select(element)

@property
def tags_dropdown(self):
element = self.browser.find_element_by_css_selector(QuotesPageLocators.TAG_DROPDOWN)
return Select(element)

@property
def search_button(self):
return self.browser.find_element_by_css_selector(QuotesPageLocators.SEARCH_BUTTON)

def select_author(self, author_name: str):
self.author_dropdown.select_by_visible_text(author_name)

def get_available_tags(self) -> List[str]:
return [option.text for option in self.tags_dropdown.options]

def select_tag(self, tag_name: str):
self.tags_dropdown.select_by_visible_text(tag_name)

def search_for_quotes(self, author_name: str, tag_name: str) -> List[QuoteParser]:
self.select_author(author_name)
try:
self.select_tag(tag_name)
except NoSuchElementException:
raise InvalidTagForAuthorError(f"Author '{author_name}' does not have any quotes tagged with '{tag_name}'.")
self.search_button.click()
return self.quotes


class InvalidTagForAuthorError(ValueError):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from locators.quote_locators import QuoteLocators


class QuoteParser:
def __init__(self, parent):
self.parent = parent

def __repr__(self):
return f'<Quote {self.content}, by {self.author}>'

@property
def content(self):
locator = QuoteLocators.CONTENT_LOCATOR
return self.parent.find_element_by_css_selector(locator).text

@property
def author(self):
locator = QuoteLocators.AUTHOR_LOCATOR
return self.parent.find_element_by_css_selector(locator).text

@property
def tags(self):
locator = QuoteLocators.TAGS_LOCATOR
return self.parent.find_element_by_css_selector(locator)
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
18 changes: 18 additions & 0 deletions course_contents/16_interacting_with_apis/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Interacting with APIs

## Requirements

- requests
- cachetools (for part 3)
- An account with OpenExchangeRates (free)
- Generate an **App ID** on their website, you need this to use their API.

## Recap

First two parts recapped here: https://blog.tecladocode.com/how-to-interact-with-apis-using-python/

### Caching

Can use something like `functools.lru_cache` for caching function calls. That is, if you apply this decorator to a function and then you call the function with the same arguments 10 times, 9 of them will be really quick and the function won't evaluate.

Can use `cachetools.TTLCache` to cache a function call for up to a certain amount of time. When interacting with APIs it can be useful as sometimes we won't be interested in repeating the same call over and over.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import requests

APP_ID = "72dba35060b54cf9ad3ffbdc68de9174"
ENDPOINT = "https://openexchangerates.org/api/latest.json"

response = requests.get(f"{ENDPOINT}?app_id={APP_ID}")
exchange_rates = response.json()

usd_amount = 1000
gbp_amount = usd_amount * exchange_rates['rates']['GBP']

print(f"USD{usd_amount} is GBP{gbp_amount}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from libs.openexchange import OpenExchangeClient

APP_ID = "72dba35060b54cf9ad3ffbdc68de9174"

client = OpenExchangeClient(APP_ID)

usd_amount = 1000
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')

print(f"USD{usd_amount} is GBP{gbp_amount}")
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import requests
import functools


class OpenExchangeClient:
BASE_URL = "https://openexchangerates.org/api/"

def __init__(self, app_id):
self.app_id = app_id

@property
def latest(self):
return requests.get(f"{self.BASE_URL}/latest.json?app_id={self.app_id}").json()

def convert(self, from_amount, from_currency, to_currency):
rates = self.latest['rates']
to_rate = rates[to_currency]

if from_currency == 'USD':
return from_amount * to_rate
else:
from_in_usd = from_amount / rates[from_currency]
return from_in_usd * to_rate

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from libs.openexchange import OpenExchangeClient
import time



APP_ID = "72dba35060b54cf9ad3ffbdc68de9174"

client = OpenExchangeClient(APP_ID)

usd_amount = 1000
start = time.time()
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')
print(f'First call took {time.time() - start} seconds.')

start = time.time()
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')
gbp_amount = client.convert(usd_amount, 'USD', 'GBP')
print(f'After, 10 calls took {time.time() - start} seconds.')

print(f"USD{usd_amount} is GBP{gbp_amount}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import requests
from cachetools import cached, TTLCache


class OpenExchangeClient:
BASE_URL = "https://openexchangerates.org/api/"

def __init__(self, app_id):
self.app_id = app_id

@property
@cached(cache=TTLCache(maxsize=2, ttl=900))
def latest(self):
return requests.get(f"{self.BASE_URL}/latest.json?app_id={self.app_id}").json()

def convert(self, from_amount, from_currency, to_currency):
rates = self.latest['rates']
to_rate = rates[to_currency]

if from_currency == 'USD':
return from_amount * to_rate
else:
from_in_usd = from_amount / rates[from_currency]
return from_in_usd * to_rate

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
17 changes: 17 additions & 0 deletions course_contents/20_unit_testing/1_functions/functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Union, Tuple

def divide(dividend: Union[int, float], divisor: Union[int, float]):
if divisor == 0:
raise ValueError('The divisor cannot be zero.')

return dividend / divisor


def multiply(*args: Tuple[Union[int, float]]):
if len(args) == 0:
raise ValueError('At least one value to multiply must be passed.')
total = 1
for arg in args:
total *= arg

return total
Loading

0 comments on commit 3ef8c11

Please sign in to comment.