diff --git a/.gitignore b/.gitignore index 38ce427..f9ad6cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +#### Python Section + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -121,3 +123,48 @@ dmypy.json # Pyre type checker .pyre/ + +# VS Code Settings +.vscode/ + + +#### MacOs Section + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +#### Hugo Section + +# generated files by hugo +docs/ +/public/ +/resources/_gen/ + +# executable may be added to repository +hugo.exe +hugo.darwin +hugo.linux \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..4b26f77 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2013-2018 GitHub, Inc. and contributors + +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: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md index f71fc43..8d8fb9c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,15 @@ -# Front End Masters - Two Day Python Workshop +# Frontend Masters - Fundamentals & Intermediate Python Courses -These are the resources for Nina Zakharenko's Two Day Intro to and Intermediate Python Course +These are the resources for Nina Zakharenko's Python Fundamentals and Intermediate Python Courses + +* [Watch Python Fundamentals on Frontend Masters](https://frontendmasters.com/courses/python/) The majority of the content can be found on the course website. -* [Course Website - learnpython.netlify.com](https://learnpython.netlify.com) +* [Course Website - https://www.learnpython.dev](https://www.learnpython.dev) * [This Repo - git.io/python3](https://git.io/python3) -* [Follow Nina on Twitter](https://twitter.com/nnja) \ No newline at end of file +* [Follow Nina on Twitter](https://twitter.com/nnja) + +## License + +The content of this project itself is licensed under the [Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0) license](https://creativecommons.org/licenses/by-nc-nd/4.0/), and the underlying source code used to format and display that content, along with the code exercises is licensed under the [MIT license](LICENSE.md). \ No newline at end of file diff --git a/pyworkshop/.DS_Store b/pyworkshop/.DS_Store deleted file mode 100644 index 3e5c59c..0000000 Binary files a/pyworkshop/.DS_Store and /dev/null differ diff --git a/pyworkshop/intro_python/chapter2/exercise.py b/pyworkshop/1_intro_python/chapter2/exercise.py similarity index 100% rename from pyworkshop/intro_python/chapter2/exercise.py rename to pyworkshop/1_intro_python/chapter2/exercise.py diff --git a/pyworkshop/intro_python/chapter3/exercise.py b/pyworkshop/1_intro_python/chapter3/exercise.py similarity index 100% rename from pyworkshop/intro_python/chapter3/exercise.py rename to pyworkshop/1_intro_python/chapter3/exercise.py diff --git a/pyworkshop/intro_python/chapter4/dicts_exercise.py b/pyworkshop/1_intro_python/chapter4/dicts_exercise.py similarity index 100% rename from pyworkshop/intro_python/chapter4/dicts_exercise.py rename to pyworkshop/1_intro_python/chapter4/dicts_exercise.py diff --git a/pyworkshop/intro_python/chapter4/lists_exercise.py b/pyworkshop/1_intro_python/chapter4/lists_exercise.py similarity index 100% rename from pyworkshop/intro_python/chapter4/lists_exercise.py rename to pyworkshop/1_intro_python/chapter4/lists_exercise.py diff --git a/pyworkshop/intro_python/chapter4/mutability_exercise.py b/pyworkshop/1_intro_python/chapter4/mutability_exercise.py similarity index 100% rename from pyworkshop/intro_python/chapter4/mutability_exercise.py rename to pyworkshop/1_intro_python/chapter4/mutability_exercise.py diff --git a/pyworkshop/intro_python/chapter4/sets_excercise.py b/pyworkshop/1_intro_python/chapter4/sets_excercise.py similarity index 100% rename from pyworkshop/intro_python/chapter4/sets_excercise.py rename to pyworkshop/1_intro_python/chapter4/sets_excercise.py diff --git a/pyworkshop/intro_python/chapter4/tuples_exercise.py b/pyworkshop/1_intro_python/chapter4/tuples_exercise.py similarity index 100% rename from pyworkshop/intro_python/chapter4/tuples_exercise.py rename to pyworkshop/1_intro_python/chapter4/tuples_exercise.py diff --git a/pyworkshop/intro_python/chapter5/exercise.py b/pyworkshop/1_intro_python/chapter5/exercise.py similarity index 100% rename from pyworkshop/intro_python/chapter5/exercise.py rename to pyworkshop/1_intro_python/chapter5/exercise.py diff --git a/pyworkshop/intro_python/chapter6/exercise.py b/pyworkshop/1_intro_python/chapter6/exercise.py similarity index 100% rename from pyworkshop/intro_python/chapter6/exercise.py rename to pyworkshop/1_intro_python/chapter6/exercise.py diff --git a/pyworkshop/intro_python/chapter7/cities.json b/pyworkshop/1_intro_python/chapter7/cities.json similarity index 100% rename from pyworkshop/intro_python/chapter7/cities.json rename to pyworkshop/1_intro_python/chapter7/cities.json diff --git a/pyworkshop/1_intro_python/chapter7/exceptions.py b/pyworkshop/1_intro_python/chapter7/exceptions.py new file mode 100644 index 0000000..07840d1 --- /dev/null +++ b/pyworkshop/1_intro_python/chapter7/exceptions.py @@ -0,0 +1,7 @@ +# this will throw an error +try: + int("a") +except ValueError as e: + print("oops, you can't do that!", e) + +print("this is the end of my program") \ No newline at end of file diff --git a/pyworkshop/intro_python/chapter7/file_exercise.py b/pyworkshop/1_intro_python/chapter7/file_exercise.py similarity index 100% rename from pyworkshop/intro_python/chapter7/file_exercise.py rename to pyworkshop/1_intro_python/chapter7/file_exercise.py diff --git a/pyworkshop/1_intro_python/chapter7/hello.py b/pyworkshop/1_intro_python/chapter7/hello.py new file mode 100644 index 0000000..5ca4f59 --- /dev/null +++ b/pyworkshop/1_intro_python/chapter7/hello.py @@ -0,0 +1,4 @@ +greetings = ["Hello", "Bonjour", "Hola"] + +for greeting in greetings: + print(f"{greeting}, World!") \ No newline at end of file diff --git a/pyworkshop/1_intro_python/chapter7/mystery.py b/pyworkshop/1_intro_python/chapter7/mystery.py new file mode 100644 index 0000000..3f4e908 --- /dev/null +++ b/pyworkshop/1_intro_python/chapter7/mystery.py @@ -0,0 +1,14 @@ +def mystery(): + num = 10 * 3 + + if num == 10: + print("condition 10") + num = num * 10 + elif num == 30: + print("condition 30") + num = num * 30 + + print(f"num was {num}") + return num + +print(mystery()) \ No newline at end of file diff --git a/pyworkshop/1_intro_python/chapter7/name.py b/pyworkshop/1_intro_python/chapter7/name.py new file mode 100644 index 0000000..bd85767 --- /dev/null +++ b/pyworkshop/1_intro_python/chapter7/name.py @@ -0,0 +1,2 @@ +name = "Nina" +print(name) \ No newline at end of file diff --git a/pyworkshop/1_intro_python/chapter7/name_lib.py b/pyworkshop/1_intro_python/chapter7/name_lib.py new file mode 100644 index 0000000..484049a --- /dev/null +++ b/pyworkshop/1_intro_python/chapter7/name_lib.py @@ -0,0 +1,8 @@ +def upper_case_name(name): + return name.upper() + +if __name__ == "__main__": + name = "Nina" + name_upper = upper_case_name(name) + print(f"Upper case name is {name_upper}") + print("dunder name", __name__) \ No newline at end of file diff --git a/pyworkshop/1_intro_python/chapter7/other_program.py b/pyworkshop/1_intro_python/chapter7/other_program.py new file mode 100644 index 0000000..2facd7e --- /dev/null +++ b/pyworkshop/1_intro_python/chapter7/other_program.py @@ -0,0 +1,6 @@ +import name_lib + +my_name = "Fred" +upper_name = name_lib.upper_case_name(my_name) + +print(f"In my own code, upper name is {upper_name}") \ No newline at end of file diff --git a/pyworkshop/1_intro_python/chapter8/dog.py b/pyworkshop/1_intro_python/chapter8/dog.py new file mode 100644 index 0000000..4e30f25 --- /dev/null +++ b/pyworkshop/1_intro_python/chapter8/dog.py @@ -0,0 +1,13 @@ +import requests + +api_url = "http://shibe.online/api/shibes?count=1" + +params = {"count": 10} +response = requests.get(api_url, params=params) + +status_code = response.status_code +print("status code: ", status_code) + +response_json = response.json() +print(response_json) +print(response.url) \ No newline at end of file diff --git a/pyworkshop/intro_python/chapter8/exercise.py b/pyworkshop/1_intro_python/chapter8/exercise.py similarity index 100% rename from pyworkshop/intro_python/chapter8/exercise.py rename to pyworkshop/1_intro_python/chapter8/exercise.py diff --git a/pyworkshop/2_intermediate_python/chapter1/exercise.py b/pyworkshop/2_intermediate_python/chapter1/exercise.py new file mode 100644 index 0000000..e0b0f11 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter1/exercise.py @@ -0,0 +1,25 @@ +my_string = str(100) +my_string +type(my_string) + +my_int = int(my_string) +print(my_int) +type(my_int) + +float("3.1415") +int(3.1415) + +# Converting between lists and strings +my_list = list("hello") +print(my_list) +str(my_list) + +# String join method +''.join(my_list) +','.join(my_list) +'-'.join(my_list) + +# String split +my_string = "the,quick,brown,fox" +my_string.split(",") + diff --git a/pyworkshop/2_intermediate_python/chapter2/exercise.py b/pyworkshop/2_intermediate_python/chapter2/exercise.py new file mode 100644 index 0000000..645c974 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter2/exercise.py @@ -0,0 +1,27 @@ +# Comprehensions +my_list = [num for num in range(0, 100) if num % 2 == 0] +print(my_list) + +import random +my_dict = {num:random.randint(0, 100) for num in my_list} +print(my_dict) + +my_set = {num for num in my_dict.values()} +print(my_set) + +# Slicing +my_list = [num for num in range(0, 100)] +my_slice = my_list[30:70:2] +print(my_slice) + +my_backwards_slice = my_slice[::-1] +print(my_backwards_slice) + +# Zip +names = ["Nina", "Max", "Floyd", "Lloyd"] +scores = [random.randint(0, 100) for name in names] +print(scores) + +for name, score in zip(names, scores): + print(f"{name} got a score of {score}") + diff --git a/pyworkshop/2_intermediate_python/chapter3/exercise_part1.py b/pyworkshop/2_intermediate_python/chapter3/exercise_part1.py new file mode 100644 index 0000000..972d222 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter3/exercise_part1.py @@ -0,0 +1,13 @@ +class Vehicle: + + def __init__(self, make, model, fuel="gas"): + self.make = make + self.model = model + self.fuel = fuel + +daily_driver = Vehicle("Subaru", "Crosstrek") +# By default, this is how python represents our object: +print(daily_driver) + +# The variables we saved to the instance are available like this: +print(f"I drive a {daily_driver.make} {daily_driver.model}. It runs on {daily_driver.fuel}.") diff --git a/pyworkshop/2_intermediate_python/chapter3/exercise_part2.py b/pyworkshop/2_intermediate_python/chapter3/exercise_part2.py new file mode 100644 index 0000000..f030092 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter3/exercise_part2.py @@ -0,0 +1,19 @@ +class Vehicle: + + number_of_wheels = 4 + + def __init__(self, make, model, fuel="gas"): + self.make = make + self.model = model + self.fuel = fuel + +daily_driver = Vehicle("Subaru", "Crosstrek") + +daily_driver.number_of_wheels = 3 + +# Instance variables +print(f"I drive a {daily_driver.make} {daily_driver.model}. It runs on {daily_driver.fuel}.") +print(f"My {daily_driver.model} has {daily_driver.number_of_wheels} wheels.") + +# Class variable +print(f"Most vehicles have {Vehicle.number_of_wheels} wheels.") \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter3/exercise_part3.py b/pyworkshop/2_intermediate_python/chapter3/exercise_part3.py new file mode 100644 index 0000000..110db1e --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter3/exercise_part3.py @@ -0,0 +1,37 @@ +class Vehicle: + + def __init__(self, make, model, fuel="gas"): + self.make = make + self.model = model + self.fuel = fuel + + +class Car(Vehicle): + + number_of_wheels = 4 + + +class Truck(Vehicle): + + number_of_wheels = 6 + + def __init__(self, make, model, fuel="diesel"): + super().__init__(make, model, fuel) + + +daily_driver = Car("Subaru", "Crosstrek") +print(f"I drive a {daily_driver.make} {daily_driver.model}. " + f"It uses {daily_driver.fuel} and has {daily_driver.number_of_wheels} wheels.") + +truck = Truck("Ford", "F350") +print(f"I also have a {truck.make} {truck.model}. " + f"It uses {truck.fuel} and has {truck.number_of_wheels} wheels.") + +print( + f"My daily driver is a {type(daily_driver)} and my truck is a {type(truck)}") + +print(f"Is my daily driver a car? {isinstance(daily_driver, Car)}") +print(f"Is my truck a Vehicle? {isinstance(truck, Vehicle)}") +print(f"Is my truck a Car? {isinstance(truck, Car)}") + +print(f"Is a Truck a subclass of Vehicle? {issubclass(Truck, Vehicle)}") diff --git a/pyworkshop/2_intermediate_python/chapter3/gh_models.py b/pyworkshop/2_intermediate_python/chapter3/gh_models.py new file mode 100644 index 0000000..5d4d301 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter3/gh_models.py @@ -0,0 +1,12 @@ +class GitHubRepo: + + def __init__(self, name, language, num_stars): + self.name = name + self.language = language + self.num_stars = num_stars + + def __str__(self): + return f"-> {self.name} is a {self.language} repo with {self.num_stars} stars." + + def __repr__(self): + return f'GitHubRepo({self.name}, {self.language}, {self.num_stars})' \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter4/exercise_part1_indentationerrors.py b/pyworkshop/2_intermediate_python/chapter4/exercise_part1_indentationerrors.py new file mode 100644 index 0000000..e77f338 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter4/exercise_part1_indentationerrors.py @@ -0,0 +1,2 @@ +def my_function(): +print("Hello!") \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter4/exercise_part2_syntaxerrors.py b/pyworkshop/2_intermediate_python/chapter4/exercise_part2_syntaxerrors.py new file mode 100644 index 0000000..a59cb73 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter4/exercise_part2_syntaxerrors.py @@ -0,0 +1,2 @@ +a = [4, +x = 5 \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter4/exercise_part3_zerodivision.py b/pyworkshop/2_intermediate_python/chapter4/exercise_part3_zerodivision.py new file mode 100644 index 0000000..71de68b --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter4/exercise_part3_zerodivision.py @@ -0,0 +1,2 @@ +# Will cause a ZeroDivisionError +a = 1 / 0 \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter4/exercise_part4_custom_exceptions.py b/pyworkshop/2_intermediate_python/chapter4/exercise_part4_custom_exceptions.py new file mode 100644 index 0000000..467722d --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter4/exercise_part4_custom_exceptions.py @@ -0,0 +1,6 @@ +class MyException(Exception): + def __init__(self, message): + new_message = f"!!!ERROR!!! {message}" + super().__init__(new_message) + +raise MyException("Something went wrong!") \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter4/exercise_part5_try_except.py b/pyworkshop/2_intermediate_python/chapter4/exercise_part5_try_except.py new file mode 100644 index 0000000..c529758 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter4/exercise_part5_try_except.py @@ -0,0 +1,11 @@ +try: + my_dict = {"hello": "world"} + print(my_dict["foo"]) +except KeyError: + print("Oh no! That key doesn't exist") + +try: + my_dict = {"hello": "world"} + print(my_dict["foo"]) +except KeyError as key_error: + print(f"Oh no! The key {key_error} doesn't exist!") \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter4/exercise_part6_reraising.py b/pyworkshop/2_intermediate_python/chapter4/exercise_part6_reraising.py new file mode 100644 index 0000000..560aaf0 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter4/exercise_part6_reraising.py @@ -0,0 +1,8 @@ +while True: + for divisor in range(5, -1, -1): + try: + quotient = 10 / divisor + print(f"10 / {divisor} = {quotient}") + except ZeroDivisionError: + print("Oops! We tried to divide by zero!") + raise RuntimeError \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter4/gh_exceptions.py b/pyworkshop/2_intermediate_python/chapter4/gh_exceptions.py new file mode 100644 index 0000000..125fe87 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter4/gh_exceptions.py @@ -0,0 +1,13 @@ +""" +GitHub API Application: Custom Exception Classes +""" + +class GitHubApiException(Exception): + + def __init__(self, status_code): + if status_code == 403: + message = "Rate limit reached. Please wait a minute and try again." + else: + message = f"HTTP Status Code was: {status_code}." + + super().__init__(message) \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter5/exercise.py b/pyworkshop/2_intermediate_python/chapter5/exercise.py new file mode 100644 index 0000000..c420d4c --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter5/exercise.py @@ -0,0 +1,22 @@ + +# Using os +import os + +my_folder = os.getcwd() +print(f"Here are the files in {my_folder}:") + +with os.scandir(my_folder) as folder: + for entry in folder: + print(f" - {entry.name}") + +# Using sys + +import sys + +arguments = sys.argv +print(f"We received the following arguments:") + +for arg in arguments: + print(f" - {arg}") + +print(f"We are running on a '{sys.platform}' machine") \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter6/cli_exercise.py b/pyworkshop/2_intermediate_python/chapter6/cli_exercise.py new file mode 100644 index 0000000..8dd2d72 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter6/cli_exercise.py @@ -0,0 +1,5 @@ +import sys + +args = sys.argv + +print(args) \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter6/cli_exercise_input.py b/pyworkshop/2_intermediate_python/chapter6/cli_exercise_input.py new file mode 100644 index 0000000..975979e --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter6/cli_exercise_input.py @@ -0,0 +1,5 @@ +name = input("Hello, what is your name? ") + +birthday_string = input(f"Hello {name.strip()}. Please enter your birthday in MM/DD/YYYY format: ") + +print(f"Hello {name}. Your birthday is on {birthday_string}.") \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter6/optional_adv_exercise.py b/pyworkshop/2_intermediate_python/chapter6/optional_adv_exercise.py new file mode 100755 index 0000000..6479c0a --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter6/optional_adv_exercise.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +""" +A small command line Python program that uses the GitHub search API to list +the top projects by language, based on stars. + +GitHub Search API documentation: https://developer.github.com/v3/search/ + +Requests to this endpoint are rate limited to 10 requests per +minute per IP address. +""" + +import sys +import requests + + +GITHUB_API_URL = "https://api.github.com/search/repositories" + + +class GitHubApiException(Exception): + + def __init__(self, status_code): + if status_code == 403: + message = "Rate limit reached. Please wait a minute and try again." + else: + message = f"HTTP Status Code was: {status_code}." + + super().__init__("A GitHub API Error Occurred: " + message) + + +class GitHubRepo: + """ + A class used to represent a single GitHub Repository. + """ + + def __init__(self, name, language, num_stars): + self.name = name + self.language = language + self.num_stars = num_stars + + def __str__(self): + return f"-> {self.name} is a {self.language} repo with {self.num_stars} stars." + + def __repr__(self): + return f'GitHubRepo({self.name}, {self.language}, {self.num_stars})' + + +def create_query(languages, min_stars): + """ + Create the query string for the GitHub search API, + based on the minimum amount of stars for a project, and + the provided programming languages. + """ + # Notice we are calling .strip() on each language, to clear it of leading + # and trailing whitespace + query = " ".join(f"language:{language.strip()}" for language in languages) + query = query + f" stars:>{min_stars}" + return query + + +def repos_with_most_stars(languages, min_stars=50000, sort="stars", order="desc"): + query = create_query(languages, min_stars) + parameters = {"q": query, "sort": sort, "order": order} + response = requests.get(GITHUB_API_URL, params=parameters) + + if response.status_code != 200: + raise GitHubApiException(response.status_code) + + response_json = response.json() + items = response_json["items"] + return [GitHubRepo(item["name"], item["language"], item["stargazers_count"]) for item in items] + + +if __name__ == "__main__": + # Accept an optional argument for minimum number of stars from the command line + # $ ./gh_api 100000 # means an input of 100,000 minimum stars. + script_arguments = sys.argv + min_stars = 50000 + + if len(script_arguments) >= 2: + try: + min_stars = int(script_arguments[1]) + except ValueError: + sys.exit("Error: Command line argument must be a valid number.") + + # Accept the list of languages from the user, or provide a default list. + languages = input( + "Enter a comma separated list of programming languages (or press ENTER for defaults): " + ).strip() + if not languages: + languages = ["python", "javascript", "ruby"] + else: + languages = languages.split(",") + + # Get the results + result_repos = repos_with_most_stars(languages=languages, min_stars=min_stars) + if not result_repos: + print("No Results Found.") + else: + for repo in result_repos: + print(repo) diff --git a/pyworkshop/2_intermediate_python/chapter7/divisible.py b/pyworkshop/2_intermediate_python/chapter7/divisible.py new file mode 100644 index 0000000..0927c9e --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter7/divisible.py @@ -0,0 +1,2 @@ +def divisible_by(check_number, divisor): + return check_number % divisor == 0 diff --git a/pyworkshop/2_intermediate_python/chapter7/test_divisible.py b/pyworkshop/2_intermediate_python/chapter7/test_divisible.py new file mode 100644 index 0000000..ccf3a5b --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter7/test_divisible.py @@ -0,0 +1,12 @@ +import unittest +from divisible import divisible_by + +class TestCase(unittest.TestCase): + + def test_divisible_by(self): + self.assertTrue(divisible_by(10, 2)) + self.assertTrue(divisible_by(10, 3)) + + +if __name__ == '__main__': + unittest.main() diff --git a/pyworkshop/2_intermediate_python/chapter8/app.py b/pyworkshop/2_intermediate_python/chapter8/app.py new file mode 100644 index 0000000..ad8ad9d --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter8/app.py @@ -0,0 +1,40 @@ +""" +A Simple Flask Web Application interface +For viewing popular GitHub Repos sorted by stars using the +GitHub Search API. + +To run: +(env) $ python -m pip install -r requirements.txt +(env) $ export FLASK_ENV=development; python3 -m flask run +""" +from flask import Flask, render_template, request + +from repos.api import repos_with_most_stars +from repos.exceptions import GitHubApiException + +app = Flask(__name__) + +available_languages = ["Python", "JavaScript", "Ruby", "Java"] + + +@app.route('/', methods=['POST', 'GET']) +def index(): + if request.method == 'GET': + # Use the list of all languages + selected_languages = available_languages + elif request.method == 'POST': + # Use the languages we selected in the request form + selected_languages = request.form.getlist("languages") + + results = repos_with_most_stars(selected_languages) + + return render_template( + 'index.html', + selected_languages=selected_languages, + available_languages=available_languages, + results=results) + + +@app.errorhandler(GitHubApiException) +def handle_api_error(error): + return render_template('error.html', message=error) diff --git a/pyworkshop/2_intermediate_python/chapter8/repos/__init__.py b/pyworkshop/2_intermediate_python/chapter8/repos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyworkshop/2_intermediate_python/chapter8/repos/api.py b/pyworkshop/2_intermediate_python/chapter8/repos/api.py new file mode 100644 index 0000000..20837f4 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter8/repos/api.py @@ -0,0 +1,34 @@ +import requests + +from repos.exceptions import GitHubApiException +from repos.models import GitHubRepo + + +GITHUB_API_URL = "https://api.github.com/search/repositories" + + +def create_query(languages, min_stars): + """ + Create the query string for the GitHub search API, + based on the minimum amount of stars for a project, and + the provided programming languages. + """ + # Notice we are calling .strip() on each language, + # to clear it of leading and trailing whitespace + query = " ".join(f"language:{language.strip()}" for language in languages) + query = query + f" stars:>{min_stars}" + return query + + +def repos_with_most_stars(languages, min_stars=40000, sort="stars", order="desc"): + query = create_query(languages, min_stars) + parameters = {"q": query, "sort": sort, "order": order} + print(parameters) + response = requests.get(GITHUB_API_URL, params=parameters) + + if response.status_code != 200: + raise GitHubApiException(response.status_code) + + response_json = response.json() + items = response_json["items"] + return [GitHubRepo(item["name"], item["language"], item["stargazers_count"]) for item in items] diff --git a/pyworkshop/2_intermediate_python/chapter8/repos/exceptions.py b/pyworkshop/2_intermediate_python/chapter8/repos/exceptions.py new file mode 100644 index 0000000..0ba8343 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter8/repos/exceptions.py @@ -0,0 +1,13 @@ +""" +GitHub API Application: Custom Exception Classes +""" + +class GitHubApiException(Exception): + + def __init__(self, status_code): + if status_code == 403: + message = "Rate limit reached. Please wait a minute and try again." + else: + message = f"HTTP Status Code was: {status_code}." + + super().__init__("A GitHub API Error Occurred: " + message) diff --git a/pyworkshop/2_intermediate_python/chapter8/repos/models.py b/pyworkshop/2_intermediate_python/chapter8/repos/models.py new file mode 100644 index 0000000..cdee487 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter8/repos/models.py @@ -0,0 +1,19 @@ +""" +GitHub API Application: Custom Model Classes +""" + +class GitHubRepo: + """ + A class used to represent a single GitHub Repository. + """ + + def __init__(self, name, language, num_stars): + self.name = name + self.language = language + self.num_stars = num_stars + + def __str__(self): + return f"-> {self.name} is a {self.language} repo with {self.num_stars} stars." + + def __repr__(self): + return f'GitHubRepo({self.name}, {self.language}, {self.num_stars})' diff --git a/pyworkshop/2_intermediate_python/chapter8/requirements.txt b/pyworkshop/2_intermediate_python/chapter8/requirements.txt new file mode 100644 index 0000000..5eaf725 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter8/requirements.txt @@ -0,0 +1,2 @@ +flask +requests \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter8/static/favicon.png b/pyworkshop/2_intermediate_python/chapter8/static/favicon.png new file mode 100644 index 0000000..42c4379 Binary files /dev/null and b/pyworkshop/2_intermediate_python/chapter8/static/favicon.png differ diff --git a/pyworkshop/2_intermediate_python/chapter8/static/style.css b/pyworkshop/2_intermediate_python/chapter8/static/style.css new file mode 100644 index 0000000..0a0b219 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter8/static/style.css @@ -0,0 +1,229 @@ +/* +CSS Styles for GitHub Repos by Stars exercise site. +Sorry for the bad css! I'm a "full-stack" developer. +*/ + +/* +Checkbox Style From: https://codepen.io/wilder_taype/pen/pNXwMW +*/ + +@import url(https://fonts.googleapis.com/css?family=Roboto:400,700); +*{font-family: 'Roboto', sans-serif;} + +.option-input { + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + -o-appearance: none; + appearance: none; + position: relative; + top: 13.33333px; + right: 0; + bottom: 0; + left: 0; + height: 40px; + width: 40px; + transition: all 0.15s ease-out 0s; + background: #cbd1d8; + border: none; + color: #fff; + cursor: pointer; + display: inline-block; + margin-right: 0.5rem; + outline: none; + position: relative; + z-index: 1000; +} +.option-input:hover { + background: #9faab7; +} +.option-input:checked { + background: #40e0d0; +} +.option-input:checked::before { + height: 40px; + width: 40px; + position: absolute; + content: '✔'; + display: inline-block; + font-size: 26.66667px; + text-align: center; + line-height: 40px; +} +.option-input:checked::after { + background: #40e0d0; + content: ''; + display: block; + position: relative; + z-index: 100; +} + +body { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: box; + background: #e8ebee; + color: #9faab7; + font-family: "Helvetica Neue", "Helvetica", "Roboto", "Arial", sans-serif; + text-align: center; +} +body div { + padding: 5rem; +} +body label { + display: block; + line-height: 40px; + text-align: left; +} + +/* +Button Style From: https://codepen.io/wilder_taype/pen/LeoQEb +*/ + +button { + display: block; + border-radius: 3px; + margin: 15px 5px; + color: #fff; + cursor: pointer; + box-shadow: 3px 7px 7px 4px rgba(237,80,83,.5), 1px 0 0 rgba(0,0,0,.15); + background: #ed5053; + padding: 20px; + flex-grow: 1; + width: 100px; + border: none; + font-size: 1.6rem; + line-height: 1.6; +} + +/* +Table Style From: https://codepen.io/gmb/pen/xVGYZw +*/ + +html { + font-size: 62.5%; +} + +body { + font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 1.6rem; + line-height: 1.6; + color: #20262e; + background-color: #28b1de; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.container { + max-width: 800px; +} + +h1 { + text-align: center; + font-size: 3rem; + color: rgba(255, 255, 255, .8); + text-transform: uppercase; + line-height: 1.375; + margin: 0 0 2.4rem 0; + letter-spacing: 1px +} + +h2 { + text-align: right; + font-size: 1.2rem; + text-transform: uppercase; + line-height: 1.375; + margin: 0; + letter-spacing: 1px +} + +table { + width: 100%; + min-width: 300px; + margin-bottom: 2.4rem; + background-color: #20262e; + color: #fff; + overflow: hidden; +} + +table tr:nth-child(even) { + background-color: rgb(46, 53, 62); +} + +table th, +table td:before { + color: #28b1de; +} + +table th { + display: none; +} + +table th, +table td { + margin: .5rem 2rem; + text-align: left; +} + +table td { + display: block; + font-size: 90%; +} + +table td:first-child { + padding-top: 1rem; +} + +table td:last-child { + padding-bottom: 1rem; +} + +table td:before { + content: attr(data-th) ':\00a0'; + font-weight: bold; + min-width: 8rem; + display: inline-block; +} + + +@media (min-width: 600px) { + table td:before { + display: none; + } + table th, + table td { + display: table-cell; + } + table th, + table td, + table td:first-child, + table td:last-child { + padding: 1.5rem 2rem; + } +} + +/* +Error Page Style From: https://codepen.io/akashrajendra/pen/JKKRvQ +*/ + +#error{ + font-family: 'Lato', sans-serif; + color: #888; + margin: 0; + display: table; + width: 100%; + height: 100vh; + text-align: center; +} + +.fof{ + display: table-cell; + vertical-align: middle; +} + +.fof h1{ + font-size: 50px; + display: inline-block; + padding-right: 12px; +} \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter8/static_files.zip b/pyworkshop/2_intermediate_python/chapter8/static_files.zip new file mode 100644 index 0000000..707f131 Binary files /dev/null and b/pyworkshop/2_intermediate_python/chapter8/static_files.zip differ diff --git a/pyworkshop/2_intermediate_python/chapter8/templates/error.html b/pyworkshop/2_intermediate_python/chapter8/templates/error.html new file mode 100644 index 0000000..0e0b73c --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter8/templates/error.html @@ -0,0 +1,13 @@ + + + + + +
+
+

{{message}}

+
+
+ + + \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter8/templates/index.html b/pyworkshop/2_intermediate_python/chapter8/templates/index.html new file mode 100644 index 0000000..89c1844 --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter8/templates/index.html @@ -0,0 +1,57 @@ + + + + + + + Learn Python: Popular GitHub Repos (by ⭐️) With Flask + + + + + + + +
+

Languages

+
+ {% for language in available_languages %} + + {% endfor %} + +
+
+ +
+

Popular GitHub Repos (by ⭐️)

+
+ + + {% if not results %} + No Results. + {% else %} + + + + + + {% endif %} + + {% for result in results %} + + + + + + {% endfor %} + +
NameLanguageNumber Stars
{{result.name}}{{result.language}}{{result.num_stars}}
+
+
+ + + \ No newline at end of file diff --git a/pyworkshop/2_intermediate_python/chapter8/test.py b/pyworkshop/2_intermediate_python/chapter8/test.py new file mode 100644 index 0000000..bb9176e --- /dev/null +++ b/pyworkshop/2_intermediate_python/chapter8/test.py @@ -0,0 +1,32 @@ +import repos.api +import repos.exceptions + +import unittest + +class TestCreateQuery(unittest.TestCase): + + def test_create_query(self): + test_languages = ["Python", "Ruby", "Java"] + test_min_stars = 10000 + + expected = "language:Python language:Ruby language:Java stars:>10000" + result = repos.api.create_query(test_languages, test_min_stars) + + self.assertEqual(result, expected, "Unexpected result from create_query") + + +class TestGitHubApiException(unittest.TestCase): + + def test_exception_403(self): + status_code = 403 + exception = repos.exceptions.GitHubApiException(status_code) + self.assertTrue("Rate limit" in str(exception), "'Rate limit' not found") + + def test_exception_500(self): + status_code = 500 + exception = repos.exceptions.GitHubApiException(status_code) + self.assertTrue(str(status_code) in str(exception)) + + +if __name__ == "__main__": + unittest.main() diff --git a/pyworkshop/2_intermediate_python/chapter8/tests/__init__.py b/pyworkshop/2_intermediate_python/chapter8/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyworkshop/2_intermediate_python/chapter8/tests/tests.py b/pyworkshop/2_intermediate_python/chapter8/tests/tests.py new file mode 100644 index 0000000..e69de29 diff --git a/pyworkshop/intro_python/.DS_Store b/pyworkshop/intro_python/.DS_Store deleted file mode 100644 index cb5e79c..0000000 Binary files a/pyworkshop/intro_python/.DS_Store and /dev/null differ diff --git a/website/.gitattributes b/website/.gitattributes new file mode 100644 index 0000000..3c63f3b --- /dev/null +++ b/website/.gitattributes @@ -0,0 +1,10 @@ +*.png filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*..eot filter=lfs diff=lfs merge=lfs -text +*.svg filter=lfs diff=lfs merge=lfs -text +*.eot filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.woff filter=lfs diff=lfs merge=lfs -text +*.woff2 filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..895e4f0 --- /dev/null +++ b/website/README.md @@ -0,0 +1,27 @@ +Local setup instructions: + +1. Install Hugo version v0.54.0 for the site to render properly +1. clone hugo learn theme from https://github.com/matcornic/hugo-theme-learn, update `themesdir` in config.toml +1. `cd python` then, `hugo server -D` to run local server + +Note: the file and folder names don't contribute to order, and may not always be accurate. Sorted by chapter and individual page weight. + +If you move things around or delete chapters, you may have to restart the server for the chapters to appear correctly. + +How to use the theme: https://learn.netlify.com/en/cont/ +More about theme shortcodes: https://learn.netlify.com/en/shortcodes/ + +use `git lfs` for tracking binary types. + +```bash +$ git lfs track "*.psd" +git add .gitattributes +``` + +Currently tracked types: + - .png + - .jpg + - .jpeg + - .pdf + - .mp4 + - .gif \ No newline at end of file diff --git a/website/archetypes/default.md b/website/archetypes/default.md new file mode 100644 index 0000000..00e77bd --- /dev/null +++ b/website/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +draft: true +--- + diff --git a/website/config.toml b/website/config.toml new file mode 100644 index 0000000..45280b7 --- /dev/null +++ b/website/config.toml @@ -0,0 +1,52 @@ +baseURL = "/" +languageCode = "en-US" +defaultContentLanguage = "en" + +title = "Nina Zakharenko - Introduction and Intermediate Python" +theme = "nnja-theme-learn" +themesdir = "themes" +metaDataFormat = "yaml" +defaultContentLanguageInSubdir= true +ignoreFiles = [ "\\.pyc$", "__pycache__"] +publishDir = "docs" +googleAnalytics = "UA-138644383-1" + +[params] + # Change default color scheme with a variant one. Can be "red", "blue", "green". + themeVariant = "blue" + editURL = "https://github.com/nnja/python/edit/master/website/content/" + defaultSocialImage = "images/twittercard.png" + description = "Free Learn Python Course by Nina Zakharenko - An intensive two day introduction and intermediate course on Python. Video course published on Frontend Masters." + author = "Nina Zakharenko" + showVisitedLinks = true + disableBreadcrumb = false + disableNextPrev = false + disableInlineCopyToClipBoard = true + twitter = "nnja" + + +# For search functionality +[outputs] +home = [ "HTML", "RSS", "JSON"] + +[Languages] +[Languages.en] +title = "Learn Python by Nina Zakharenko" +weight = 1 +languageName = "English" + +[[Languages.en.menu.shortcuts]] +name = " Github repo" +identifier = "ds" +url = "https://git.io/python3" +weight = 10 + +[[Languages.en.menu.shortcuts]] +name = " Follow Nina on Twitter" +url = "https://twitter.com/nnja" +weight = 20 + +[[Languages.en.menu.shortcuts]] +name = " Credits" +url = "/credits" +weight = 30 \ No newline at end of file diff --git a/website/content/01-introduction/00-who.md b/website/content/01-introduction/00-who.md new file mode 100644 index 0000000..0d4fb4a --- /dev/null +++ b/website/content/01-introduction/00-who.md @@ -0,0 +1,38 @@ +--- +date: 2019-01-24T09:14:14-08:00 +title: About Your Instructor +weight: 1 +draft: false +--- + +# @nnja + +{{< figure src="/images/me.jpg" width="20%" title="Nina Zakharenko" >}} + + +Hi, I'm Nina Zakharenko. I'm a Senior Cloud Developer Advocate at Microsoft, focusing on Python. Before Microsoft, I wrote code for other cool companies like Reddit, Meetup, and HBO. In my spare time I like drinking scotch and tinkering with wearable electronics. I enjoy hiking and snowboarding from my home base in Portland, Oregon. I change my hair color regularly. + + +I've been involved in the Python community for approximately 6 years. During that time I've spoken at multiple Python conferences on a variety of topics, including PyCon US, PyParis, DjangoCon, and even PyCon Russia in Moscow. + +#### Stay in touch +- @nnja +- LinkedIn +- GitHub where you can find the code for this site and my other projects + +I occasionally blog on Medium at @nnja. You can find out more about me on my homepage [nnja.io](https://nnja.io). + +#### Frontend Masters + +Check out my other [Frontend Masters courses](https://frontendmasters.com/teachers/nina-zakharenko/), such as [Git In-Depth](https://frontendmasters.com/courses/git-in-depth/). + +#### My Conference Talks + +You can watch a selection of my talks on YouTube +such as: + +- [Elegant Solutions For Everyday Python Problems](https://www.youtube.com/watch?v=WiQqqB9MlkA) - PyCon 2018 +- [Memory Management in Python - The Basics](https://www.youtube.com/watch?v=F6u5rhUQ6dU) - PyCon 2016 +- [Code Review Skills for Pythonistas](https://www.youtube.com/watch?v=6L3ZVLtSeo8) - EuroPython 2018 +- [Five Things You Didn't Know Python Can Do](https://www.youtube.com/watch?v=WlGkBqBRsik) - All Things Open 2018 +- [Technical Debt - The Code Monster In Your Closet](https://www.youtube.com/watch?v=JKYktDRoRxw) - PyCon 2015 \ No newline at end of file diff --git a/website/content/01-introduction/02-navigating-course.md b/website/content/01-introduction/02-navigating-course.md new file mode 100644 index 0000000..d13dadd --- /dev/null +++ b/website/content/01-introduction/02-navigating-course.md @@ -0,0 +1,80 @@ +--- +title: "Navigating The Course" +date: 2019-02-04T00:13:49-08:00 +draft: false +weight: 2 +--- + +The course content is free and open source, and hosted on GitHub. A link will be available *after the class* so that edits can be suggested via Pull Requests. + +### Suggesting Changes to The Course Material + +If you notice a typo or any other issues with the material *after the class*, please open a pull request on GitHub by clicking the "Edit this page" button on the top right corner of every course page. + + + +![Edit Page](/01-introduction/images/edit-page.png?classes=shadow&outline&width=10pc) + +### Clearing History of Read Pages + +Once a section is completed, a check mark will appear to the right of that section. To clear it, hit the "Clear History" button at the bottom of the table of contents on the left hand side. + +![Clear History](/01-introduction/images/clear_history.png?classes=shadow&outline&width=10pc) + +### Copying Code + +To copy code and commands, click on the clipboard icon (() on the top right of any code box. + +{{% notice tip %}} +You can even copy code intended for the Python REPL. The prompt characters won't be copied over. +{{% /notice %}} + +Try it now by coping the code below. + +```python +def greeting(): + print("Hello, World!") +``` + +### Searching + +The whole course is searchable via the search bar on the top left. + +![Search](/01-introduction/images/search.png?classes=shadow&outline&width=15pc) + +### Navigation + +View the course contents in the side bar on the left. + +#### Arrows + +The course can be navigated sequentially to the page before or after via the left and right arrows on each page. + +![Navigate](/01-introduction/images/arrows.png?classes=shadow&outline&width=10pc) + +{{% notice tip %}} +You can also use the left and right keyboard arrows to navigate between pages. +{{% /notice %}} + +#### Breadcrumbs + +Use the breadcrumb links on the floating header of each page to jump between related sections. + +![Header](/01-introduction/images/header.png?classes=shadow&outline) + +#### Table of Contents + +Click on the "Table of Contents" Icon in the page header to jump between sections in the page. + +![Table of Contents](/01-introduction/images/toc.png?classes=shadow&outline) + +### Expanding Sections + +Sometimes additional instructions or information will be available via the expand section arrow. Keep an eye out for this icon: + +![Expand Section](/01-introduction/images/expand-section.png?classes=shadow&outline&width=7pc) + + +{{% notice info %}} +The course material is heavy in information and links. I suggest that you follow the links **after class** as a source of additional information, so that you don't get distracted from the important material. +{{% /notice %}} \ No newline at end of file diff --git a/website/content/01-introduction/02-requirements/05-vs-code/01-getting-started.md b/website/content/01-introduction/02-requirements/05-vs-code/01-getting-started.md new file mode 100644 index 0000000..8194018 --- /dev/null +++ b/website/content/01-introduction/02-requirements/05-vs-code/01-getting-started.md @@ -0,0 +1,130 @@ +--- +title: "Getting Started" +draft: false +weight: 1 +--- + +Visual Studio Code (commonly called VS Code) is a free, open source, lightweight cross platform code editor. A fresh installation is bare bones -- the power of VS Code comes via the extensions. There are useful extensions for every programming languages you can think of, but the choice of which ones to install and how to configure your editor is up to you. + +## Installation + +You should have installed the editor and the Python extension as part of the pre-requisites for the course. If you haven't, please do so now as we'll be using VS Code to edit our Python for the rest of the course. + + {{% button href="https://code.visualstudio.com/download" icon="fas fa-download" %}}Download Visual Studio Code{{% /button %}} + + {{% button href="https://marketplace.visualstudio.com/items?itemName=ms-python.python" icon="fas fa-download" %}}Python Extension for Visual Studio Code{{% /button %}} + +## Advantages + +You'll want to use a code editor for your Python projects as your projects grow in scope. It'll allow you to easily manage projects with multiple files and modules. The Python extension also offers a built-in REPL, so that we can quickly and easily test out snippets of code and instantly see the results. + +Code editors also offer syntax highlighting, syntax checking, auto-completion, and more. + +## Keyboard Bindings + +If you prefer keyboard shortcuts from a different editor, such as sublime, vim, or emacs, you can install a [key-map extension](https://code.visualstudio.com/docs/getstarted/keybindings#_keymap-extensions) to remap your keybindings, preferably at the next break. + +## Choices + +We'll be using VS Code for this course so I can show you the ropes, but after class is over you're free to switch to any editor of your choice. + +{{% notice info %}} +Note: For the rest of the course, instructions for Mac/Linux terminal and Windows PowerShell terminal should be the same. +{{% /notice %}} + +## Opening Your First Project + +#### Setting Up a Project Structure + +Now that VS Code is downloaded and installed, it's time to open our project. + +On Mac OS and Linux, follow these steps in you terminal application, or follow the steps manually in the program you use to manage files. + +{{% notice tip %}} +Remember, you can copy all the commands by clicking on the
+clipboard icon () located in the top right section of a code block. +{{% /notice %}} + +```bash +# change to our home directory +$ cd + +# enter the directory of the pyworkshop folder from the last step +$ cd pyworkshop +``` + +For Mac / Linux: + +```bash +# activate the virtual environment +$ source env/bin/activate + +# make a new empty python file called project.py +(env) $ touch project.py +``` + +For Windows: + +```powershell +# activate the virtual environment +> env\scripts\activate + +# make a new empty python file called project.py +(env)> fc > project.py +``` + + + +#### Opening a Project in VS Code From The Command Line + +First, navigate to your project directory and activate your virtual environment. If you followed the previous steps, you should already be in the right directory. + +```bash +# Open VS Code using the current directory as a project. +(env) $ code . +``` + +This will open a project in the current directory. This is the last time we'll be using the terminal outside of VS Code for the duration of the course. + + + + +## Keyboard Shortcuts + +To get started with VS Code, you only need to remember two keyboard shortcuts. + +### #1 : Show Command Palette + +Open the command palette with `Ctrl+Shift+P` on Windows and Linux, and `⌘⇧P` (command + shift + P) on Mac OS. + +The command palette lets you search and run any of the commands available within VS Code. If you don't know how to do something, the command palette will usually point you in the right direction. + +The command palette is how you'll navigate VS Code. + +![Command Palette](/01-introduction/02-requirements/05-vs-code/images/command-palette.png?classes=shadow,border "The VS Code Command Palette") + +### #2 : Quick Open, Go to File + +Open Quick Open with `Ctrl+P` on Windows and Linux, and `⌘P`(command + P) on Mac OS. + +Quick open is how you'll navigate your codebase and files. + +**To dismiss either dialog, press the Escape key.** + +{{% notice tip %}} +Write these two shortcuts down, because we'll be using them frequently for the rest of the course. +{{% /notice %}} \ No newline at end of file diff --git a/website/content/01-introduction/02-requirements/05-vs-code/03-working-with-python.md b/website/content/01-introduction/02-requirements/05-vs-code/03-working-with-python.md new file mode 100644 index 0000000..82ce500 --- /dev/null +++ b/website/content/01-introduction/02-requirements/05-vs-code/03-working-with-python.md @@ -0,0 +1,68 @@ +--- +title: "Working With Python" +draft: false +weight: 3 +--- + +{{% notice info %}} +Once we open our first Python file in VS Code, we'll see some configuration pop-ups. For the time being, **don't** dismiss them. +{{% /notice %}} + +![Python Pop Ups](/01-introduction/02-requirements/05-vs-code/images/popups.png?classes=shadow,border) + +## Configuring VS Code for Python + +### Open the `project.py` file + +If you haven't created a `project.py` file in the `pyworkshop` directory, now is the time to do so. +You can make a new file (Ctrl+N or ⌘P) and then save it (Ctrl+S or ⌘S). + +Now that you've learned the necessary keyboard shortcuts, use Quick Open with `Ctrl+P` or `⌘P` to open the `project.py` file. + +{{% notice info %}} +If you haven't installed the Python extension at this point, you'll get a pop-up recommending you install it for this type of file. Please [install it now](https://marketplace.visualstudio.com/items?itemName=ms-python.python&WT.mc_id=pyclass-docs-ninaz), and reload VS Code when prompted to load the extension. +{{% /notice %}} + +## Configuring The Interpreter + +Many operating systems include Python, but unfortunately it's usually a few versions behind. + +We _never_ want to use Python2 for new Python projects, and we want to make sure we select the latest version of Python3 that we installed in the pre-requisites. + +Luckily, VS Code is smart. If you launch it from a directory with an *activated* virtual environment, it'll automatically pick up the correct interpreter. + +{{%expand "In case you need to configure the interpreter manually, follow these (optional) instructions." %}} + +Click on the Select Python Interpreter button in the popup. + +![Interpreter 1](/01-introduction/02-requirements/05-vs-code/images/interpreter.1.png?classes=shadow,border) + + +{{%expand "Note: If dismissed the popup, open the command palette and select Python: Select Interpreter. Expand this section for more details." %}} +![Interpreter 2](/01-introduction/02-requirements/05-vs-code/images/interpreter.2.png?classes=shadow,border) +{{% /expand%}} + +Select the version of Python 3 in your virtual environment `env` folder as your interpreter. If you open a Python file in a new directory, you may need to select your interpreter again. + +{{% /expand%}} + +{{% notice tip %}} +You can always see what your interpreter is set to on the left side of the status bar at the bottom of the window. +{{% /notice %}} + +![Selected Interpreter](/01-introduction/02-requirements/05-vs-code/images/selected-interpreter.png?classes=shadow,border) + +## Setting Up a Linter + +Per the [VS Code documentation](https://code.visualstudio.com/docs/python/linting?WT.mc_id=pyclass-docs-ninaz), linting highlights syntactical and stylistic problems in your Python source code. A linter will give you code hints about a variety of different types of problems. For example, when you have subtle errors in your code, like trying to use a variable you haven't defined yet. A linter will also show you when you're not following Python style convention called PEP8. PEP8 is a set of defined rules for how Python code should look. We'll cover it in more depth later, but what you need to know right now is that PEP8 warnings are not syntax errors. If your code doesn't adhere to the PEP8 standard, it will still run. + +By default the linter will run every time you save a file, so it's good practice to save often with (Ctrl+S or ⌘S). + +Let's go ahead and click install on the Linter popup. + +![Install pylint](/01-introduction/02-requirements/05-vs-code/images/install-pylint.png?classes=shadow,border) + +{{%expand "Note: If you accidentally dismissed the popup, open the command palette and search for Python: Select Linter, then select pylint. The popup will now reappear, and you can hit install. Expand this section for more details." %}} +![Select linter](/01-introduction/02-requirements/05-vs-code/images/select-linter.png?classes=shadow,border) +![Choose pylint](/01-introduction/02-requirements/05-vs-code/images/select-pylint.png?classes=shadow,border) +{{% /expand%}} \ No newline at end of file diff --git a/website/content/01-introduction/02-requirements/05-vs-code/04-the-repl-in-vscode.md b/website/content/01-introduction/02-requirements/05-vs-code/04-the-repl-in-vscode.md new file mode 100644 index 0000000..2f3d529 --- /dev/null +++ b/website/content/01-introduction/02-requirements/05-vs-code/04-the-repl-in-vscode.md @@ -0,0 +1,128 @@ +--- +title: "Using The REPL" +date: 2019-02-10T18:11:15-08:00 +draft: false +weight: 4 +--- + +## The REPL + +REPL stands for Read, Evaluate, Print, Loop. The REPL is how you interact with the Python Interpreter. + +Unlike running a file containing Python code, in the REPL you can type commands and instantly see the output printed out. You can also use the REPL to print out help for methods and objects in Python, list out what methods are available, and much more. + +## Open The REPL + +To start the REPL in VS code, open the command palette and search for and select "Start REPL". The advantage to starting the REPL from inside VS Code is that it respects the environment you already set up, that is the version of Python you chose earlier. + +![Start REPL](/01-introduction/02-requirements/05-vs-code/images/repl-start.png?classes=shadow,border) + +Note: If you'd like to start the REPL from the command line outside of the editor, type `python` in your shell, + +Running this command should bring up a new pane at the bottom of your editor that you can type into. A great feature of the REPL is that we can instantly see the result of commands being run. + +![REPL](/01-introduction/02-requirements/05-vs-code/images/repl.png?classes=shadow,border) + +{{% notice note %}} +Note, in the REPL three arrows >>> indicate a line of input given at the prompt. +If you see these arrows in example code, don't copy them into your own REPL. +Later, when we run out Python code from files, you will no longer see the triple arrows. +{{% /notice %}} + +Let's get familiar with the REPL. + +- `#` - comments start with `#`. They will be ignored. +- `>>>` - this is the prompt. In example code, lines starting with `>>>` means they are **input** +- lines that don't start with either of these are **output** that was produced by running input from the prompt + + by typing these line of code at the `>>>` prompt, and press enter after each line. + +```python +# My REPL. Don't copy the >>> symbols, that means the code was entered +# into the prompt. +# +# If the line does not start with >>>, that means it is output, +# not input +>>> name = "Nina" +>>> name +'Nina' +``` + +In the REPL, we can see the value of any variable just by entering it into the prompt. + +You can copy and paste code into the REPL, even multiple lines of code at once. Copy the three lines below and paste them into your REPL. What's the result? + +```python +x = 5 +y = 33.5 +x * y +``` + +{{%expand "See the result." %}} +```python +>>> x = 5 +>>> y = 33.5 +>>> x * y +167.5 +``` +{{% /expand%}} + +The REPL allows us to gather information in real time about our Python program, which makes it a great learning tool. + +## Using `type()`, `dir()`, and `help()` + +We can use three very useful methods in the REPL to help us understand our Python programs. + +Pass in an object into the `type()` Use type to find the type of an object in Python. + +{{% notice info %}} +If you're not sure what a variable or an object is, don't worry. We'll cover it later in the day. +{{% /notice %}} + +For example, in the REPL, let's make a new variable `name`, and check its `type`. + +```python +>>> name = "Nina" +>>> type(name) + +``` + +We'll see that the type is `str`, Python's version of a string. Now that we know this object's type, we can +pass the type into other methods. + +The first one is `dir()` which stands for directory. If we check the directory of `str` (notice, no quotes here)) in the REPL, we'll see all the methods available on strings in Python. Don't worry about these for now, we'll use them later in the day. + +```Python +>>> dir(str) +['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] +``` + +The next useful method is `help()`. You can pass a type, method, or other object to help to instantly see available documentation about the method, the parameters it expects, and what it returns. + +Let's try this in the REPL, and look up the documentation for the `isupper` method in String. We access it with the period symbol (`.`). This is called dot-notation. + +```python +>>> help(str.isupper) +``` + +Will show: + +```text +isupper(self, /) + Return True if the string is an uppercase string, False otherwise. + + A string is uppercase if all cased characters in the string are uppercase and + there is at least one cased character in the string. +``` + +{{% notice note %}} +Press the 'q' key to exit this screen. +{{% /notice %}} + +Keep note of these three helpful methods, and don't be afraid to use them throughout class. + +## Why Use The REPL? + +In this class, we'll be working with a mix of the REPL and running code in files, like we'll see in the next section. You'll want to store code for reuse in files, while you can consider the REPL more of a scratch area. It's the place where you can instantly play around and try out Python code. The REPL is a handy tool for both beginner and advanced Python programmers. + +We'll use the REPL for the majority of Day 1, and move on to running Python files in Day 2. \ No newline at end of file diff --git a/website/content/01-introduction/02-requirements/05-vs-code/05-running-code.md b/website/content/01-introduction/02-requirements/05-vs-code/05-running-code.md new file mode 100644 index 0000000..3ebac63 --- /dev/null +++ b/website/content/01-introduction/02-requirements/05-vs-code/05-running-code.md @@ -0,0 +1,63 @@ +--- +title: "Running Code" +date: 2019-02-10T18:14:45-08:00 +draft: false +weight: 5 +--- + +VS Code provides a built in terminal that allows us to easily run our programs. We'll mostly be working with Python program files at the end of Day 1, and for most of Day 2. + +### Creating Python Files with the `*.py` extension + +You know a file is a Python program when it ends with a `.py` extension. + +#### Creating New Python Files + +To create a new file in VS Code, hit `Ctrl+N` on Windows and Linux, and `⌘N` (command + N) on Mac OS. + +This will open a new file. Next, save the file with a `.py` extension. + +{{% notice info %}} +Create a new simple Python program in a file called `hello.py` in your `pyworkshop` directory with the following contents: +{{% /notice %}} + +```python +# in file: hello.py +greetings = ["Hello", "Bonjour", "Hola"] + +for greeting in greetings: + print(f"{greeting}, World!") +``` + +#### Opening The VS Code Terminal Window + +Next, you'll need to open your terminal if you don't have it open already. The quick keyboard shortcut to do that is Ctrl - ` + +{{% notice note %}} +If you already had your Python REPL open, you'll need to select a terminal with a shell in it (generally, the one labeled with `1:`). +{{% /notice %}} + +![](/02-introduction-to-python/175-running-code/images/terminal-drop-down.png) +![](/02-introduction-to-python/175-running-code/images/terminal-drop-down-select.png) + +#### Running The File + +Once you've opened your `hello.py` file and selected your new terminal window, open the VS Code command palette. + +{{% notice note %}} +Open the command palette with `Ctrl+Shift+P` on Windows and Linux, and `⌘⇧P` (command + shift + P) on Mac OS. +{{% /notice %}} + +Select Python: Run Python File in Terminal + +![](/02-introduction-to-python/175-running-code/images/vs-code-run-file-command-palette.png) + +You should see: + +```bash +Hello, World! +Bonjour, World! +Hola, World! +``` + +How easy was that? 🎉 \ No newline at end of file diff --git a/website/content/01-introduction/02-requirements/05-vs-code/_index.md b/website/content/01-introduction/02-requirements/05-vs-code/_index.md new file mode 100644 index 0000000..721f959 --- /dev/null +++ b/website/content/01-introduction/02-requirements/05-vs-code/_index.md @@ -0,0 +1,12 @@ ++++ +title = "Setting Up VS Code" +date = 2019-01-25T15:05:31-06:00 +weight = 57 +chapter = true ++++ + +![Command Palette](/01-introduction/02-requirements/05-vs-code/images/vs-code-logo.png?width=5pc "The VS Code Command Palette") + +# Visual Studio Code + +Visual Studio Code (or VS Code) is a free and open source graphical code editor. VS Code offers plenty of extensions to customize your experience. We'll be using the powerful Python extension to edit our code because it's jam-packed with helpful features. \ No newline at end of file diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/command-palette.png b/website/content/01-introduction/02-requirements/05-vs-code/images/command-palette.png new file mode 100644 index 0000000..eeb469f Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/command-palette.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/install-circuitpy.mp4 b/website/content/01-introduction/02-requirements/05-vs-code/images/install-circuitpy.mp4 new file mode 100644 index 0000000..9dcad87 Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/install-circuitpy.mp4 differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/install-pylint.png b/website/content/01-introduction/02-requirements/05-vs-code/images/install-pylint.png new file mode 100644 index 0000000..ad759f6 Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/install-pylint.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/interpreter.1.png b/website/content/01-introduction/02-requirements/05-vs-code/images/interpreter.1.png new file mode 100644 index 0000000..df53ee0 Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/interpreter.1.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/interpreter.2.png b/website/content/01-introduction/02-requirements/05-vs-code/images/interpreter.2.png new file mode 100644 index 0000000..1a3dc65 Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/interpreter.2.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/open-folder.png b/website/content/01-introduction/02-requirements/05-vs-code/images/open-folder.png new file mode 100644 index 0000000..fcf065d Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/open-folder.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/popups copy.png b/website/content/01-introduction/02-requirements/05-vs-code/images/popups copy.png new file mode 100644 index 0000000..98c52db Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/popups copy.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/popups.png b/website/content/01-introduction/02-requirements/05-vs-code/images/popups.png new file mode 100644 index 0000000..75398ff Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/popups.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/repl-start.png b/website/content/01-introduction/02-requirements/05-vs-code/images/repl-start.png new file mode 100644 index 0000000..1b10fea Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/repl-start.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/repl.png b/website/content/01-introduction/02-requirements/05-vs-code/images/repl.png new file mode 100644 index 0000000..0c7e4ed Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/repl.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/select-linter.png b/website/content/01-introduction/02-requirements/05-vs-code/images/select-linter.png new file mode 100644 index 0000000..0eb22ba Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/select-linter.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/select-pylint.png b/website/content/01-introduction/02-requirements/05-vs-code/images/select-pylint.png new file mode 100644 index 0000000..486428e Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/select-pylint.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/selected-interpreter.png b/website/content/01-introduction/02-requirements/05-vs-code/images/selected-interpreter.png new file mode 100644 index 0000000..04e7ba2 Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/selected-interpreter.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/vs-code-icon.png b/website/content/01-introduction/02-requirements/05-vs-code/images/vs-code-icon.png new file mode 100644 index 0000000..002be7d Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/vs-code-icon.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/vs-code-logo.png b/website/content/01-introduction/02-requirements/05-vs-code/images/vs-code-logo.png new file mode 100644 index 0000000..3245933 Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/vs-code-logo.png differ diff --git a/website/content/01-introduction/02-requirements/05-vs-code/images/welcome-page.png b/website/content/01-introduction/02-requirements/05-vs-code/images/welcome-page.png new file mode 100644 index 0000000..c0cb7b2 Binary files /dev/null and b/website/content/01-introduction/02-requirements/05-vs-code/images/welcome-page.png differ diff --git a/website/content/01-introduction/02-requirements/_index.md b/website/content/01-introduction/02-requirements/_index.md new file mode 100644 index 0000000..e11a85a --- /dev/null +++ b/website/content/01-introduction/02-requirements/_index.md @@ -0,0 +1,157 @@ +--- +title: Requirements +weight: 3 +--- + +{{% notice warning %}} +You'll need a few mandatory prerequisites for successfully participating in the course. +{{% /notice %}} + +- A Linux, Mac OS, a Windows 10 Machine +- Python3.7 +- Visual Studio Code +- The Python Extension for Visual Studio Code + + +#### Additional Instructions For Windows Users + +- Note: Always select "Allow this app to make changes to your device" during the installation process. +- On the last page of the Python 3 installer, select "Disable Path Limit" before hitting close. +- In the start menu, right click on "Windows PowerShell". Select "Run as an administrator" +- In the PowerShell terminal window, that opens, type: + +(`>` means prompt, don't type that in) + +```powershell +> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned +``` + +Then, type `Y` for Yes. + +{{% notice info %}} +Keep this window open for the following steps to create a virtual environment! +{{% /notice %}} + +### Downloads + +{{% button href="https://www.python.org/downloads/" icon="fas fa-download" %}}Download Python3{{% /button %}} +{{% button href="https://code.visualstudio.com/download" icon="fas fa-download" %}}Download Visual Studio Code{{% /button %}} +{{% button href="https://marketplace.visualstudio.com/items?itemName=ms-python.python" icon="fas fa-download" %}}Python Extension for Visual Studio Code{{% /button %}} + +## Making sure you're ready + +To make sure you have all the prerequisites properly installed: + +### Checking for Python 3.7 + +#### For Windows + +Using the same PowerShell window from earlier, type: + +```powershell +> py -3.7 +``` + +This should open a REPL window with a prompt. + +{{% notice tip %}} +Press `Ctrl + Z` followed by Enter to exit this screen and go back to your prompt. +{{% /notice %}} + +#### For Mac / Linux + +Type the following on your terminal. +```bash +$ python3 --version +``` + +You should see +```bash +Python 3.7.2 +``` + +{{% notice info %}} +If you don't see a Python version greater than 3.7, please follow the instructions for [installing Python3](https://www.python.org/downloads/) again. +{{% /notice %}} + +### Creating a Virtual Environment and The Project Folder + +A Virtual Environment in Python is a self-contained directory that contains a Python installation for a particular version of the language. + +It's a very useful way to make sure that we're using the right Python version when we're working on a particular project. + +Let's create a project directory and a Python 3.7 Virtual Environment. + +#### For Windows + +Using the same PowerShell terminal from earlier, type the following commands in one by one: + +```powershell +> cd $home +> mkdir pyworkshop +> cd pyworkshop +> py -3 -m venv env +> env\scripts\activate +``` + +Your prompt should now look like this, but with your own username. + +```powershell +(env) PS C:\Users\nina\pyworkshop> +``` + +{{% notice tip %}} +`env\scripts\activate` is how you *activate* your virtual environment in Windows. You'll want to do that each time you enter this Python project directory from a new shell. +{{% /notice %}} + +#### For Mac / Linux + +Open a terminal window. Type the following. + +(Do not type the `$` character, that signifies a prompt.) + +```bash +$ cd +$ mkdir pyworkshop +$ cd pyworkshop +$ python3.7 -m venv env +$ source env/bin/activate +``` + +{{% notice tip %}} +`source env/bin/activate` is how you *activate* your virtual environment on Mac or Linux. You'll want to do that each time you enter this Python project directory from a new shell. +{{% /notice %}} + +Your prompt will look like this to indicate that the virtual environment is active. + +```bash +(env) $ +``` + +{{% notice info %}} +You are expected to work from this project folder for the duration of the class, with an activated virtual environment. +{{% /notice %}} + +### Checking VS Code + +Look for VS Code in your Applications, or type the following in your Mac/Linux *or* Powershell terminal. + +```text +$ code --version +``` + +You should see something like: + +```text +1.32.3 +a3db5be9b5c6ba46bb7555ec5d60178ecc2eaae4 +x64 +``` + +{{% notice info %}} +If you don't see VS Code, please follow the instructions for [installing VS Code](https://code.visualstudio.com/download) again. +{{% /notice %}} + +{{% notice note %}} +Note that after the course you can use the IDE of your choice to continue on your Python adventure. +{{% /notice %}} diff --git a/website/content/01-introduction/_index.md b/website/content/01-introduction/_index.md new file mode 100644 index 0000000..303f7fb --- /dev/null +++ b/website/content/01-introduction/_index.md @@ -0,0 +1,24 @@ +--- +title: Course Introduction +weight: 1 +chapter: true +--- + +# Learn Python + +Welcome to Introduction and Intermediate Python, recorded for Frontend Masters. + +Created by Nina Zakharenko. @nnja. + +Watch the accompanying screencast via subscription on [FrontendMasters.com](https://frontendmasters.com/teachers/nina-zakharenko/). + +{{< figure src="/images/fem.png" title="Watch the course" link="https://frontendmasters.com/courses/python/">}} + +Stay up to date with me on LinkedIn, or to contact me directly, please send me an email at learnpython@nnja.io. + +You can find the content and exercises on the Github repo. + + +The course content is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.Creative Commons License + +I'd like to encourage learning individuals to learn Python from the course material for free, but if you'd like to use the material for your own workshop or for commercial use, please consider supporting my work and [watch the screencast](https://frontendmasters.com/teachers/nina-zakharenko/) instead. \ No newline at end of file diff --git a/website/content/01-introduction/images/arrows.png b/website/content/01-introduction/images/arrows.png new file mode 100644 index 0000000..19ec202 Binary files /dev/null and b/website/content/01-introduction/images/arrows.png differ diff --git a/website/content/01-introduction/images/clear_history.png b/website/content/01-introduction/images/clear_history.png new file mode 100644 index 0000000..cec447f Binary files /dev/null and b/website/content/01-introduction/images/clear_history.png differ diff --git a/website/content/01-introduction/images/copy.png b/website/content/01-introduction/images/copy.png new file mode 100644 index 0000000..61cbd87 Binary files /dev/null and b/website/content/01-introduction/images/copy.png differ diff --git a/website/content/01-introduction/images/edit-page.png b/website/content/01-introduction/images/edit-page.png new file mode 100644 index 0000000..c2a7886 Binary files /dev/null and b/website/content/01-introduction/images/edit-page.png differ diff --git a/website/content/01-introduction/images/expand-section.png b/website/content/01-introduction/images/expand-section.png new file mode 100644 index 0000000..d497e4d Binary files /dev/null and b/website/content/01-introduction/images/expand-section.png differ diff --git a/website/content/01-introduction/images/header.png b/website/content/01-introduction/images/header.png new file mode 100644 index 0000000..402114a Binary files /dev/null and b/website/content/01-introduction/images/header.png differ diff --git a/website/content/01-introduction/images/search.png b/website/content/01-introduction/images/search.png new file mode 100644 index 0000000..ce48024 Binary files /dev/null and b/website/content/01-introduction/images/search.png differ diff --git a/website/content/01-introduction/images/toc.png b/website/content/01-introduction/images/toc.png new file mode 100644 index 0000000..9b07833 Binary files /dev/null and b/website/content/01-introduction/images/toc.png differ diff --git a/website/content/02-introduction-to-python/010-Best-Practices/01-anatomy-of-a-python-program.md b/website/content/02-introduction-to-python/010-Best-Practices/01-anatomy-of-a-python-program.md new file mode 100644 index 0000000..d5a761e --- /dev/null +++ b/website/content/02-introduction-to-python/010-Best-Practices/01-anatomy-of-a-python-program.md @@ -0,0 +1,171 @@ +--- +title: "Anatomy of a Python Program" +date: 2019-03-08T23:25:07-08:00 +draft: false +weight: 4 +--- + +Let's take a very quick look at a Python program that uses the GitHub Search API to display a list of popular repositories in three different programming languages, sorted by the amount of stars that they have. It may be hard to believe, but by the end of the day, you'll be able to write a program just like this. + +### A Python program for using the GitHub search API + +```python +""" +A small Python program that uses the GitHub search API to list +the top projects by language, based on stars. +""" + +import requests + +GITHUB_API_URL = "https://api.github.com/search/repositories" + + +def create_query(languages, min_stars=50000): + query = f"stars:>{min_stars} " + + for language in languages: + query += f"language:{language} " + + # a sample query looks like: "stars:>50 language:python language:javascript" + return query + + +def repos_with_most_stars(languages, sort="stars", order="desc"): + query = create_query(languages) + params = {"q": query, "sort": sort, "order": order} + + response = requests.get(GITHUB_API_URL, params=params) + status_code = response.status_code + + if status_code != 200: + raise RuntimeError(f"An error occurred. HTTP Code: {status_code}.") + else: + response_json = response.json() + return response_json["items"] + + +if __name__ == "__main__": + languages = ["python", "javascript", "ruby"] + results = repos_with_most_stars(languages) + + for result in results: + language = result["language"] + stars = result["stargazers_count"] + name = result["name"] + + print(f"-> {name} is a {language} repo with {stars} stars.") +``` + +### Step by step + +{{%expand "Expand this section to walk through the program step-by-step." %}} + +There's some helpful information about the program in a comment at the top. The comment is separated with triple quotes `"""`. + +The first thing we're doing is importing the popular `requests` library. We'll be using it to make API calls. + +```python +import requests +``` + +Next, we define the URL for the GitHub search API, which we found in the documentation for that endpoint in a variable called `GITHUB_API_URL`. This variable is named in all upper case because it's a constant. A value that we don't expect to change over the course of our program. + +```python +GITHUB_API_URL = "https://api.github.com/search/repositories" +``` + +First, the code in the `main` method will run. This code defines three different languages we'd like to see results for. + +```python + languages = ["python", "javascript", "ruby"] +``` + +Then calls the `repos_with_most_stars` method with that language list, and gets back a list of results. + +```python + results = repos_with_most_stars(languages) +``` + +Let's jump to the `repos_with_most_stars` function. We know it's a function because of the `def` keyword, followed by a function name, parameters -- both required and optional in parenthesis, and finally, a colon `:`. It accepts a list of languages to sort by, as well as some *optional* parameters for how we want to sort the list. By default, we sort it by the number of stars the repo has, in descending order. + +```python +def repos_with_most_stars(languages, sort="stars", order="desc"): +``` + +Next, we need to create a query string that this particular API understands. We do that in the `create_query` function. This function takes the languages as a required parameter, and the minimum number of stars we'd like to query for as an optional parameter. + +```python +def create_query(languages, min_stars=50000): +``` + +In this function, we create a query string that looks like this `stars:>50000 language:python language:javascript language:ruby". These parameters of this query string are defined by the expectations of the API that we're working with. We return this value. + +Since the query is a little confusing, there's a comment in the code that explains what it does. The comment starts with `# `. + +Back in the `repos_with_most_stars` function, we use our query string as part of the parameters that we'll be passing in to the API, along with the URL that we'll be using. + +We declare them in a dictionary called `params`, like this: + +```python +# looks like: q=stars:>50000+language:python+language:javascript+language:ruby+&sort=stars&order=desc' +params = {"q": query, "sort": sort, "order": order} +``` + +These params map to part of a URL that will look like this: `q=stars:>50000+language:python+language:javascript+language:ruby+&sort=stars&order=desc` + +Next, we use these parameters as well as the URL defined in `GITHUB_API_URL` to call the API using the requests library. + +```python + response = requests.get("https://api.github.com/search/repositories", params=params) +``` + +We're requesting the data at this URL: [https://api.github.com/search/repositories?q=stars:>50000+language:python+language:javascript+language:ruby+&sort=stars&order=desc](https://api.github.com/search/repositories?q=stars:>50000+language:python+language:javascript+language:ruby+&sort=stars&order=desc) + +We're using the requests library because it allows us to quickly and easily work with the data returned from an API. + +Next, we quickly check the HTTP status code to make sure that it was 200. If it wasn't, that means that something went wrong with our request and we'll want to throw an exception to quit our program. + +```python + if status_code != 200: + raise RuntimeError(f"An error occurred. HTTP Code: {status_code}.") +``` + +If everything went OK, we get the JSON from the response as a Python dictionary, using `response.json()`, next, we `return` the data in the "items" key back to the main method. In this case, we don't care about the additional data that the API returned. + +```python + response_json = response.json() + return response_json["items"] +``` + + +Next, back in the main method, we go through each result, and print out a line with the name of the repo, the language, and the amount of stars it has. + +```python + print(f"-> {name} is a {language} repo with {stars} stars.") +``` + +{{% /expand %}} + + +### End Result + +The end result looks something like this: + +```bash +(env) $ python dayone.py + +-> freeCodeCamp is a JavaScript repo with 298046 stars. +-> bootstrap is a JavaScript repo with 131403 stars. +-> vue is a JavaScript repo with 130099 stars. +-> react is a JavaScript repo with 123969 stars. +-> d3 is a JavaScript repo with 82932 stars. +-> javascript is a JavaScript repo with 82514 stars. +-> react-native is a JavaScript repo with 74810 stars. +-> create-react-app is a JavaScript repo with 64725 stars. +-> awesome-python is a Python repo with 63693 stars. +... and more results +``` + +{{% notice note %}} +Don't worry if you didn't understand this code, we'll be building this program in the workshop today. +{{% /notice %}} \ No newline at end of file diff --git a/website/content/02-introduction-to-python/010-Best-Practices/02-brief-history.md b/website/content/02-introduction-to-python/010-Best-Practices/02-brief-history.md new file mode 100644 index 0000000..6f7ee74 --- /dev/null +++ b/website/content/02-introduction-to-python/010-Best-Practices/02-brief-history.md @@ -0,0 +1,59 @@ +--- +title: "About Python 🐍" +date: 2019-01-24T11:58:21-08:00 +draft: false +weight: 2 +--- + +### What is Python? + +Python is a programming language created by Guido Van Rossum in the late 1980s. + +You might be surprised to learn that Python is 30 years old! + +### Where is it used? + +A common misconception is that Python is a scripting language. It's used at companies from Reddit, to Netflix, to Dropbox. + +### What's all this about Python 2 vs Python 3 + +This part is a bit of a stain on Python's history. Python 3 was released 2008, and its adoption was slow. First and foremost because it took popular packages a fair amount of time to port over their code. + +This debate is now over. Python 2 will reach end-of-life in 2020, meaning that important updates - including security updates - will stop being released. That's why this course focuses on *Python3 only*. + +### Why Python? + +Python the language is [open source](https://github.com/python/cpython). + +Python has a wide variety of applications such as: + +- AI/ML + - SciPi + - NumPy + - Pandas + - PyTorch +- Hardware & Micro-controllers + - Raspberry Pi + - MicroPython + - CircuitPython +- Web Development + - Django + - Flask +- Scripting + - DevOps + Configuration scripts + +Python has an incredibly rich fully featured [standard library](https://docs.python.org/3/library/), as well as the [PyPI Package Index](https://pypi.org/) for 3rd party packages, which as of February 2019 contains 167,107 packages. + +Python is considered to be a "batteries included" language, because the standard library contains a majority of the libraries and packages you'll need in a standard application. + + +### Continuing In The Community + +The absolute best part about Python is the incredibly supportive community. + +The biggest yearly conference is [PyCon US](https://us.pycon.org) with approximately 4000 attendees. + +There are many local user groups worldwide, with many listed on [this wiki](https://wiki.python.org/moin/LocalUserGroups). + +There are many supportive groups for women and non-binary developers, such as [PyLadies](https://www.pyladies.com/) and [DjangoGirls](https://djangogirls.org/). These organizations have chapters in most major cities. diff --git a/website/content/02-introduction-to-python/010-Best-Practices/03-conventions.md b/website/content/02-introduction-to-python/010-Best-Practices/03-conventions.md new file mode 100644 index 0000000..688bcdd --- /dev/null +++ b/website/content/02-introduction-to-python/010-Best-Practices/03-conventions.md @@ -0,0 +1,10 @@ ++++ +title = "Conventions" +date = 2019-02-10T21:46:57-08:00 +weight = 3 +draft = false ++++ + +### PEP8 + +[PEP8](https://pep8.org/) is a Python coding standard, that sets guidelines for how our Python code should look like. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/010-Best-Practices/_index.md b/website/content/02-introduction-to-python/010-Best-Practices/_index.md new file mode 100644 index 0000000..022f813 --- /dev/null +++ b/website/content/02-introduction-to-python/010-Best-Practices/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Why Choose Python" +date = 2019-01-25T15:21:05-06:00 +weight = 10 +chapter = true +draft = false +pre = "1. " ++++ + +### Chapter 1 + +# Why Python? + +Let's talk about the anatomy of a Python program, what makes Python unique, and cover some best practices. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/020-basic-data-types/00-variables.md b/website/content/02-introduction-to-python/020-basic-data-types/00-variables.md new file mode 100644 index 0000000..8f33146 --- /dev/null +++ b/website/content/02-introduction-to-python/020-basic-data-types/00-variables.md @@ -0,0 +1,104 @@ +--- +title: "Variables and Types" +date: 2019-02-03T23:26:13-08:00 +draft: false +weight: 1 +--- + +### Naming Variables + +Python variables can't start with a number. In general, they're named all lower case, separated by underscores. Unlike other languages, that name their variables with camelCase. + +You don't want to name your variables the same as the *types* that we'll be working with. For example **don't** name your variables `int`, `list`, `dict`. Etc. + +### Open The REPL + +Learn about variables by typing along in the Python REPL with me. + +{{% notice tip %}} +Open the REPL from VS Code by opening the command palette (ctrl + shift + P on Windows, or cmd + shift + P on Mac) and selecting Python: Start REPL +{{% /notice %}} + +Any Python code that starts with the `>>>` symbols indicates that it was typed into a REPL. + +You can then use ctrl + ` (backtick) to open and close the VS Code terminal on Mac, or ctrl + ' (single quote) on Windows. You won't lose your work in the REPL unless you close VS Code. + +{{% notice info %}} +If you'd like to save the contents of your REPL as class goes on, you can right click, select all, and paste it into a new file. +{{% /notice %}} + +## Variables + +Variables in Python allow us to store information and give it a label that we can use to retrieve that information later. We can use variables to store numbers, strings (a sequence of characters), or even more complex data types like lists and dictionaries. + +We assign _values_ to _variables_ by putting the _value_ to the right of an equal sign. + +Because Python is a *dynamic* language, we don't need to declare the type of the variables before we store data in them. + +That means that this is valid Python code: + +```python +>>> x = 42 +``` + +Unlike typed languages, the type of what's contained in Python variables can change at any time. + +For example, the below is perfectly valid Python code: + +```python +>>> x = 42 +>>> x = "hello" +``` + +Here, the value of the variable `x` changed from a number to a string. + +When creating variables, there are a few best practices you should follow. + +#### Naming Variables + +Convention says that variables should be named in lowercase, with whole words separated by underscores. + +{{% notice note %}} +If you want to learn more about Python naming conventions look at [PEP8](https://www.python.org/dev/peps/pep-0008/#naming-conventions) during a break. +{{% /notice %}} + +Because Python is a dynamic language and you don't have type hints to explain what's stored inside a variable while reading code, you should do your best naming your variables to describe what is stored inside of them. + +It's ok to be _verbose_. For example, `n` is a poor variable name, while `numbers` is a better one. If you're storing a collection of items, name your variable as a plural. + +{{% notice note %}} +Learn more about great naming practices for dynamic types by watching this 30-minute [talk by Brandon Rhodes](https://www.youtube.com/watch?v=YklKUuDpX5c). +{{% /notice %}} + +#### Naming Gotchas + +There are some things that you can't name your variables, such as `and`, `if`, `True`, or `False`. That's because Python uses these names for program control structure. + +You can't start your variable name with a digit, although your variable name can end in a digit. Your variable name can't contain special characters, such as `!`, `@`, `#`, `$`, `%` and more. + +{{% notice warning %}} +💣 Python will let you override built-in methods and types without a warning so don't name your Python variables things like `list`, `str`, or `int`. +{{% /notice %}} + +If you notice your program behaving oddly and you can't find the source of the bug, double check the list of [built-in functions](https://docs.python.org/3/library/functions.html) and [built-in types](https://docs.python.org/3/library/stdtypes.html) to make sure that your variable names don't conflict. + +## Types + +Python has a very easy way of determining the type of something, with the `type()` function. + +```python +>>> num = 42 +>>> type(num) + +``` + +### No-Value, `None`, or Null Value + +There's a special type in Python that signifies no value at all. In other languages, it might be called Null. In Python, it's called `None`. + +If you try to examine a variable on the REPL that's been set to `None`, you won't see any output. We'll talk more about the `None` type later in the class. + +```python +>>> x = None +>>> x +``` diff --git a/website/content/02-introduction-to-python/020-basic-data-types/02-numbers.md b/website/content/02-introduction-to-python/020-basic-data-types/02-numbers.md new file mode 100644 index 0000000..bbaea7a --- /dev/null +++ b/website/content/02-introduction-to-python/020-basic-data-types/02-numbers.md @@ -0,0 +1,58 @@ +--- +title: "Numbers" +date: 2019-02-03T23:14:25-08:00 +draft: false +weight: 2 +--- + +First, open up the REPL. + +{{% notice tip %}} +Remember, you'll learn best if you type along with me. +{{% /notice %}} + +There are three different types of numbers in Python: `int` for Integer, Float, and Complex. + +```python +# These are all integers +x = 4 +y = -193394 +z = 0 +``` + +```python +# These are all floats +x = 5.0 +y = -3983.2 +z = 0. +``` + +```python +# This is a complex number +x = 42j +``` + +In Python, Integers and other simple data types are just objects under the hood. That means that you can create new ones by calling methods. You can provide either a number, or a string. This will come in handy later on in the course. + +```python +x = int(4) +y = int('4') +z = float(5.0) +``` + +Python also provides a `decimal` library, which has certain benefits over the `float` datatype. For more information, refer to the [Python documentation](https://docs.python.org/3/library/decimal.html). + +## Mathematical Operations + +Numbers can be added together. If you add a `float` and an `int`, the resulting type will be a `float`. + +If you divide two `int`s (integers), the result will be of type `float`. + +## Boolean Types + +In Python, Booleans are of type `bool`. Surprisingly, the boolean types `True` and `False` are also numbers under the hood. + +* `True` is `1` under the hood. +* `False` is `0` under the hood. + +That means you can do silly things, like add two Boolean numbers together, but I'll cover why this is a useful Python feature later in the course. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/020-basic-data-types/03-strings.md b/website/content/02-introduction-to-python/020-basic-data-types/03-strings.md new file mode 100644 index 0000000..19790e7 --- /dev/null +++ b/website/content/02-introduction-to-python/020-basic-data-types/03-strings.md @@ -0,0 +1,75 @@ +--- +title: "Strings" +date: 2019-02-03T23:14:35-08:00 +draft: false +weight: 3 +--- + +#### Representing Strings + +Strings in Python can be enclosed either with single quotes like `'hello'` or double quotes, like `"hello"`. + +Strings can also be **concatenated** (added together) using the `+` operator to combine an arbitrary number of Strings. For example: + +
1334
+ +```python +salutation = "Hello " +name = "Nina" +greeting = salutation + name +# The value of greeting will be "Hello Nina" +``` + +To use the same type of quote within a string, that quote needs to be **escaped** with a `\` - backwards slash. + +```python +greeting = 'Hello, it\'s Nina' +``` + +Alternately, mixed quotes can be present in a Python string without escaping. + +```python +# Notice that the single quote ' is surrounded by +# double quotes, "" +greeting = "Hello, it's Nina" +``` + +Long multi-line strings can be represented in between `"""` (triple quotes), but the whitespace will be part of the string. + +```python +long_greeting = """ + Greetings and salutations, dear Nina. + I'm superfluous with my words, + and require more space to say Hello!" + """ +``` + +#### Printing Strings + +Strings can be printed out using the `print()` function in Python. While you're working the REPL, you'll see that variables are displayed for you. When you move on to writing standalone Python programs, that will no longer be the case. + +To use the `print()` function, call it with a regular or formatted string. + +```python +>>> print("Hello") +Hello +>>> name = "Nina" +>>> print(name) +Nina +``` + +#### String Formatting + +There are several types of string formatting in Python. + +If you're using Python 3.7 and above (remember to check with `python --version` on the command line) you can use my favorite type of string formatting, and the one I'll be using for the course called f-strings. + +```python +>>> name = "Nina" +>>> greeting = f"Hello, {name}" + +>>> print(greeting) +Hello, Nina +``` + +f-strings allow you to simply and easily reference variables in your code, and as a bonus, they're *much* faster. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/020-basic-data-types/05-common-string-and-number-errors.md b/website/content/02-introduction-to-python/020-basic-data-types/05-common-string-and-number-errors.md new file mode 100644 index 0000000..0e6e4c0 --- /dev/null +++ b/website/content/02-introduction-to-python/020-basic-data-types/05-common-string-and-number-errors.md @@ -0,0 +1,72 @@ +--- +title: "Common Mistakes" +date: 2019-02-10T18:33:43-08:00 +draft: false +weight: 5 +--- + +There are a few common errors that you'll encounter when working with Strings and numbers. Remember, in Python program errors are called Exceptions. By going over what they are, you'll be able to recognize them immediately. + +### Scenario 1: Mismatched string quotes + +{{% notice info %}} +Mismatched string quotes will result in a `SyntaxError` +{{% /notice %}} + +When we try to start a String with one type of quote, and end with another, we'll see a syntax error. + +For example, starting the string Hello with a double quote, and ending in a single quote, like this: + +#### Input: **"**Hello**'** + +For example, in the REPL: + +```python +>>> name = 'Hello" + File "", line 1 + name = "Hello' + ^ +SyntaxError: EOL while scanning string literal +``` + +**Solution:** use matching quote types for defining your strings. Either single quotes `'Hello'` or double quotes `"Hello"`. + +### Scenario 2: Trying to print a String and a number with concatenation using the "+" symbol. + +{{% notice info %}} +Trying to add or concatenate a String and a number will result in a `TypeError` +{{% /notice %}} + +If you add try to add (or concatenate) a String and a number, you'll get an error saying that adding the two types together isn't possible. + +#### Input: 3 + "Three" + +In the REPL: +```python +>>> print(3 + " Three") +Traceback (most recent call last): + File "", line 1, in +TypeError: unsupported operand type(s) for +: 'int' and 'str' +``` + +**Solutions:** + +There are two possible solutions here, for two different scenarios. + +In the first scenario, you'd like to add a number to a string via concatenation. In order to do that, you must first convert the number to a string via the `str()` method. + +In the REPL: +```python +>>> my_num = 3 +>>> print(str(my_num) + " Three") +3 Three +``` + +In the second scenario, you'd like to a convert a number that's contained in a string (ex: `"3"`) into an Integer, so you can use it like any other number. In this case, you'd like to convert it to an Integer, with the `int()` method. + +In the REPL: +```python +>>> str_num = "3" +>>> print(int(str_num) + 5) +8 +``` \ No newline at end of file diff --git a/website/content/02-introduction-to-python/020-basic-data-types/10-exercise.md b/website/content/02-introduction-to-python/020-basic-data-types/10-exercise.md new file mode 100644 index 0000000..6125d85 --- /dev/null +++ b/website/content/02-introduction-to-python/020-basic-data-types/10-exercise.md @@ -0,0 +1,76 @@ +--- +title: "Practice" +date: 2019-02-04T15:28:32-08:00 +draft: false +weight: 10 +pre: "⭐️ " +--- + +## Basic Data Types, Strings and Numbers + +### Types + +List the type of the following variables using the `type()` function. + +```python +>>> x = 42 +>>> y = 3 / 4 +>>> z = int('7') +>>> a = float(5) +>>> name = "Nina" +``` + +### Numbers + +Calculate the amount of rent you pay daily, by taking your monthly rent and diving it by 30. + +```python +>>> rent = 480 +>>> per_day = rent / 30 +>>> print(per_day) +16.0 +``` + +### Strings + +Try printing some things to your REPL: + +```python +>>> print("Hello world") +Hello world +>>> name = "Nina" +>>> print("My name is", name) +My name is Nina +``` + +There are three different ways to format strings in Python3. You may run into %-formatting and `str.format()` in older code. These are still common in Python but no longer recommended, due to readability concerns. + +```python +>>> name = "Nina" +>>> print("Hello, my name is %s" % name) +Hello, my name is Nina +``` + +The current recommended way to format string is with f-Strings. f-Strings are much more readable and easier to maintain than the previous methods. With f-Strings, your string is prepended with the letter `f` and your variables or expressions to interpret are placed in `{brackets}`. + +```python +>>> name = "Nina" +>>> print(f"Hello, my name is {name} and I pay ${rent / 30} in rent per day") +Hello, my name is Nina and I pay $16.0 in rent per day +``` + +### Helper Functions + +Python has a few built-in functions to help you if you get stuck. `type()` tells you what an object's type is, for example a string (`str`) or integer (`int`). `dir()` returns a list of valid attributes for an object, so you can quickly see what variables an object has or what functions you can call on it. `help()` brings up helpful documentation on any object. You can also type `help()` on its own to bring an interactive help console. + +```python +>>> x = 42 +>>> y = 3 / 4 +>>> name = "Nina" +>>> type(x) + +>>> type(y) + +>>> type(name) + +``` \ No newline at end of file diff --git a/website/content/02-introduction-to-python/020-basic-data-types/_index.md b/website/content/02-introduction-to-python/020-basic-data-types/_index.md new file mode 100644 index 0000000..9215a11 --- /dev/null +++ b/website/content/02-introduction-to-python/020-basic-data-types/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Basic Data Types" +date = 2019-01-25T15:02:51-06:00 +weight = 20 +chapter = true +draft = false +pre = "2. " ++++ + +### Chapter 2 + +# Basic Data Types + +Working with numbers, strings, and more. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/060-functions/10-defining-functions.md b/website/content/02-introduction-to-python/060-functions/10-defining-functions.md new file mode 100644 index 0000000..6d0befa --- /dev/null +++ b/website/content/02-introduction-to-python/060-functions/10-defining-functions.md @@ -0,0 +1,207 @@ +--- +title: "Defining Functions" +date: 2019-02-10T18:16:01-08:00 +draft: false +weight: 10 +--- + +The purpose of functions in Python are to create reusable code. If we find ourselves copying and pasting the same code multiple times, that's a good sign that a function might help! + +### Anatomy of a function + +This is the recipe for defining a Python function: + +1. `def`: the `def` keyword, telling Python we're about to start a function definition +1. a name for the function +1. `(`: opening parenthesis +1. (optional) the **names** of one or more arguments, separated with `,` +1. (optional) the **names** and **values** of one or more default arguments, separated with (`,`) *note: we'll see these in the next section* +1. `)` closing parenthesis +1. `:` a colon + +A function in Python is defined with the `def` keyword, followed by the function names, zero or more argument names contained in parenthesis `()`, and a colon `:` to indicate the start of the function. + +The contents of the function then follow. + +Then, an *optional* `return` statement can follow, if the function plans on passing data back to the caller. + +```python +# A Basic Function that accepts no arguments and returns nothing. +def hello_world(): + print("Hello, World!") + + +# A Function that accepts two arguments, and returns the value of +# those numbers added together. +def add_numbers(x, y): + return x + y +``` + +{{% notice tip %}} +If you **forget** the recipe while trying to create a function, Python will help you remember with a `SyntaxError`. +{{% /notice %}} + +For example, trying to create a function without the colon `:`: + +```python +>>> def hello_world() + File "", line 1 + def hello_world() + ^ +SyntaxError: invalid syntax +``` + +And trying to create a function without the parenthesis `()`: + +```python +>>> def hello_world: + File "", line 1 + def hello_world: + ^ +SyntaxError: invalid syntax +``` + +### Function Contents + +The recipe for function contents: + +1. a new line +1. indentation (press tab on your keyboard) +1. one or more lines +1. (optional) a `return` statement + +#### `return` statement + +A `return` statement is a way to "short-circuit" the function. + +Using a `return` statement, you can optionally pass back data to the caller of your function. + +##### with no `return` statement + +If a function doesn't have a return statement, it implicitly returns `None`. + +```python +>>> def foo(): +... x = 5 +... +>>> val = foo() +>>> type(val) + +``` + +##### with a `return` statement, but no value + +If a function has a return statement, but no value, it also returns `None`. This is typically used to control the flow of a program. + +```python +>>> def foo(): +... x = 5 +... return +... +>>> val = foo() +>>> type(val) + +``` + +##### with a `return` statement and a value + +To return a value from a function, just type it after the `return` statement. You can return anything from a Python function, including other functions! For today, we'll focus on simple and complex data types. + +```python +>>> def foo(): +... x = 5 +... return x +... +>>> val = foo() +>>> val +5 +``` + +As we explore simple functions, our `return` statements will usually be at the end of the function, but that's not the only way they can be used. A function can have multiple `return` statements, and those `return` statements can be used to help control the flow of the program. + +{{% notice note %}} +Note: Because it's syntactically correct to have multiple return statements in a function, it's up to you to use them correctly. If you use a linter for your code files and you place additional code in a function **after** a return statement, the +linter will give you a helpful hint about the rest of the code being unreachable. +{{% /notice %}} + +#### Indentation + +One of the most important aspects of functions is indentation. Remember, Python doesn't use curly braces to figure out what's inside a function like other languages you've seen like JavaScript or Java. + +Python knows what code is related to a function by how it's indented. Anything that's indented one level deep under the function declaration is part of the function, no matter how many spaces there are between lines. + +To add a level of indentation, just press the **Tab** key on your keyboard after entering a new line. + +If you're using the REPL, once you're done entering your function, you'll need to press enter an additional time, to mark the end of the function. You know you're done defining your function when you see the 3 input arrows `>>>` again. + +Let's try it together. Type the following code in your REPL. Note that the 3 dots '...' indicate that those lines are *indented* in the REPL. If you type your code in a Python file, you won't see the `...` dots. + +```python +>>> def add_numbers(x, y): +... return x + y +... +``` + +{{%expand "See an error? Expand this section." %}} +Note: If you get an `IndentationError`, that means that you didn't correctly indent your code after your function definition. Try typing your function into the REPL one more time. + +```python +# The error you'll see if you didn't indent your function correctly. +>>> def add_numbers(x, y): +... return x + y +File "", line 2 + return x + y + ^ +IndentationError: expected an indented block +``` +{{% /expand%}} + +### Calling Functions + +#### With no arguments + +Once you've defined a function, you can call it from your Python code as many times as you'd like. + +To call a Python function, type in it's name, along with parenthesis, and any *required* arguments to the function. Let's try it now, with a function that doesn't require arguments. + +```python +>>> def hello_world(): +... print("Hello, World!") +... +>>> hello_world() +Hello, World! +``` + +#### With arguments + +Let's try it again, this time with a function that does accept arguments. + +{{% notice tip %}} +Here, note that the function accepts **names** for the arguments. But, when we call the function, we're passing in **values**. +{{% /notice %}} + +```python +>>> def add_numbers(x, y): +... return x + y +... +>>> add_numbers(3, 5) +8 +>>> +``` + +#### Storing the `return`ed value of a function. + +Storing the `return`ed value of a function is easy. All you need to do is assign it to a variable. + +Let's try it now. + +```python +>>> def add_numbers(x, y): +... return x + y +... +>>> new_number = add_numbers(3, 5) +>>> new_number +8 +``` + +The variable `new_number` now contains the result of running our `add_numbers` function with our arguments `3` and `5`. diff --git a/website/content/02-introduction-to-python/060-functions/30-function-arguments.md b/website/content/02-introduction-to-python/060-functions/30-function-arguments.md new file mode 100644 index 0000000..a422696 --- /dev/null +++ b/website/content/02-introduction-to-python/060-functions/30-function-arguments.md @@ -0,0 +1,192 @@ +--- +title: "Function Arguments" +date: 2019-02-10T18:16:14-08:00 +draft: false +weight: 30 +--- + +### Arguments in Practice + +### Positional arguments are required + +Positional arguments are all required, and must be given in the order they are declared. + +For example, this function doesn't do what we expected, because we passed in our arguments in the wrong order. + +In the REPL: + +```python +>>> def say_greeting(name, greeting): +... print(f"{greeting}, {name}.") +... +>>> say_greeting("Hello!", "Nina") +Nina, Hello!. +``` + +### Keyword arguments with default values + +Functions can accept two types of named arguments, ones without default values, and ones with default values. Arguments that have default values are called **keyword arguments**. The nice thing about defaults is they can be overridden when needed. + +Let's see this in practice, by writing two functions that print out a greeting. One function will have a default argument to make things easier for us. + +```python +# No default arguments +def say_greeting(greeting, name): + print(f"{greeting}, {name}.") + +# Default argument - greeting will always be +# Hello, if one isn't provided. +def say_greeting_with_default(name, greeting="Hello", punctuation="!"): + print(f"{greeting}, {name}{punctuation}") +``` + +#### Without default arguments + +Now, in the REPL, let's try calling our function with no default arguments: + +```python +>>> # No Default arguments +>>> def say_greeting(greeting, name): +... print(f"{greeting}, {name}.") +... +>>> say_greeting("Good Day", "Nina") +Good Day, Nina. +``` + +#### Using default arguments + +Let's make a new function, `say_greeting_with_default` that accepts two arguments -- `name`, and a now **optional** argument, `greeting`. If `greeting` is not passed in, it will default to `Hello`. + +In the REPL: + +```python +>>> # With Default Arguments +>>> def say_greeting_with_default(name, greeting="Hello", punctuation="!"): +... print(f"{greeting}, {name}{punctuation}") +... +>>> say_greeting_with_default("Nina") +Hello, Nina! +>>> say_greeting_with_default("Nina", "Good Day") +Good Day, Nina! +``` + +#### Order matters! + +A function can accept all of one type or the other, but arguments need to go in a specific order. + +All of the *required arguments go first*. They are then *followed by the optional keyword arguments*. + +What happens when we try to define our arguments out of order? If you guessed a `SyntaxError`, you're correct! + +```python +>>> def say_greeting_bad(greeting="Hello", name): +... print("Oops, this won't work!") +... + File "", line 1 +SyntaxError: non-default argument follows default argument +``` + +### Calling functions with arguments + +There are a few important things to know about calling functions with arguments. + +#### Arguments without defaults are **required**! + +Arguments without default values are **required** by Python. Otherwise your function wouldn't know what to do! If you don't pass in all the required arguments, you'll get a `TypeError`. + +In the REPL: +```python +>>> def say_greeting(name, greeting): +... print(f"{greeting}, {name}.") +... +>>> say_greeting("Nina") +Traceback (most recent call last): + File "", line 1, in +TypeError: say_greeting() missing 1 required positional argument: 'greeting' +``` + +#### You can pass in none, some, or all of the keyword arguments. + +If your function takes keyword arguments, you can provide zero, one, or all of them when you call it. You don't need to pass these arguments in order either. + +```python +>>> def create_query(language="JavaScipt", num_stars=50, sort="desc"): +... return f"language:{language} num_stars:{num_stars} sort:{sort}" +... +>>> create_query() +'language:JavaScipt num_stars:50 sort:desc' +>>> create_query(language="Ruby") +'language:Ruby num_stars:50 sort:desc' +>>> create_query(num_stars=1, language="Python", sort="asc") +'language:Python num_stars:1 sort:asc' +``` + +#### You can pass in required parameters by keyword. + +Even if your function arguments don't have keyword arguments with defaults, you can still pass values in to the function by name. This is especially helpful if you want to be extra clear about what you're passing in. + +```python +>>> def say_greeting(name, greeting): +... print(f"{greeting}, {name}.") +... +>>> say_greeting("Nina", "Hello") +Hello, Nina. +>>> say_greeting(name="Max", greeting="Bonjour") +Bonjour, Max. +``` + +### Arguments Danger Zone + +{{% notice warning %}} +Never use mutable types, like `list`s as a default argument. +{{% /notice %}} + +We'll talk more about `list`s and mutability in the coming chapter, but for the time being remember to never use an empty list as a default value to a function. + +Why? Because it won't work like you'd expect it to. + +```python +>>> # Don't do this! +>>> def add_five_to_list(my_list=[]): +... my_list.append(5) +... return my_list +... +>>> # This works like we expected it to. +>>> add_five_to_list() +[5] +>>> # Huh? +>>> add_five_to_list() +[5, 5] +>>> # We see that the original `my_list` is still being modified. +>>> add_five_to_list() +[5, 5, 5] +``` + +If you need to use a mutable type, like a `list` as a default, use a *marker* instead. We'll cover this technique when we talk about `list`s in the next chapter. + +In Python, default arguments are evaluated only once -- when the function is defined. Not each time the function is called. That means if you use a value that can be changed, it won't behave like you'd expect it to. + +### Naming Functions and Arguments + +Because Python is a dynamic language (sometimes called duck-typed) we use names as cues for what our function does, the arguments it accepts, and the values it returns. + +This is especially important because we generally don't declare *types* for our programs when we're first starting out. *Note: Python does support Type hinting, but it's more of an intermediate feature. Make sure you have the basics down before learning more about it.* + +{{% notice tip %}} +Try to avoid single character names for your functions and variables, unless they have meaning in math. +{{% /notice %}} + +For example, in this function, `x` and `y` are common names used when referring to points, so it's OK to use single-letter names in this scenario. + +```python +def add_points(x1, y1, x2, y2): + return x1 + x2, y1 + y2 +``` + +For sequences, like `list`s, it's appropriate to name them in the plural. + +For example, I'd expect a variable called `name` to be a single string, and a variable called `names` to be a list of strings. + +{{% notice tip %}} +A great resource to help you figure out the best naming conventions to use in your production Python code is a talk by Brandon Rhodes, called ["The Naming of Ducks: Where Dynamic Types Meet Smart Conventions"](https://www.youtube.com/watch?v=YklKUuDpX5c). +{{% /notice %}} diff --git a/website/content/02-introduction-to-python/060-functions/40-scope.md b/website/content/02-introduction-to-python/060-functions/40-scope.md new file mode 100644 index 0000000..f03f527 --- /dev/null +++ b/website/content/02-introduction-to-python/060-functions/40-scope.md @@ -0,0 +1,76 @@ +--- +title: "Function Scope" +date: 2019-02-10T18:30:11-08:00 +draft: false +weight: 40 +--- + +### Scope inside a function + +Inside of a function in Python, the **scope** changes. + +Think about it this way: scoping in Python happens with whitespace. When we delineate the code a function contains by indenting it under a function definition, it's scope **changes** to a new internal scope. It has access to the variables defined outside of it, but it can't change them. + +Once the function is done running, its scope goes away, as do its defined variables. + +Let's double check this in the REPL: + +```python +>>> def twitter_info(): +... twitter_account = "nnja" +... print(f"Account inside function: {twitter_account}") +... +>>> twitter_info() +Account inside function: nnja +>>> print(f"Account outside of function: {twitter_account}") +Traceback (most recent call last): + File "", line 1, in +NameError: name 'twitter_account' is not defined +``` + +We get a `NameError` when trying to access the `twitter_account` variable outside of the function. That's because it's out of scope, exactly like we expected it to be. + + +### Using variables defined outside of the function + +Generally, we want to be careful when using variables defined outside of our function. + +Note, that if we try to change the value of a variable defined outside of our function, we'll see the changes in the function, but not outside of it. + +You can't change variables defined outside of the function inside of the function. If you try to, your changes will only apply while the function is running. Once the function is done running, the value goes back to what it was before your function ran. + +A little confusing, but let's see it in action: + +```python +>>> name = "Nina" +>>> print(f"Name outside of function: {name}") +Name outside of function: Nina +>>> +>>> def try_change_name(): +... name = "Max" +... print(f"Name inside of function: {name}") +... +>>> try_change_name() +Name inside of function: Max +>>> print(f"Name outside of function: {name}") +Name outside of function: Nina +``` + +If we didn't know what to look for, the program might not behave how we'd expect it to. A good rule of thumb is to name our variables clearly, and minimize how many variables we declare outside of functions and classes, which you'll learn about in day two. + + +{{% notice tip %}} +An appropriate use is when using a constant, a variable defined in all caps, with the words separated by underscores. A constant is a value that we expect to use several times within our program, but we never expect to change it programmatically. +{{% /notice %}} + +For example: + +```python +>>> ROOT_API_URL = "https://api.github.com" +>>> def api_search_repos_url(): +... return f"{ROOT_API_URL}/search/repositories" +... +>>> api_search_repos_url() +'https://api.github.com/search/repositories' +>>> +``` \ No newline at end of file diff --git a/website/content/02-introduction-to-python/060-functions/50-exercise.md b/website/content/02-introduction-to-python/060-functions/50-exercise.md new file mode 100644 index 0000000..4c5aeb0 --- /dev/null +++ b/website/content/02-introduction-to-python/060-functions/50-exercise.md @@ -0,0 +1,129 @@ +--- +title: "Practice" +date: 2019-03-02T00:00:00-08:00 +draft: false +weight: 50 +pre: "⭐️ " +--- + +## Functions + +Let's try creating a basic function. Use tab to indent the second line, and press enter on an empty line to finish the function. + +```python +>>> def add_numbers(x, y): +... return x + y +... # Press Enter +``` + +Now let's try our new function. Type this into your REPL: + +```python +>>> add_numbers(1, 2) +# Let's use the string formatting we learned in the last chapter +>>> print(f"The sum of 1 and 2 is {add_numbers(1, 2)}") +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> add_numbers(1, 2) +3 +# Let's use the string formatting we learned in the last chapter +>>> print(f"The sum of 1 and 2 is {add_numbers(1, 2)}") +The sum of 1 and 2 is 3 +``` + +{{% /expand%}} + + +## The Importance of Whitespace + +Here's an error that you'll become very familiar with during your career as a Pythonista, the `IndentationError`. Whitespace is important for defining function scope in python, so missing or extra indentations or spaces will cause the runtime to throw this error. Let's redefine our `add_numbers` function, but we'll forget to indent the second line, `return x + y`. Notice that the second line is directly under (at the same indentation level) as the `def`: + +```python +>>> def add_numbers(x, y): +... return x + y + File "", line 2 + return x + y + ^ +IndentationError: expected an indented block +``` + +Notice how the runtime tells us the line that failed (`line 2`), gives you a copy of the line with an arrow pointing to the offending error (`return x + y`), and then tells you the error (`IndentationError`) with additional information (`expected an indented block`). + +## Function Scope + +As we saw earlier, scoping in Python happens with whitespace. Let's see this in action: + +```python +>>> x = 1 +>>> y = 2 +>>> def add_numbers(x, y): +... print(f"Inside the function, x = {x} and y = {y}") +... return x + y +... +>>> print(f"Outside the function, x = {x} and y = {y}") +>>> print(f"The sum of 5 and 6 is {add_numbers(5, 6)}") +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> x = 1 +>>> y = 2 +>>> def add_numbers(x, y): +... print(f"Inside the function, x = {x} and y = {y}") +... return x + y +... +>>> print(f"Outside the function, x = {x} and y = {y}") +Outside the function, x = 1 and y = 2 +>>> +>>> print(f"The sum of 5 and 6 is {add_numbers(5, 6)}") +Inside the function, x = 5 and y = 6 +The sum of 5 and 6 is 11 +``` + +{{% /expand%}} + + +## Positional Arguments vs Keyword Arguments + +The `x` and `y` arguments for our `add_numbers()` function are called positional arguments. Python also lets us declare *keyword* arguments. Keyword arguments are great for setting default values, because passing them is optional. Just remember that keyword arguments must come *after* any positional arguments. Let's make a more generic function for doing math: + +```python +>>> def calculate_numbers(x, y, operation="add"): +... if operation == "add": +... return x + y +... elif operation == "subtract": +... return x - y +... +# Let's try our new function. Remember, if we don't pass the operation keyword argument, the default is "add" +>>> calculate_numbers(2, 3) +# You can pass a keyword argument as a normal positional argument +>>> calculate_numbers(2, 3, "subtract") +# You can also use the argument's keyword. This helps with readability +>>> calculate_numbers(2, 3, operation="subtract") +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> def calculate_numbers(x, y, operation="add"): +... if operation == "add": +... return x + y +... elif operation == "subtract": +... return x - y +... +# Let's try our new function. Remember, if we don't pass the operation keyword argument, the default is "add" +>>> calculate_numbers(2, 3) +5 +# You can pass a keyword argument as a normal positional argument +>>> calculate_numbers(2, 3, "subtract") +-1 +# You can also use the argument's keyword. This helps with readability +>>> calculate_numbers(2, 3, operation="subtract") +-1 +``` + +{{% /expand%}} diff --git a/website/content/02-introduction-to-python/060-functions/_index.md b/website/content/02-introduction-to-python/060-functions/_index.md new file mode 100644 index 0000000..513ec15 --- /dev/null +++ b/website/content/02-introduction-to-python/060-functions/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Functions" +date = 2019-01-25T15:03:48-06:00 +weight = 60 +chapter = true +draft = false +pre = "3. " ++++ + +### Chapter 3 + +# Functions + +Functions help us organize our code in a way that's reusable, and accept arguments and defaults where needed. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/080-advanced-datatypes/10-lists.md b/website/content/02-introduction-to-python/080-advanced-datatypes/10-lists.md new file mode 100644 index 0000000..3416806 --- /dev/null +++ b/website/content/02-introduction-to-python/080-advanced-datatypes/10-lists.md @@ -0,0 +1,205 @@ +--- +title: "Lists, Part 1" +date: 2019-02-10T18:20:32-08:00 +draft: false +weight: 10 +--- + +Lists are one of the most powerful data types in Python. Generally, they're container objects used to store related items together. + +### `list` cheat sheet + +| type | `list` | +|------------------ |--------------------------------------------------------------------------------------- | +| use | Used for storing similar items, and in cases where items need to be added or removed. | +| creation | `[]` or `list()` for empty list, or `[1, 2, 3]` for a list with items. | +| search methods | `my_list.index(item)` or `item in my_list` | +| search speed | Searching in an item in a large list is slow. Each item must be checked. | +| common methods | `len(my_list)`, `append(item)` to add, `insert(index, item)` to insert in the middle, `pop()` to remove. | +| order preserved? | Yes. Items can be accessed by index. | +| mutable? | Yes | +| in-place sortable? | Yes. `my_list.sort()` will sort the list in-place. `my_list.sort(reverse=True)` will sort the list in-place in *descending* order. `my_list.reverse()` will *reverse the items* in `my_list` in-place. | + +### In Practice + +Let's create a few lists to see how they work. + +An empty list can be created in two ways. The first, by calling the `list()` method. More commonly, it's created with two empty brackets `[]`. Don't forget to check the type of the list with the `type` built-in function. + +```python +>>> list() +[] +>>> [] +[] +>>> type(list()) + +>>> type([]) + +``` + +Let's create our list with a few items in it. Let's say we want to keep track of a list of names. We add items to our list, and separate them with commas `,`. + +```python +>>> names = ["Nina", "Max", "Jane"] +``` + +We can check its length with the built-in `len()` method, like so: + +```python +>>> len(names) +3 +``` + +### Indexes and Indices + +Lists retain the order of the items in them. In the next section, you'll learn about some data structures that don't. + +In order to *access* items in a list, we'll need to use an *index*. (Multiple indexes are sometimes also called indices). The index for the item you want to access is *an integer* put in *square brackets* after the list. + +{{% notice tip %}} +**Indexes start at 0** in Python and most other programming languages. +{{% /notice %}} + +```python +>>> names = ["Nina", "Max", "Jane"] +>>> names[0] +'Nina' +>>> names[1] +'Max' +>>> names[2] +'Jane' +``` + +#### Updating an item in a list + +To update a particular item in a `list` use square-bracket notion and assign a new value. `my_list[pos] = new_value` + +```python +>>> names = ["Nina", "Max", "Jane"] +>>> names[2] = "Floyd" +>>> names +['Nina', 'Max', 'Floyd'] +``` + +{{% notice info %}} +If you try to access an index that is greater than or equal to (>=) the length of the list, you'll get an `IndexError`. +{{% /notice %}} + +```python +>>> names = ["Nina", "Max", "Jane"] +>>> len(names) +3 +>>> names[3] +Traceback (most recent call last): + File "", line 1, in +IndexError: list index out of range +``` + +#### Formatting + +{{% notice tip %}} +We can *optionally* add new lines after the commas. This helps with readability for more complex list items. +{{% /notice %}} + +Notice that we can also *optionally* add a trailing comma after the last item. A trailing comma isn't required to create a valid list, but it does help minimize version control differences when working on a Python codebase with a team. + +```python +>>> names = [ +... "Nina", +... "Max", +... "Jane", +... ] +``` + + +### Common Gotchas + +{{% notice info %}} +If you forget to include commas between your items, you'll get a `SyntaxError`. +{{% /notice %}} + +```python +>>> numbers = [1, 2 3] + File "", line 1 + numbers = [1, 2 3] + ^ +SyntaxError: invalid syntax +``` + +The REPL makes it difficult to forget the closing bracket, but if you forget it while writing code in a Python file, you'll see a `SyntaxError` with a different name. It'll say: `SyntaxError: unexpected EOF while parsing` or `SyntaxError: invalid syntax`. + +For example: + +```python +# Python file: program.py +names = ["Nina", +x = 5 +``` + +Notice how the `SyntaxError` points to a completely valid line of Python code. In these cases, you also need to check the line of code **before** the line with the `SyntaxError`. There, we'll notice that we forgot the closing bracket of our `names` list. + +```bash +# In a shell +(env) $ python program.py + File "/Users/nina/Desktop/program.py", line 2 + x = 5 + ^ +SyntaxError: invalid syntax +``` + +### Sorting + +Sorting sounds complicated, but in practice, it's just one method call away! + +#### Sorting a Copy Of Your List + +If you'd like sort to return a brand new copy of your list, instead of modifying your original copy, you can use the built-in `sorted(my_list)` function on your list to return a *new* `list`, sorted in increasing (ascending) order. Or use `sorted(my_list, reverse=True)` to create a new `list` sorted backwards, in decreasing (or descending) order. This operation will **not modify** the underlying list. + +Either of these operations will return a *new* list. + +```python +>>> lottery_numbers = [1, 4, 32423, 2, 45, 11] +>>> sorted(lottery_numbers) +[1, 2, 4, 11, 45, 32423] +>>> lottery_numbers +[1, 4, 32423, 2, 45, 11] +>>> sorted(lottery_numbers, reverse=True) +[32423, 45, 11, 4, 2, 1] +>>> lottery_numbers +[1, 4, 32423, 2, 45, 11] +``` + +#### Sorting the list in-place + +You can call `my_list.sort()` on your list to sort it in increasing (ascending) order, or `my_list.sort(reverse=True)` on the list to sort it backwards, in decreasing (or descending) order. This operation will modify the underlying list, and *doesn't return a value*. + +```python +>>> lottery_numbers = [1, 4, 32423, 2, 45, 11] +>>> lottery_numbers.sort() +>>> lottery_numbers +[1, 2, 4, 11, 45, 32423] + +>>> lottery_numbers.sort(reverse=True) +>>> lottery_numbers +[32423, 45, 11, 4, 2, 1] + +>>> words = ["Umbrella", "Fox", "Apple"] +>>> words.sort() +>>> words +['Apple', 'Fox', 'Umbrella'] +``` + +#### Reverse the list in-place + +To reverse the items of a list in-place, call `my_list.reverse()` on it. + +```python +>>> lottery_numbers = [1, 4, 32423, 2, 45, 11] +>>> lottery_numbers.reverse() +>>> lottery_numbers +[11, 45, 2, 32423, 4, 1] +``` + +### Finding Methods + +Remember, if you ever forget which methods are available on `list`, just call `dir` on it. Ignore the methods that start with underscores. If you need help remembering what a method does, you can call `help()` on it. For example, for append, call `help(list.append)`. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/080-advanced-datatypes/20-adding-removing-finding.md b/website/content/02-introduction-to-python/080-advanced-datatypes/20-adding-removing-finding.md new file mode 100644 index 0000000..f922e28 --- /dev/null +++ b/website/content/02-introduction-to-python/080-advanced-datatypes/20-adding-removing-finding.md @@ -0,0 +1,240 @@ ++++ +title = "Lists, Part 2" +date = 2019-03-03T17:22:06-08:00 +weight = 20 +draft = false ++++ + +### Adding, Removing, Changing, and Finding Items in `list`s cheat sheet + +| action | method | returns | possible errors | +|-------------------------------------------------- |--------------------------------------- |------------------- |-------------------------------------------- | +| check length | `len(my_list)` | `int` | | +| **add:** to the end | `my_list.append(item)` | - | | +| **insert:** at position | `my_list.insert(pos, item)` | - | | +| **update:** at position | `my_list[pos] = item` | - - | `IndexError` if `pos` is >= `len(my_list)` | +| **extend:** add items from another list | `my_list.extend(other_list)` | - | | +| is item in list? | `item in my_list` | `True` or `False` | | +| **index** of item | `my_list.index(item)` | `int` | `ValueError` if `item` is not in `my_list` | +| **count** of item | `my_list.count(item)` | `int` | | +| **remove** an item | `my_list.remove(item)` | - | `ValueError` if `item` not in `my_list` | +| **remove** the last item, or an item at an index | `my_list.pop()` or `my_list.pop(pos)` | `item` | `IndexError` if `pos` >= `len(my_list)` | + + +#### Checking Length + +Before we add or remove items, it's usually a good idea to check a list's length. We do that with the `len` built in function. We can even use the `len` built in function to check the lengths of other types, like strings. + +Let's see it in action on a names `list` with two items, and a name `str`ing with four characters. + +```python +>>> len(names) +2 +>>> name = "Nina" +>>> len(name) +4 +``` + +### Adding Items + +Let's start with a list of two names. + +```python +>>> names = ["Nina", "Max"] +``` + +##### `my_list.append(item)` adds to the end of `my_list` + +We can use `my_list.append(item)` to add an additional item to the end of the list. + +```python +>>> names.append("John") +>>> names +['Nina', 'Max', 'John'] +``` + +##### `my_list.insert(pos, item)` inserts an item into `my_list` at the given position + +Use `my_list.insert(pos, item)` to insert items in an arbitrary position in the list. If the position is 0, we'll insert at the beginning of the list. + +```python +>>> names.insert(0, "Rose") +>>> names +['Rose', 'Nina', 'Max', 'John'] +``` + +You can call `dir()` on our names list to verify that it's actually of type `list`. If you forget which order insert is called in, don't forget you can always use the `help()` function on the REPL. **Remember: Press `q` to quit the help screen.** Let's try it now: + +```python +>>> type(names) + +>>> help(names.insert) + +Help on method_descriptor: + +insert(self, index, object, /) + Insert object before index. +``` + +You can also call help on `names.insert`. Because `names` is already of type `list`, it achieves the same result. + +##### `my_list.extend(other_list)` adds all the contents of `other_list` to `my_list` + +```python +>>> names = ["Nina", "Max"] +>>> colors = ["Red", "Blue"] +>>> names +['Nina', 'Max'] +>>> names.extend(colors) +>>> names +['Nina', 'Max', 'Red', 'Blue'] +``` + +### Looking for Items + +Looking for items in a list is *slow*. Each item needs to be checked in order to find a match. + +This doesn't matter much when you're just getting started, unless your data set is large, or if you're building high-performance systems. If you want to quickly search for an item, you'll need to use a `set` or a `dict`ionary instead. + +There are a few ways to determine if an item is in the list, and at which position. Let's try this on our list of names. + +```python +names = ["Nina", "Max", "Phillip", "Nina"] +``` + +##### Use the `in` keyword to determine if an item is present or not. + +```python +>>> "Nina" in names +True +>>> "Rose" in names +False +``` + +##### Use the `my_list.index(item)` method to find the **first** index of a potential match. + +Notice that only the *first* index of the string `"Nina"` is returned. We'll learn more about what an index is in the next chapter. + +{{% notice info %}} +If the item we're looking for *is not* in the list, Python will throw a `ValueError`. +{{% /notice %}} + +You'll learn how to deal with exceptions later. For now, you can use the `in` operator to check if an item is present in the list before finding its index. + +```python +>>> names.index("Nina") +0 +>>> names.index("Rose") +Traceback (most recent call last): + File "", line 1, in +ValueError: 'Rose' is not in list +``` + +##### Use the `my_list.count(item)` method to find out how many times an item appears in a list. + +```python +>>> names.count("Nina") +2 +>>> names.count("Rose") +0 +``` + +### Updating Items + +To update items in a list, use the *position* of the item you'd like to change using square bracket `[]` syntax. Like: `my_list[pos] = new_item` + +For example: + +```python +>>> names = ["Nina", "Max"] +>>> names[0] = "Rose" +>>> names +['Rose', 'Max'] +``` + +Or, when used with `my_list.index(item)`: + +```python +>>> names = ["Nina", "Max"] +>>> pos = names.index("Max") +>>> names[pos] = "Rose" +>>> names +['Nina', 'Rose'] +``` + +{{% notice info %}} +You'll see a `IndexError: list assignment index out of range` if you try to update an item in a position that doesn't exist, that is *if the position is greater than or equal to `>=` the length of the list*. +{{% /notice %}} + +```python +>>> names = ["Nina", "Max"] +>>> len(names) +2 +>>> names[2] = "Rose" +Traceback (most recent call last): + File "", line 1, in +IndexError: list assignment index out of range +``` + +### Removing Items + +There are a few ways to remove items from a list. + +##### Use `my_list.remove(item)` to remove the *first* instance of the item + +Be careful. `remove()` only removes the first instance of the item from the list, which isn't always what we want to do. + +```python +>>> names = ["Nina", "Max", "Rose"] +>>> names.remove("Nina") +>>> names +['Max', 'Rose'] +>>> +>>> +>>> names = ["Nina", "Max", "Nina"] +>>> names.remove("Nina") +>>> names +['Max', 'Nina'] +``` + +{{% notice info %}} +If we try to remove an item that's not in the list, we'll get a `ValueError: list.remove(x): x not in list`. +{{% /notice %}} + +```python +>>> names = ["Nina"] +>>> names.remove("Max") +Traceback (most recent call last): + File "", line 1, in +ValueError: list.remove(x): x not in list +``` + +##### Use `my_list.pop()` to remove the last item, or `my_list.pop(index)` to remove the item at that index + +Using `pop()` will also **return** the item that was in that position. That's useful if we want to save the item. + +```python +>>> names = ["Nina", "Max", "Rose"] +>>> names.pop() +'Rose' +>>> names +['Nina', 'Max'] +>>> names.pop(1) +'Max' +>>> names +['Nina'] +``` + +{{% notice info %}} +If we try to pop an item from an index that is longer than or equal to the length of the list, we'll get an `IndexError: pop index out of range`. +{{% /notice %}} + +```python +>>> names = ["Nina"] +>>> len(names) +1 +>>> names.pop(1) +Traceback (most recent call last): + File "", line 1, in +IndexError: pop index out of range +``` diff --git a/website/content/02-introduction-to-python/080-advanced-datatypes/30-tuples.md b/website/content/02-introduction-to-python/080-advanced-datatypes/30-tuples.md new file mode 100644 index 0000000..405dd71 --- /dev/null +++ b/website/content/02-introduction-to-python/080-advanced-datatypes/30-tuples.md @@ -0,0 +1,124 @@ +--- +title: "Tuples" +date: 2019-02-10T18:20:45-08:00 +draft: false +weight: 30 +--- + +Tuples are light-weight collections used to keep track of related, but different items. Tuples are **immutable**, meaning that once a tuple has been created, the items in it can't change. + +You might ask, why tuples when Python already has lists? Tuples are different in a few ways. While lists are generally used to store collections of similar items together, tuples, by contrast, can be used to contain a snapshot of data. They can't be continually changed, added or removed from like you could with a list. + +### `tuple` cheat sheet + +| type | `tuple` | +|-------------------- |--------------------------------------------------------------------------------------------------------- | +| use | Used for storing a snapshot of related items when we don't plan on modifying, adding, or removing data. | +| creation | `()` or `tuple()` for empty tuple. `(1, )` for one item, or `(1, 2, 3)` for a tuple with items. | +| search methods | `my_tuple.index(item)` or `item in my_tuple` | +| search speed | Searching for an item in a large tuple is slow. Each item must be checked. | +| common methods | Can't add or remove from tuples. | +| order preserved? | Yes. Items can be accessed by index. | +| mutable? | **No** | +| in-place sortable? | **No** | + +### Uses + +A good use of a `tuple` might be for storing the information for a *row* in a spreadsheet. That data is information only. We don't necessarily care about updating or manipulating that data. We just want a read-only snapshot. + + +Tuples are an interesting and powerful datatype, and one of the more unique aspects of Python. Most other programming languages have ways of representing lists and dictionaries, but only a small subset contain tuples. Use them to your advantage. + +### Examples + +#### Empty and one-item `tuple`s + +One important thing to note about tuples, is there's a quirk to their creation. Let's check the type of an empty `tuple` created with `()`. +```python +>>> a = () +>>> type(a) + +``` + +That looks like we'd expect it to. What about if we *tried* to create a one-item `tuple` using the same syntax? + +```python +>>> b = (1) +>>> type(b) + +``` + +It didn't work! `type((1))` is an `int`eger. In order to create a one-item tuple, you'll need to include a trailing comma. + +```python +>>> c = (1, ) +>>> type(c) + +``` + +{{% notice tip %}} +If you're creating a one-item tuple, you **must** include a trailing comma, like this: `(1, )` +{{% /notice %}} + +#### Creation + +Let's say we have a spreadsheet of students, and we'd like to represent each row as a tuple. + +```python +>>> student = ("Marcy", 8, "History", 3.5) +``` + +#### Access by index + +We can access items in the `tuple` by index, but we **can't change them**. + +```python +>>> student = ("Marcy", 8, "History", 3.5) +>>> student[0] +'Marcy' +>>> student[0] = "Bob" +Traceback (most recent call last): + File "", line 1, in +TypeError: 'tuple' object does not support item assignment +``` + +{{% notice info %}} +We'll see `TypeError: 'tuple' object does not support item assignment` if we try to change the items in a tuple. +{{% /notice %}} + +`tuple`s also don't have an `append` or `extend` method available on them like lists do, because they can't be changed. + +### `tuple` unpacking. + +Sounds like a lot of work for not a lot of benefit, right? Not so. `tuple`s are great when you depend on your data staying unchanged. Because of this guarantee, we can use `tuples` in other types of containers like `set`s and `dict`ionaries. + +It's also a great way to quickly consolidate information. + +You can also use `tuples` for something called unpacking. Let's see it in action: + +```python +>>> student = ("Marcy", 8, "History", 3.5) +>>> +>>> name, age, subject, grade = student +>>> name +'Marcy' +>>> age +8 +>>> subject +'History' +>>> grade +3.5 +``` + +You can return tuples from functions, and use unpacking. + +```python +>>> def http_status_code(): +... return 200, "OK" +... +>>> code, value = http_status_code() +>>> code +200 +>>> value +'OK' +``` diff --git a/website/content/02-introduction-to-python/080-advanced-datatypes/50-sets.md b/website/content/02-introduction-to-python/080-advanced-datatypes/50-sets.md new file mode 100644 index 0000000..2cad7ab --- /dev/null +++ b/website/content/02-introduction-to-python/080-advanced-datatypes/50-sets.md @@ -0,0 +1,236 @@ +--- +title: "Sets" +date: 2019-02-10T18:20:42-08:00 +draft: false +weight: 50 +--- + +Sets are a datatype that allows you to store other **immutable** types in an unsorted way. An item can only be contained in a set once. There are no duplicates allowed. The benefits of a set are: very fast membership testing along with being able to use powerful set operations, like `union`, `difference`, and `intersection`. + +### `set` cheat sheet + +| type | `set` | +|-------------------- |------------------------------------------------------------------------------------------------------------------------------- | +| use | Used for storing immutable data types uniquely. Easy to compare the items in `set`s. | +| creation | `set()` for an empty set (`{}` makes an empty `dict`) and `{1, 2, 3}` for a set with items in it | +| search methods | `item in my_set` | +| search speed | Searching for an item in a large set is very fast. | +| common methods | `my_set.add(item)`, `my_set.discard(item)` to remove the item if it's present, `my_set.update(other_set)` | +| order preserved? | **No**. Items *can't* be accessed by index. | +| mutable? | **Yes**. Can add to or remove from `set`s. | +| in-place sortable? | **No**, because items aren't ordered. | +### Examples + +#### Empty `set`s + +Let's create our first few sets. + +The first thing we might try to do is create an empty set with `{}`, but we'll come across a hurdle. + +```python +>>> my_new_set = {} +>>> type(my_new_set) + +>>> my_set = set() +>>> type(my_set) + +``` + +{{% notice info %}} +You can't create an empty `set` with `{}`. That creates a `dict`. Create an empty set with `set()` instead. +{{% /notice %}} + +{{% notice tip %}} +While you're learning Python, it's useful to use `type()`, `dir()` and `help()` as often as possible. +{{% /notice %}} + +#### `set`s with items + +Now, let's make a new set with some items in it, and test out important set concepts. + +#### `set`s can't contain duplicate values + +```python +>>> names = {"Nina", "Max", "Nina"} +>>> names +{'Max', 'Nina'} +>>> len(names) +2 +``` + +#### `set`s can't contain mutable types + +The way that `set`s allow you to quickly check if an item is contained in them or not is with an algorithm called a hash. I won't cover the details, but an algorithm is a way of representing an immutable data type with a unique numerical representation. In Python, there's a built-in `hash()` function. + +The `hash()` function only works on immutable data types. That means, data types where the contents can't be changed after creation. + +```python +>>> hash("Nina") +3509074130763756174 +>>> hash([]) +Traceback (most recent call last): + File "", line 1, in +TypeError: unhashable type: 'list' +``` + +{{% notice info %}} +You'll see a `TypeError: unhashable type: 'list'` if you try to add a mutable data type (like a `list`) to a set. +{{% /notice %}} + +If you try to add a mutable data type (like a `list`) to a set, you'll see the same `TypeError`, complaining about an `unhashable type`. + +```python +>>> {"Nina"} +{'Nina'} +>>> {[]} +Traceback (most recent call last): + File "", line 1, in +TypeError: unhashable type: 'list' +``` + +#### `set`s can be used to de-duplicate the items in a list + +Tip: *If you don't care about order*, you can quickly de-duplicate the items in a `list` by passing the `list` into the `set` constructor. + +```python +>>> colors = ["Red", "Yellow", "Red", "Green", "Green", "Green"] +>>> set(colors) +{'Red', 'Green', 'Yellow'} +``` + +#### `set`s don't have an order + +Sets don't have an order. That means that when you print them, the items won't be displayed in the order they were entered in the list. + +```python +>>> my_set = {1, "a", 2, "b", "cat"} +>>> my_set +{1, 2, 'cat', 'a', 'b'} +``` + +It also means that you *can't* access items in the `set` by position in subscript `[]` notation. + +```python +>>> my_set = {"Red", "Green", "Blue"} +>>> my_set[0] +Traceback (most recent call last): + File "", line 1, in +TypeError: 'set' object does not support indexing +``` + +{{% notice info %}} +You'll see `TypeError: 'set' object does not support indexing` if you try to access the items in a `set` by index with `my_set[pos]` +{{% /notice %}} + +Tip: If your set contains items of the same type, and you want to sort the items, you'll need to convert the `set` to a `list` first. Or, you can use the built-in `sorted(sequence)` method, which will do the conversion for you. + +```python +>>> my_set = {"a", "b", "cat", "dog", "red"} +>>> my_set +{'b', 'red', 'a', 'cat', 'dog'} +>>> sorted(my_set) +['a', 'b', 'cat', 'dog', 'red'] +``` + +#### adding to and removing from `set`s + +Since a set has no order, we can't add or remove items to it by index. We need to call the operations with the item itself. + +##### Add items to a set with `my_set.add(item)`. + +```python +>>> colors = {"Red", "Green", "Blue"} +>>> colors.add("Orange") +>>> colors +{'Orange', 'Green', 'Blue', 'Red'} +``` + +##### Remove items with `my_set.discard(item)` + +You can remove an item from a `set` if it's present with `my_set.discard(item)`. If the set doesn't contain the item, no error occurs. + +```python +>>> colors = {"Red", "Green", "Blue"} +>>> colors.discard("Green") +>>> colors +{'Blue', 'Red'} +>>> colors.discard("Green") +>>> colors +{'Blue', 'Red'} +``` + +You can also remove items from a `set` with `my_set.remove(item)`, which will raise a `KeyError` if the item doesn't exist. + + +##### Update a set with another sequence using `my_set.update(sequence)` + +You can update a `set` by passing in another sequence, meaning another `set`, `list`, or `tuple`. + +```python +>>> colors = {"Red", "Green"} +>>> numbers = {1, 3, 5} +>>> colors.update(numbers) +>>> colors +{1, 3, 'Red', 5, 'Green'} +``` + +{{% notice info %}} +Be careful passing in a `str`ing to `my_set.update(sequence)`. That's because a `str`ing is *also* a sequence. It's a sequence of characters. +{{% /notice %}} + +```python +>>> numbers = {1, 3, 5} +>>> numbers.update("hello") +>>> numbers +{1, 3, 'h', 5, 'o', 'e', 'l'} +``` + +Your set will update with each character of the `str`ing, which was probably not your intended result. + +### `set` operations + +`sets` allow quick and easy operations to compare items between two sets. + +#### `set` operations cheat sheet + + method operation | symbol operation | result | +|--------------------- |------------------ |------------------------------------------------------------------------------- | +| `s.union(t)` | s | t | creates a new set with all the items **from both `s` and `t`** | +| `s.intersection(t)` | `s & t` | creates a new set containing *only* items that are **both in `s` and in `t`** | +| `s.difference(t)` | `s ^ t` | creates a new set containing items that are **not in both `s` and in `t`** | + +#### examples + +Let's see it in action. + +We have two sets, `rainbow_colors`, which contain the colors of the rainbow, and `favorite_colors`, which contain my favorite colors. + +```python +>>> rainbow_colors = {"Red", "Orange", "Yellow", "Green", "Blue", "Violet"} +>>> favorite_colors = {"Blue", "Pink", "Black"} +``` + +First, let's combine the sets and create a new `set` that contains all of the items from `rainbow_colors` and `favorite_colors` using the union operation. You can use the `my_set.union(other_set)` method, or you can just use the symbol for union `|=` from the table above. + +```python +>>> rainbow_colors | favorite_colors +{'Orange', 'Red', 'Yellow', 'Green', 'Violet', 'Blue', 'Black', 'Pink'} +``` + +Next, let's find the intersection. We'll create a new `set` with *only* the items in both `set`s. + +```python +>>> rainbow_colors & favorite_colors +{'Blue'} +``` + +Lastly, We can also find the difference. Create a new set with the items that are in in one, but not the other. We'll see that `"Blue"` is missing from the list. + +```python +>>> rainbow_colors ^ favorite_colors +{'Orange', 'Red', 'Yellow', 'Green', 'Violet', 'Black', 'Pink'} +``` + +There are other useful operations available on `set`s, such as checking if one set is a subset, a superset, and more, but I don't have time to cover them all. Python also has a `frozenset` type, if you need the functionality of a `set` in an immutable package (meaning that the contents can't be changed after creation). + +Find out more by reading the [documentation](https://docs.python.org/3/library/stdtypes.html#set), or calling `help()` on `set`. diff --git a/website/content/02-introduction-to-python/080-advanced-datatypes/60-dictionaries.md b/website/content/02-introduction-to-python/080-advanced-datatypes/60-dictionaries.md new file mode 100644 index 0000000..d216da3 --- /dev/null +++ b/website/content/02-introduction-to-python/080-advanced-datatypes/60-dictionaries.md @@ -0,0 +1,202 @@ +--- +title: "Dictionaries" +date: 2019-02-10T18:20:37-08:00 +draft: false +weight: 60 +--- + +Dictionaries are a useful type that allow us to store our data in key, value pairs. Dictionaries themselves are **mutable**, *but*, dictionary keys can only be **immutable** types. + +We use dictionaries when we want to be able to quickly access additional data associated with a particular key. A great practical application for dictionaries is memoization. Let's say you want to save computing power, and store the result for a function called with particular arguments. The arguments could be the key, with the result stored as the value. Next time someone calls your function, you can check your dictionary to see if the answer is pre-computed. + +Looking for a key in a large dictionary is extremely fast. Unlike lists, we don't have to check every item for a match. + +### `dict`ionary cheat sheet + +| type | `dict` | +|-------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------- | +| use | Use for storing data in key, value pairs. Keys used must be **immutable** data types. | +| creation | `{}` or `dict()` for an empty `dict`. `{1: "one", 2: "two"}` for a `dict` with items. | +| search methods | `key in my_dict` | +| search speed | Searching for a key in a large dictionary is fast. | +| common methods | `my_dict[key]` to get the value by `key`, and throw a `KeyError` if `key` is not in the dictionary. Use `my_dict.get(key)` to fail silently if `key` is not in `my_dict`. `my_dict.items()` for all key, value pairs, `my_dict.keys()` for all keys, and `my_dict.values()` for all values. | +| order preserved? | **Sort of**. As of Python 3.6 a `dict` is sorted by insertion order. Items *can't* be accessed by index, only by key. | +| mutable? | **Yes**. Can add or remove keys from `dict`s. | +| in-place sortable? | **No**. `dict`s don't have an index, only keys. | + +### Examples + +#### Empty `dict`s + +We already learned one of the methods of creating an empty `dict` when we tried (and failed) to create an empty set with `{}`. The other way is to use the `dict()` method. + +```python +>>> my_dict = {} +>>> type(my_dict) + + +>>> my_dict = dict() +>>> type(my_dict) + +``` + +#### Creating `dict`s with items + +If we want to create `dict`s with items in them, we need to pass in key, value pairs. A `dict` is declared with curly braces `{}`, followed by a key and a value, separated with a colon `:`. Multiple key and value pairs are separated with commas `,`. + + +We can call familiar methods on our dictionary, like finding out how many key / value pairs it contains with the built-in `len(my_dict)` method. + +```python +>>> nums = {1: "one", 2: "two", 3: "three"} + +>>> len(nums) +3 +``` + +#### Side note: What can be used as keys? + +Any type of object, mutable or immutable, can be used as a value but just like `set`s, `dict`ionaries can only use immutable types as keys. That means you can use `int`, `str`, or even `tuple` as a key, but **not** a `set`, `list`, or other `dict`ionary. + +The follow is OK: + +```python +>>> my_dict = {1: 1} +>>> my_dict = {1: []} +``` + +{{% notice info %}} +You'll see a `TypeError: unhashable type: 'list'` if you try to use a mutable type, like a `list` as a `dict`ionary key. +{{% /notice %}} + +```python +>>> my_dict = {[]: 1} +Traceback (most recent call last): + File "", line 1, in +TypeError: unhashable type: 'list' +``` + +#### Accessing + +Our `dict` contains `key`, `value` pairs. Because a `dict`ionary isn't ordered, we *can't access the items in it by position*. Instead, to access the items in it, we use square-bracket `my_dict[key]` notation, similar to how we access items in a list with square bracket notation containing the position. + +```python +>>> nums = {1: "one", 2: "two", 3: "three"} +>>> nums[1] +'one' +>>> nums[2] +'two' +``` + +Q: What happens when we try to access a key in a `dict`ionary with square bracket notation, but the key isn't present? + +{{% notice info %}} +We'll get a `KeyError: key` if we try to access `my_dict[key]` with square bracket notation, but `key` isn't in the dictionary. +{{% /notice %}} + +```python +>>> nums = {1: "one", 2: "two", 3: "three"} +>>> nums[4] + +Traceback (most recent call last): + File "", line 1, in +KeyError: 4 +``` + +One way to get around this is to use the `my_dict.get(key)` method. Using this method, if the key isn't present, no error is thrown, and no value (aka the `None` type) is returned. + +```python +>>> nums = {1: "one", 2: "two", 3: "three"} +>>> nums.get(4) + +>>> result = nums.get(4) +>>> type(result) + +``` + +If we want to provide a *default value* if the key is missing, we also pass an *optional* argument to the `my_dict.get(key)` method like so: `my_dict.get(key, default_val)` + +```python +>>> nums = {1: "one", 2: "two", 3: "three"} +>>> nums.get(4, "default") +'default' +``` + +#### Adding, Removing + +To add a new key value pair to the dictionary, you'll use square-bracket notation. + +If you try to put a key into a dictionary that's already there, you'll just end up replacing it. To avoid subtle bugs, you can check if a particular key is in a dictionary with the `in` keyword. We'll cover that technique in Chapter 6 - Control Statements and Looping. + +```python +>>> nums = {1: "one", 2: "two", 3: "three"} +>>> nums[8] = "eight" + +>>> nums +{1: 'one', 2: 'two', 3: 'three', 8: 'eight'} + +>>> nums[8] = "oops, overwritten" +>>> nums +{1: 'one', 2: 'two', 3: 'three', 8: 'oops, overwritten'} +>>> 8 in nums +True +``` + +#### Updating + +Just like with `list`s an `set`s, you can update the items in a dictionary with the items from another dictionary. + +```python +>>> colors = {"r": "Red", "g": "Green"} +>>> numbers = {1: "one", 2: "two"} +>>> colors.update(numbers) +>>> colors +{'r': 'Red', 'g': 'Green', 1: 'one', 2: 'two'} +``` + +### Complex Dictionaries + +One incredibly useful scenario for dictionaries is storing the values in a `list` or other sequence. Going into too much detail is outside of the scope of the class, but I'll show you a quick example: + +```python +>>> colors = {"Green": ["Spinach"]} +>>> colors +{'Green': ['Spinach']} +>>> colors["Green"].append("Apples") +>>> colors +{'Green': ['Spinach', 'Apples']} +``` + +### Working with `items`, `keys`, and `values` + +There are three useful methods you need to remember about `dict`ionary access: + +1. `my_dict.keys()` +2. `my_dict.values()` +3. `my_dict.items()` + +#### 1. `my_dict.keys()` Getting all the keys in a dictionary + +```python +>>> nums = {1: 'one', 2: 'two', 3: 'three', 8: 'eight'} +>>> nums.keys() +dict_keys([1, 2, 3, 8]) +``` + +#### 2. `my_dict.values()` Getting all the values in a dictionary. + +```python +>>> nums = {1: 'one', 2: 'two', 3: 'three', 8: 'eight'} +>>> nums.values() +dict_values(['one', 'two', 'three', 'eight']) +``` + +#### 3. `my_dict.items()` Getting all the items (key, value pairs) in a dictionary + +Notice that `my_dict.items()` returns a type that looks like a list. It contains two-item `tuple`s containing the key, value pairs. + +```python +>>> nums = {1: 'one', 2: 'two', 3: 'three', 8: 'eight'} +>>> nums.items() +dict_items([(1, 'one'), (2, 'two'), (3, 'three'), (8, 'eight')]) +``` diff --git a/website/content/02-introduction-to-python/080-advanced-datatypes/65-mutability.md b/website/content/02-introduction-to-python/080-advanced-datatypes/65-mutability.md new file mode 100644 index 0000000..c54e793 --- /dev/null +++ b/website/content/02-introduction-to-python/080-advanced-datatypes/65-mutability.md @@ -0,0 +1,31 @@ +--- +title: "Mutability Cheat Sheet" +date: 2019-03-03T13:06:52-08:00 +draft: false +weight: 65 +--- + +### Mutability + +Mutability, simply put: the contents of a mutable object can be changed, while the contents of an immutable object cannot be. + +### Simple Types + +All of the simple data types we covered first are **immutable** + +| type | use | mutable? | +|--------------------------- |------------------------- |---------- | +| `int`, `float`, `decimal` | store numbers | **no** | +| `str` | store strings | **no** | +| `bool` | store `True` or `False` | **no** | + +### Container Types + +For the mutability of the container types we covered in this chapter, check this helpful list: + +| container type | use | mutable? | +|---------------- |--------------------------------------------------------------------------------------------------------- |---------- | +| `list` | ordered group of items, accessible by position | **yes** | +| `set` | mutable unordered group consisting only of immutable items. useful for set operations (membership, intersection, difference, etc) | **yes** | +| `tuple` | contain ordered groups of items in an **immutable** collection | **no** | +| `dict` | contains key value pairs | **yes** | diff --git a/website/content/02-introduction-to-python/080-advanced-datatypes/70-exercise.md b/website/content/02-introduction-to-python/080-advanced-datatypes/70-exercise.md new file mode 100644 index 0000000..5b3a7bc --- /dev/null +++ b/website/content/02-introduction-to-python/080-advanced-datatypes/70-exercise.md @@ -0,0 +1,358 @@ +--- +title: "Practice" +date: 2019-02-04T15:28:32-08:00 +draft: false +weight: 70 +pre: "⭐️ " +--- + +## Lists, Dictionaries, Tuples, and Sets + +### Lists + +Lists are great for storing an ordered sequence of objects. Remember that you can see the current state of your list at any time by typing the name of your list by itself. Check your list after every operation to see if it has changed. + +```python +>>> my_list = ["h", "e", "l", "l", "o"] +# Let's look at our list: +>>> my_list +# Let's add to my_list: +>>> my_list.append("!") +# Now let's see it again: +>>> my_list +``` + +Let's play with slices. How do we get the last two elements of our list? + +```python +# We know the number of items in our list is 6... +>>> len(my_list) +6 +# So the last two indexes are 4 and 5. Since the first number in the slice is inclusive, and the second number is exclusive, we can ask for everything between index 4 and 6 +>>> my_list[4:6] +# We can also say "Give me everything after index 4 +>>> my_list[4:] +# Or, we can ask for just the last two items without caring how big the list is. This means "give me everything starting from two before the end": +>>> my_list[-2:] +``` + +There are many other ways to interact with our lists as well: + +```python +# Remove the first L: +>>> my_list.remove("l") +# Let's put it back at index 2 +>>> my_list.insert(2, "l") + +# Delete any element +>>> del my_list[0] +# Remove and return the last element. Useful for queues! +>>> last_item = my_list.pop() +>>> last_item + +# We can also look at individual items my using an index: +>>> my_list[2] +# Or we can see if a certain value exists in the list: +>>> "!" in my_list +# Let's sort our list in reverse order +>>> my_list.sort(reverse=True) +>>> my_list +# Note that sort() doesn't return anything, it sorts the list in-place +# You can also use the sorted() function to return a new, sorted list without modifying the old one +>>> sorted(my_list, reverse=False) +>>> my_list +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> my_list = ["h", "e", "l", "l", "o"] +>>> my_list +['h', 'e', 'l', 'l', 'o'] +>>> my_list.append("!") +>>> my_list +['h', 'e', 'l', 'l', 'o', '!'] + +>>> len(my_list) +6 +>>> my_list[4:6] +['o', '!'] +>>> my_list[4:] +['o', '!'] +>>> my_list[-2:] +['o', '!'] + +>>> my_list.remove("l") +>>> my_list.insert(2, "l") +>>> del my_list[0] +>>> last_item = my_list.pop() +>>> last_item +'o' +>>> my_list[2] +'l' +>>> "!" in my_list +False +>>> my_list.sort(reverse=True) +>>> my_list +['o', 'l', 'l', 'h', 'e', '!'] +>>> sorted(my_list, reverse=False) +['!', 'e', 'h', 'l', 'l', 'o'] +``` + +{{% /expand%}} + +### Sets + + +Sets are a great data type for storing unique data - you can only have one of any given object in a set. Sets are unordered, thus you can't access them with `[]` indexing syntax, but they do have some handy functions. + +Let's play with some set operations: + +```python +# Create an empty set +>>> my_set = {} +>>> type(my_set) +# Gotcha: using {} actually creates an empty dictionary. To create an empty set, use set() +>>> my_set = set() +>>> my_set + +# Let's create a non-empty set +>>> my_set = {1, 2, 3} +# We can add and remove items from the set +>>> my_set.add(4) +>>> my_set.remove(2) +# We can test if an item exists in the set +>>> 2 in my_set + +# Unlike lists, every item in a set must be unique +>>> my_set +>>> my_set.add(3) +>>> my_set +# There is still only one 3 in the set + +>>> my_set +# my_set should equal {1, 3, 4} +>>> my_other_set = {1, 2, 3} +# We can combine two sets +>>> my_set.union(my_other_set) +# We can get the intersection of two sets +>>> my_set.intersection(my_other_set) +# We can get the difference of two sets +>>> my_set.difference(my_other_set) + +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> my_set = {} +>>> type(my_set) + +>>> my_set = set() +>>> type(my_set) + + +>>> my_set = {1, 2, 3} +>>> my_set.add(4) +>>> my_set.remove(2) +>>> 2 in my_set +False + +>>> my_set +{1, 3, 4} +>>> my_set.add(3) +>>> my_set +{1, 3, 4} + +>>> my_other_set = {1, 2, 3} +>>> my_set.union(my_other_set) +{1, 2, 3, 4} +>>> my_set.intersection(my_other_set) +{1, 3} +>>> my_set.difference(my_other_set) +{4} +``` + +{{% /expand%}} + + +### Tuples + +Tuples are a lightweight way to hold information that describes something, like a person - their name, age, and hometown. You can think about it kind of like a row in a spreadsheet. Tuples are represented inside parentheses, however parentheses are not required to create a tuple, just a sequence of objects followed by commas. + +```python +>>> my_tuple = 1, +>>> my_tuple +# Let's add to our tuple +>>> my_tuple[1] = 2 +``` + +Oops! Remember that tuples are immutable, so you can't change them once they've been created. Tuples are great for moving data around in a lightweight way, because you can unpack them easily into multiple variables. + +```python +>>> person = ('Jim', 29, 'Austin, TX') +>>> name, age, hometown = person +>>> name +>>> age +>>> hometown +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> my_tuple = 1, +>>> my_tuple +(1,) +>>> my_tuple[1] = 2 +Traceback (most recent call last): + File "", line 1, in +TypeError: 'tuple' object does not support item assignment +``` + +```python +>>> person = ('Jim', 29, 'Austin, TX') +>>> name, age, hometown = person +>>> name +'Jim' +>>> age +29 +>>> hometown +'Austin, TX' +``` + +{{% /expand%}} + + +### Dictionaries + +Dictionaries are great for storing data that you can index with keys. The keys must be unique, and the dictionaries *are* stored in the order you inserted items, however this is only guaranteed as of Python 3.7. + +```python +>>> my_dict = {"key": "value"} +# Remember, dictionaries don't have numerical indexes like lists, so if you try to use an index number... +# Unless 0 happens to be a key. +>>> my_dict[0] +# You'll get a KeyError! + +# Let's put some more things into our dictionary +>>> my_dict["hello"] = "world" +>>> my_dict["foo"] = "bar" +>>> my_dict + +# What was the value for "hello" again? +>>> my_dict["hello"] +# You can also use get() to get a key +>>> my_dict.get("hello") +# What if the key you want doesn't exist? +>>> my_dict["baz"] +# If you're not sure if a key exists, you can ask: +>>> "baz" in my_dict +# Or you can use a default value. If "baz" doesn't exist, return "boo": +>>> my_dict.get("baz", "boo") + +# Let's try separating the dictionary into lists of keys and values: +>>> my_dict.keys() +>>> my_dict.values() + +# What if we want to iterate over a dictionary's items? We can use the items() function to get a list of tuples: +>>> my_dict.items() +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> my_dict = {"key": "value"} +>>> my_dict[0] +Traceback (most recent call last): + File "", line 1, in +KeyError: 0 + +>>> my_dict["hello"] = "world" +>>> my_dict["foo"] = "bar" +>>> my_dict +{'foo': 'bar', 'hello': 'world', 'key': 'value'} + +>>> my_dict["hello"] +'world' +>>> my_dict.get("hello") +'world' +>>> my_dict["baz"] +Traceback (most recent call last): + File "", line 1, in +KeyError: 'baz' +>>> "baz" in my_dict +False +>>> my_dict.get("baz", "default response") +'default response' + +>>> my_dict.keys() +['foo', 'hello', 'key'] +>>> my_dict.values() +['bar', 'world', 'value'] + +>>> my_dict.items() +[('foo', 'bar'), ('hello', 'world'), ('key', 'value')] +``` + +{{% /expand%}} + +### Mutability + +Remember, in Python, some data types are **immutable** -- that means that once they're created, their contents can't be changed. Tuples are immutable - once you make one, you can't alter it, you can only make a new one. Conversely, lists, dictionaries, and sets are mutable - you can change them without making new ones. + +Let's see this in practice: + +```python +# Lists are mutable +>>> my_list = [1, 2, 3] +>>> my_list[0] = 'a' +>>> my_list + +# Dictionaries are also mutable +>>> my_dict = {"hello": "world"} +>>> my_dict["foo"] = "bar" +>>> my_dict + +# Sets are mutable, but don't support indexing or item assignment, so you have to use add() and remove() +>>> my_set = {1, 2, 3} +>>> my_set[0] = 'a' # This will throw a TypeError +>>> my_set.add('a') +>>> my_set + +# Tuples are immutable +>>> my_tuple = (1, 2, 3) +>>> my_tuple[0] = 'a' # This will throw a TypeError +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> my_list = [1, 2, 3] +>>> my_list[0] = 'a' +>>> my_list +['a', 2, 3] + +>>> my_dict = {"hello": "world"} +>>> my_dict["foo"] = "bar" +>>> my_dict +{'hello': 'world', 'foo': 'bar'} + +>>> my_set = {1, 2, 3} +>>> my_set[0] = 'a' +Traceback (most recent call last): + File "", line 1, in +TypeError: 'set' object does not support item assignment +>>> my_set.add('a') +>>> my_set +{1, 2, 3, 'a'} + +>>> my_tuple = (1, 2, 3) +>>> my_tuple[0] = 'a' +Traceback (most recent call last): + File "", line 1, in +TypeError: 'tuple' object does not support item assignment +``` + +{{% /expand%}} \ No newline at end of file diff --git a/website/content/02-introduction-to-python/080-advanced-datatypes/_index.md b/website/content/02-introduction-to-python/080-advanced-datatypes/_index.md new file mode 100644 index 0000000..b0eb5c0 --- /dev/null +++ b/website/content/02-introduction-to-python/080-advanced-datatypes/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Advanced Container Types" +date = 2019-01-25T15:04:34-06:00 +weight = 80 +chapter = true +draft = false +pre = "4. " ++++ + +### Chapter 4 + +# Advanced Data Types - Containers and sequences + +Now that we've got the basics of strings and numbers down, let's talk about the advanced data types - `list`, `tuple`, `dict` and `set`. These are container objects that let us organize other types of objects into one data structure. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/090-boolean-logic/10-truthiness.md b/website/content/02-introduction-to-python/090-boolean-logic/10-truthiness.md new file mode 100644 index 0000000..f7ecf7f --- /dev/null +++ b/website/content/02-introduction-to-python/090-boolean-logic/10-truthiness.md @@ -0,0 +1,108 @@ +--- +title: "Truthiness" +date: 2019-02-10T18:17:39-08:00 +draft: false +weight: 10 +--- + +Evaluating expression to be `True` or `False` will help us control the flow of our program. + +### cheat sheet + +| type | truthiness | | +|--------------------------------------------- |-------------------------------------------------------------------------------- |--- | +| `int` | `0` is `False`, all other numbers are `True` (including negative) | | +| containers - `list`, `tuple`, `set`, `dict` | empty container evaluates to `False`, container with items evaluates to `True`) | | +| `None` | `False` | | + +We talked about `boolean` types, `True` and `False` earlier. `True` and `False` are keywords in Python, so make sure you don't name your variables the same thing. + +```python +>>> True +True +>>> False +False +``` + +Sometimes the truth is obvious. For example `3 < 5` is always `True`. Other times, in Python, the truth value might surprise you. Let's review. First, let's start with an expression we know is always `True`. + +```python +>>> 3 < 5 +True +``` + +{{% notice tip %}} +Tip: If you want to test your assumptions about an expression that returns `True` or `False`, you can pass it into the constructor for `bool`eans: `bool(expression)`. +{{% /notice %}} + +### Numbers + +In Python, the integer `0` is always `False`, while every other number, *including negative numbers*, are `True`. In fact, under the hood, `bool`eans inherit from `int`egers. + +```python +>>> bool(0) +False +>>> bool(1) +True +>>> bool(-1) +True +``` + +### Sequences + +Empty sequences in Python always evaluate to `False`, **including empty `str`ings.** + +```python +>>> bool("") # String +False +>>> bool([]) # Empty List +False +>>> bool(set()) # Empty Set +False +>>> bool({}) # Empty Dictionary +False +>>> bool(()) # Empty Tuple +False +``` + +Sequences with at least one value will evaluate to `True`. + +```python +>>> bool("Hello") # String +True +>>> bool([1]) # List +True +>>> bool({1}) # Set +True +>>> bool({1: 1}) # Dictionary +True +>>> bool((1,)) # Tuple +True +``` + +### `None` + +The `None` type in Python represents nothing. No returned value. It shouldn't come as a surprise that the truthiness of `None` is `False`. + +```python +>>> bool(None) +False +``` + +`None` is commonly used as a placeholder to mean *"I haven't set this value yet."* Since empty `str`ings and sequence evaluate to `False`, we need to be very careful when we're checking if a sequence has been *declared* or not, or if it's *empty*. We'll review this concept again when talking about `if` statements later in the day. + +```python +>>> my_name = None +>>> bool(my_name) +False +>>> my_name = "" +>>> bool(my_name) +False + +>>> my_list = None +>>> bool(my_list) +False +>>> my_list = [] +>>> bool(my_list) +False +``` \ No newline at end of file diff --git a/website/content/02-introduction-to-python/090-boolean-logic/20-comparisons.md b/website/content/02-introduction-to-python/090-boolean-logic/20-comparisons.md new file mode 100644 index 0000000..83ff9a0 --- /dev/null +++ b/website/content/02-introduction-to-python/090-boolean-logic/20-comparisons.md @@ -0,0 +1,128 @@ +--- +title: "Comparisons" +date: 2019-02-10T18:18:14-08:00 +draft: false +weight: 20 +--- + +How can we compare different values with each other? + +### Order Comparisons Cheat Sheet + +|Operator|Means| +|---|---| +|`<`|less-than| +|`<=`|less-than-or-equal-to| +|`>`|greater-than| +|`>=`|greater-than-or-equal-to| + +In Python, comparing numbers is pretty straight forward. + +```python +>>> 1 < 10 # 1 is less than 10? True +True +>>> 20 <= 20 # 20 is less than or equal to 20? True +True +>>> 10 > 1 # 10 is greater than 1? True +True +>>> -1 > 1 # -1 is greater than 1? False +False +>>> 30 >= 30 # 30 is greater than or equal to 30? True +True +``` + +Things get interesting when you try to compare strings. Strings are compared lexicographically. That means by the ASCII value of the character. You don't need to know much about ASCII, besides that capital letters come before lower case ones. + +Each character in the two strings is checked one by one, until a character is found that is of a different value. That determines the order. Under the hood, this allows Python to sort strings by comparing them to each other. + +```python +>>> "T" < "t" # Upper case letters are "lower" valued. +True +>>> "a" < "b" +True +>>> "bat" < "cat" +True +``` + +### Equality Cheat Sheet + +|Operator|Means| +|---|---| +|`==`|equals| +|`!=`|not-equals| + +The equality operators `val1 == val2` *(`val1` equals `val2`)* and `val1 != val2` *(`val1` doesn't equal `val2`)* compare the contents of two different values and return a `bool`ean. + +Equality works like you'd expect it to for simple data types. + +```python +>>> a = 1 +>>> b = 1 +>>> a == b +True +>>> a != b +False + +>>> a = "Nina" +>>> b = "Nina" +>>> a == b +True +>>> a != b +False +``` + +Equality for container types is interesting. Even though `a` and `b` are two different `list`s, their contents are still the same. So compared two lists containing the same values with `==` will return `True`. + +```python +>>> a = [1, 2, 3] +>>> b = [1, 2, 3] +>>> a == b +True +>>> a != b +False +``` + +### Identity Cheat Sheet + +|Operator|Means| +|---|---| +|`is`| *is* the same object in memory? (not equality!)| +|`is not`| *is not* the same object in memory? (not equality!)| + +{{% notice note %}} +This is something that trips up Python beginners, so make sure you remember that *equality* (`==`, `!=`) **is not** the same as *identity* (`is`, `not is`). +{{% /notice %}} + +The `is` keywords tests if the two compared objects are stored in the same memory location. I won't go into too much detail into why, but remember **not** to use `is` when what you actually want to check for is equality. + +```python +>>> a = [1, 2, 3] +>>> b = [1, 2, 3] + +>>> a == b # Testing for equality. a and b contain the same values +True +>>> a is b # Testing for identity. a and b are NOT the same object. +False +``` + +{{% notice tip %}} +When you're first starting out, the only place you'll want to use the `is` keyword is to explicitly compare a value to the built-in types of `None`, `True`, or `False`. +{{% /notice %}} + +```python +>>> a = True +>>> a is True +True + +>>> b = False +>>> b is False +True +>>> b is not True # Opposite of is b True. aka is b False? +True + +>>> c = None +>>> c is None +True +>>> c is not None +False +``` \ No newline at end of file diff --git a/website/content/02-introduction-to-python/090-boolean-logic/30-and-or-not.md b/website/content/02-introduction-to-python/090-boolean-logic/30-and-or-not.md new file mode 100644 index 0000000..e534d23 --- /dev/null +++ b/website/content/02-introduction-to-python/090-boolean-logic/30-and-or-not.md @@ -0,0 +1,191 @@ +--- +title: "and, or, not" +date: 2019-02-10T18:17:45-08:00 +draft: false +weight: 30 +--- + +`and`, `or`, and `not` are the three basic types of boolean operators that are present in math, programming, and database logic. + +In other programming languages, you might have seen the concept of `and` represented with `&&`, `or`, represented with `||`, and `not` represented by `!`. The Python language is instead focused on readability. So we'll use the english `and` instead of trying to remember fancy symbols. Python still uses the `&`, `|` and `!` expressions, but they're used for bitwise operations. + +You can use them to compare one (or more expressions) and determine if they evaluate to `True` or `False`. + +Thankfully, you don't have to be a computer scientist to understand them if you use this handy table. + +### `and`, `or`, `not` Cheat Sheet + +|Operation|Result| +|---|---| +|`a or b`|if a is False, then b, else a| +|`a and b`|if a is False, then a, else b| +|`not a`|if a is false, then `True`, else `False`| + + +### `and` + + + +{{% notice note %}} +For `a and b`, if a is false, a is returned. Otherwise b is returned. +*If `a and b` are both `bool`ean values, the expression evaluates to`True` if both a and b are `True`.* +{{% /notice %}} + +```python +>>> a = True # a is True +>>> b = True +>>> a and b # True is returned. (value of b) +True + +>>> a = False # a is False +>>> b = True +>>> a and b # False is returned. (value of a) +False + +>>> a = False # a is False +>>> b = False +>>> a and b # False is returned. (value of a) +False +``` + +Notice what happens when do the same thing to values that have a "truthiness" to them. + +```python +>>> bool(0) # Verify that zero is "falsey" +False +>>> bool(1) # Verify that one is "truthy" +True +>>> 0 and 1 # 0 is False. 0 is returned. +0 +``` + +### `or` + + + +{{% notice note %}} +For `a or b`, if a is false, b is returned. If a is true, a is returned. +*`a or b` evaluates to `True` if either (or both) of the expressions are true.* +{{% /notice %}} + +```python +>>> a = True # a is true +>>> b = True +>>> a or b # True is returned (value of a) +True + +>>> a = False # a is false +>>> b = True +>>> a or b # True is returned (value of b) +True + +>>> 0 or 1 # 0 is false. Return 1. +1 +``` + +### `not` + +| a | `not` a | +|--------- |------------ | +| true | False | +| false | **`True`** | + + +{{% notice note %}} +`not a` reverses the `bool`ean value of `a`. If it *was* true, it will return `False`. If it was false, it will return `True`. +{{% /notice %}} + +```python +>>> a = True +>>> not a # not returns the opposite. True -> False +False + +>>> a = False +>>> not a # not returns the opposite. False -> True +True +``` + +And again, with numbers. Remember, zero is considered `False`, any other number is considered `True`. + +```python +>>> bool(1) +True +>>> not 1 +False +>>> bool(0) +False +>>> not 0 +True +``` + +### In Combination + +When combining multiple boolean operators, you can add optional parenthesis for readability. + +```python +>>> a = True +>>> b = True +>>> c = False + +>>> a and (b or c) +True +``` + +You can combine multiple operators to test complex assumptions. For example, to return `True` only if *both* values are `False`, we can use the `not` negation operation on the result of an `or`. + +```python +>>> a = False +>>> b = False + +>>> a or b # False because both are False. +False + +>>> not (a or b) # True - checking if both are False. +True +``` + +### With "truthiness" + +Remember, we learned that some values in Python are *falsey* like the number zero, and some are *truthy* like any number *expect* for zero. + +It's a little counter intuitive, but when we compare values other than `bool`eans, our code behaves a little differently. + +|Operation|Result| +|---|---| +|`x or y`|if x is false, then y, else x| +|`x and y`|if x is false, then x, else y| + +Let's see it in action. First, lets test our assumptions again. + +```python +>>> bool(0) # Truthiness of 0 is False +False + +>>> bool(1) # Truthiness of 1 is True +True + +>>> bool(None) # Truthiness of None type is False +False + +>>> 1 or 0 # Returns 1, the True value +1 + +>>> 1 and 0 # Returns 0, the False value +0 + +>>> 0 or None # Neither are True. Returns nothing (None) +``` \ No newline at end of file diff --git a/website/content/02-introduction-to-python/090-boolean-logic/40-exercise.md b/website/content/02-introduction-to-python/090-boolean-logic/40-exercise.md new file mode 100644 index 0000000..4cc1c25 --- /dev/null +++ b/website/content/02-introduction-to-python/090-boolean-logic/40-exercise.md @@ -0,0 +1,182 @@ +--- +title: "Practice" +date: 2019-03-03T00:00:00-08:00 +draft: false +weight: 40 +pre: "⭐️ " +--- + +## Boolean Logic + +### Comparisons + +Let's practice using our comparison operators. Remember: + +|Operator|Means| +|---|---| +|`<`|less-than| +|`<=`|less-than-or-equal-to| +|`>`|greater-than| +|`>=`|greater-than-or-equal-to| +|`==`|equals| +|`!=`|not-equals| + +Remember, the first six operators test the object's *value*. `is` and `is not` test whether two objects are the same thing. This is useful for singletons, such as `None` or `False`. We won't be using them much in this intro course, but feel free to play with them. + +```python +>>> 10 > 5 +>>> 5 > 10 +>>> 10 > 10 +>>> 10 >= 10 +>>> 5 < 10 +>>> 5 < 5 +>>> 5 <= 5 +>>> 5 == 5 +>>> 5 != 10 +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> 10 > 5 +True +>>> 5 > 10 +False +>>> 10 > 10 +False +>>> 10 >= 10 +True +>>> 5 < 10 +True +>>> 5 < 5 +False +>>> 5 <= 5 +True +>>> 5 == 5 +True +>>> 5 != 10 +True +``` + +{{% /expand %}} + +### Truthiness + +Different languages have different ideas of what is "truthy" and "falsy." In Python, all objects can be tested for truth, and an object is considered True unless except under certain circumstances that we talked about earlier in the chapter. Remember that checking if an object is "equal" to another object doesn't necessarily mean the same thing. An object is considered "truthy" if it satisfies the check performed by `if` or `while` statements. + +Let's try a few of these out: + +```python +>>> 5 == True +>>> # The number 5 does not equal True, but... +>>> if 5: +... print("The number 5 is truthy!") +... +>>> # The number 5 is truthy for an if test! +``` + +True and False can also be represented by 1 and 0 +```python +>>> 1 == True +>>> 0 == False +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> 5 == True +False +>>> # The number 5 does not equal True, but... +>>> if 5: +... print("The number 5 is truthy!") +... +The number 5 is truthy! +>>> # The number 5 is truthy for an if test! +``` + +```python +>>> 1 == True +True +>>> 0 == False +True +``` +{{% /expand %}} + +### Boolean Operators + +Python also supports boolean operators, although they're a little different than the comparison operators. Remember that `or` and `and` return one of their operands, rather than `True` or `False`. + +|Operation|Result| +|---|---| +|`x or y`|if x is false, then y, else x| +|`x and y`|if x is false, then x, else y| +|`not x`|if x is false, then `True`, else `False`| + +```python +>>> True or False +>>> [] or [1, 2, 3] +>>> "Hello" or None +``` + +```python +>>> True and False +>>> 5 and 0 +>>> [1] and [1, 2, 3] +>>> "Hello" and None +``` + +```python +# Of course, you can use `and` and `or` aren't limited to two operands +>>> a = False +>>> b = False +>>> c = False +>>> a or b or c +>>> b = True +>>> a or b or c + +>>> a and b and c +>>> a = True +>>> c = True +>>> a and b and c +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} +```python +>>> True or False +True +>>> [] or [1, 2, 3] +[1, 2, 3] +>>> "Hello" or None +'Hello' +``` + +```python +>>> True and False +False +>>> 5 and 0 +0 +>>> [1] and [1, 2, 3] +[1, 2, 3] +>>> "Hello" and None +>>> # No output, since the result was None +``` + +```python +>>> a = False +>>> b = False +>>> c = False +>>> a or b or c +False +>>> b = True +>>> a or b or c +True + +>>> a and b and c +False +>>> a = True +>>> c = True +>>> a and b and c +True +``` + +{{% /expand %}} diff --git a/website/content/02-introduction-to-python/090-boolean-logic/_index.md b/website/content/02-introduction-to-python/090-boolean-logic/_index.md new file mode 100644 index 0000000..b8649f6 --- /dev/null +++ b/website/content/02-introduction-to-python/090-boolean-logic/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Boolean Logic" +date = 2019-01-25T15:04:16-06:00 +weight = 90 +chapter = true +draft = false +pre = "5. " ++++ + +### Chapter 5 + +# Boolean Logic + +Boolean Logic allows you to test your assumptions. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/110-control-statements-looping/05-looping-in-python.md b/website/content/02-introduction-to-python/110-control-statements-looping/05-looping-in-python.md new file mode 100644 index 0000000..a08c35d --- /dev/null +++ b/website/content/02-introduction-to-python/110-control-statements-looping/05-looping-in-python.md @@ -0,0 +1,195 @@ +--- +title: "Looping in Python" +date: 2019-02-10T18:21:55-08:00 +draft: false +weight: 5 +--- + +### `for` Loop Cheat Sheet + +Looping in Python doesn't look like looping in other languages. + +If you write JavaScript, Java, or other languages, you might have seen code that looks something like this code, that keeps track of 3 things: the starting index, the condition the loop will run until, and which action to take (in this case, incrementing the variable `i` by 1) until the condition is met. + +```javascript +for (i = 0; i < 5; i++) { + text += "The number is " + i + "
"; +} +``` + +In fact, before these languages introduced something called a `for each` loop, that was also the clunky way you'd loop through items in a sequence. + +### Looping in Python + +Looping in Python is a simpler, cleaner process because the Python language prides itself on readability. + +Remember you used the `in` keyword to test if an item was in a sequence? When combined with the `for` keyword, `in` can be used to indicate looping over each item in the sequence. The syntax is: `for single_item in items`, followed by a colon `:`, followed by a new line, a level of indentation, and the code you'd like to consider as the *body* of the loop. That is, the code that'll run multiple times, until there are no more items in the collection. + +Let's see it in action. + +```python +>>> colors = ["Red", "Green", "Blue", "Orange"] +>>> for color in colors: +... print(f"The color is: {color}") +The color is: Red +The color is: Green +The color is: Blue +The color is: Orange +``` + +#### Looping over a range of numbers + +Let's say we wanted to duplicate the code in the example JavaScript above, that prints out the numbers from 0 to 4. + +In order to do this, we'll need to use a built-in function called `range()`. The range function in python produces a sequence of integers from an optional and inclusive start, to a defined and exclusive finish. + + +In Python2, this function created a list of each number in that sequence. As you can imagine, it was horribly inefficient for large ranges. In Python3, the `range()` function returns a new optimized data type. It's great for optimization, but it's harder for debugging. + +{{% notice note %}} +If you want to explicitly see what a call to `range()` produces for debugging purposes, you can pass the result into the `list()` method to see all the values at once. For example: `list(range(5))`. Remember that this is inefficient, so use it for testing, not in production code. +{{% /notice %}} + +If we wanted to loop over all the values from 0 to 4, we'd use the range function like this: + +```python +>>> for num in range(5): +... print(f"The number is: {num}") +... +The number is: 0 +The number is: 1 +The number is: 2 +The number is: 3 +The number is: 4 +``` + +You'll notice that this call didn't *include* the number 5. + +What if we wanted the range from 1 to 4, instead of 0 to 4? `range()` can be called with `start` and `stop` parameters, and the range will *start* from `start`. + +```python +>>> for num in range(1, 5): +... print(f"The number is: {num}") +... +The number is: 1 +The number is: 2 +The number is: 3 +The number is: 4 +``` + +You can also pass an a third optional `step` parameter in. Let's say I quickly wanted to print out all the even numbers from 2 to 10. I would call `range(2, 11, 2)`. Remember, 2 is where we're starting, 11 is one higher than where we're ending (10), and 2 is the step, or the amount to jump between numbers. + +```python +>>> for num in range(2, 11, 2): +... print(f"The number is: {num}") +... +The number is: 2 +The number is: 4 +The number is: 6 +The number is: 8 +The number is: 10 +``` + +What do inclusive and exclusive mean in this context? *Exclusive* means that the end result *will not* include that number. If you'd like the numbers from 0 to 4, you would call `range(5)`. Consider 5 to the *stopping point*. *Inclusive* means the range will include the number. The `start` parameter is inclusive, meaning if you'd like the range of numbers from 1 to 4, you'd call `range(1, 5)`. + +{{% notice tip %}} +If you can't remember how to use range, don't forget to call `help(range)` from the command line. +{{% /notice %}} + +#### Looping over items with the index using `enumerate`. + +In Python, we avoid writing code like the JavaScript `for` loop at the top, but sometimes it's unavoidable, and we need a way to access the index of the items we're looping through. To do that we use a special function called `enumerate()`. The function takes a sequence, like a `list`, and it *returns* a `list` of tuples, containing the index of the item in the sequence, and the sequence itself. + +Don't worry about the list of tuples for now, but remember our tuple unpacking from earlier? + +```python +>>> point = (2, 5, 11) +>>> x, y, z = point +>>> x +2 +>>> y +5 +>>> z +11 +``` + +Because `enumerate()` returns a structure that looks like a list of `tuple`s under the hood, we can take advantage of tuple unpacking in the `for` loop. + +```python +>>> for index, item in enumerate(colors): +... print(f"Item: {item} is at index: {index}.") +... +Item: Red is at index: 0. +Item: Green is at index: 1. +Item: Blue is at index: 2. +Item: Orange is at index: 3. +``` + +{{% notice tip %}} +Remember, indicies in Python start at zero. +{{% /notice %}} + +#### Looping over a dictionary + +Now that we know we can use tuple unpacking in a for loop, let's go over how to loop over a dictionary. + +Let's say we have a dictionary of colors to their hex color code used for HTML in websites. + +```python +>>> hex_colors = { +... "Red": "#FF", +... "Green": "#008", +... "Blue": "#0000FF", +... } +``` + +{{% notice warning %}} +Remember, a dictionary is composed of key, value pairs. When we loop over a dictionary with the `for item in my_dict` syntax, we'll end up looping over **just** the keys. +{{% /notice %}} + +In this example, notice how we're looping over the wrong thing: + +```python +>>> for color in hex_colors: +... print(f"The value of color is actually: {color}") +... +The value of color is actually: Red +The value of color is actually: Green +The value of color is actually: Blue +``` + +{{% notice tip %}} +If we want to loop over the key, value pairs in a dictionary, we'll want to call `my_dict.items()`. +{{% /notice %}} + +We can use tuple unpacking along with the `my_dict.items()` list to loop over both the keys and the values at the same time. + +```python +>>> for color, hex_value in hex_colors.items(): +... print(f"For color {color}, the hex value is: {hex_value}") +... +For color Red, the hex value is: #FF0000 +For color Green, the hex value is: #008000 +For color Blue, the hex value is: #0000FF +``` + +##### Common Errors + +What if you try to loop over key, value pairs, and forget to use `my_dict.items()`? + +```python +>>> for color, hex_value in hex_colors: +... print(f"For color {color}, the hex value is: {hex_value}") +... +Traceback (most recent call last): + File "", line 1, in +ValueError: too many values to unpack (expected 2) +``` + +{{% notice info %}} +You'll see `ValueError: too many values to unpack (expected 2)` if you *forget* to call `my_dict.items()`, and try to loop over what you'd expect to be key, value pairs. +{{% /notice %}} + +#### Additional Resources + +If you really want to be a pro at looping in a Pythonic way, I recommend watching Raymond Hettinger's talk - [Transforming Code into Beautiful, Idiomatic Python](https://www.youtube.com/watch?time_continue=1855&v=OSGv2VnC0go) after the course. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/110-control-statements-looping/10-if-else-elif.md b/website/content/02-introduction-to-python/110-control-statements-looping/10-if-else-elif.md new file mode 100644 index 0000000..ff9d09c --- /dev/null +++ b/website/content/02-introduction-to-python/110-control-statements-looping/10-if-else-elif.md @@ -0,0 +1,230 @@ +--- +title: "if, else, elif" +date: 2019-02-10T18:20:07-08:00 +draft: false +weight: 10 +--- + +### The `if` Statement and Conditionals + +`if` in Python means: **only run the rest of this code once, *if* the condition evaluates to `True`.** Don't run the rest of the code at all if it's not. + +Anatomy of an `if` statement: Start with the `if` keyword, followed by a boolean value, an expression that evaluates to `True`, or a value with "Truthiness". Add a colon `:`, a new line, and write the code that will run if the statement is `True` under a level of indentation. + +{{% notice note %}} +Remember, just like with functions, we know that code is associated with an `if` statement *by it's level of indentation*. All the lines indented under the `if` statement will run if it evaluates to `True`. +{{% /notice %}} + +```python +>>> if 3 < 5: +... print("Hello, World!") +... +Hello, World! +``` + +{{% notice tip %}} +Remember, your `if` statements only run if the expression in them evaluates to `True` and just like with functions, you'll need to enter an extra space in the REPL to run it. +{{% /notice %}} + +#### Using `not` With `if` Statements + +If you only want your code to run if the expression is `False`, use the `not` keyword. + +```python +>>> b = False +>>> if not b: +... print("Negation in action!") +... +Negation in action! +``` + +### `if` Statements and Truthiness + +`if` statements also work with items that have a "truthiness" to them. + +For example: + + * The number 0 is False-y, any other number (including negatives) is Truth-y + * An empty `list`, `set`, `tuple` or `dict` is False-y + * Any of those structures with items in it is Truth-y + +```python +>>> message = "Hi there." + +>>> a = 0 +>>> if a: # 0 is False-y +... print(message) +... + +>>> b = -1 +>>> if b: # -1 is Truth-y +... print(message) +... +Hi there. + +>>> c = [] +>>> if c: # Empty list is False-y +... print(message) +... + +>>> d = [1, 2, 3] +>>> if d: # List with items is Truth-y +... print(message) +... +Hi there. +``` + +### `if` Statements and Functions + +You can easily declare `if` statements in your functions, you just need to mindful of the level of indentation. Notice how the code belonging to the `if` statement is indented at *two levels*. + +```python +>>> def modify_name(name): +... if len(name) < 5: +... return name.upper() +... else: +... return name.lower() +... +>>> name = "Nina" +>>> modify_name(name) +'NINA' +``` + +#### Nested `if` Statements + +Using the same technique, you can also *nest* your `if` statements. + +```python +>>> def num_info(num): +... if num > 0: +... print("Greater than zero") +... if num > 10: +... print("Also greater than 10.") +... +>>> num_info(1) +Greater than zero +>>> num_info(15) +Greater than zero +Also greater than 10. +``` + + +#### How *Not* To Use `if` Statements + +Remember, comparisons in Python evaluate to `True` or `False`. With conditional statements, we check for that value *implicitly*. In Python, we **do not** want to compare to `True` or `False` with `==`. + +{{% notice warning %}} +Warning - pay attention, because the code below shows what you **shouldn't** do. +{{% /notice %}} + +```python +# Warning: Don't do this! +>>> if (3 < 5) == True: # Warning: Don't do this! +... print("Hello") +... +Hello + +# Warning: Don't do this! +>>> if (3 < 5) is True: # Warning: Don't do this! +... print("Hello") +... +Hello +``` + +{{% notice tip %}} +Do this instead: +{{% /notice %}} + +```python +>>> if 3 < 5: +... print("Hello") +... +Hello +``` + +If we want to explicitly check if the value is explicitly set to `True` or `False`, we can use the `is` keyword. + +```python +>>> a = True # a is set to True +>>> b = [1, 2, 3] # b is a list with items, is "truthy" +>>> +>>> if a and b: # this is True, a is True, b is "truthy" +... print("Hello") +... +Hello +>>> if a is True: # we can explicitly check if a is True +... print("Hello") +... +Hello +>>> if b is True: # b does not contain the actual value of True. +... print("Hello") +... +>>> +``` + +### `else` + +The `else` statement is what you want to run **if and only if** your `if` statement wasn't triggered. + +An `else` statement is part of an `if` statement. If your `if` statement ran, your `else` statement will never run. + + +```python +>>> a = True +>>> if a: +... print("Hello") +... else: +... print("Goodbye") +... +Hello +``` + +And vice-versa. + +```python +>>> a = False +>>> if a: +... print("Hello") +... else: +... print("Goodbye") +... +Goodbye +``` + +In the REPL it must be written on the line after your last line of indented code. In Python code in a file, there can't be any other code between the `if` and the `else`. + +{{% notice info %}} +You'll see `SyntaxError: invalid syntax` if you try to write an `else` statement on its own, or put extra code between the `if` and the `else` in a Python file. +{{% /notice %}} + +```python +>>> if a: +... print("Hello") +... +Hello +>>> else: + File "", line 1 + else: + ^ +SyntaxError: invalid syntax +``` + +### `elif` Means Else, If. + +`elif` means *else if*. It means, **if** this `if` statement isn't considered `True`, try this **instead.** + +You can have as many `elif` statements in your code as you want. They get evaluated in the order that they're declared **until** Python finds one that's `True`. That runs the code defined in that `elif`, and skips the rest. + +```python +>>> a = 5 +>>> if a > 10: +... print("Greater than 10") +... elif a < 10: +... print("Less than 10") +... elif a < 20: +... print("Less than 20") +... else: +... print("Dunno") +... +Less than 10 +``` \ No newline at end of file diff --git a/website/content/02-introduction-to-python/110-control-statements-looping/30-while-loops.md b/website/content/02-introduction-to-python/110-control-statements-looping/30-while-loops.md new file mode 100644 index 0000000..656563c --- /dev/null +++ b/website/content/02-introduction-to-python/110-control-statements-looping/30-while-loops.md @@ -0,0 +1,49 @@ +--- +title: "while loops" +date: 2019-02-10T18:22:01-08:00 +draft: false +weight: 30 +--- + +`while` loops are a special type of loop in Python. Instead of running just once when a condition is met, like an `if` statement, they run **forever** until a condition is *no longer* met. + +`while` loops usually need to be accompanied by an always changing sentinel value. + +```python +>>> counter = 0 +>>> max = 4 +>>> +>>> while counter < max: +... print(f"The count is: {counter}") +... counter = counter + 1 +... +The count is: 0 +The count is: 1 +The count is: 2 +The count is: 3 +``` + +{{% notice warning %}} +Our loop will run forever if we forget to *update* the sentinel value. **Press Ctrl-C to exit the infinite loop.** +{{% /notice %}} + + +```python +# Warning: don't copy and paste this example. + +>>> counter = 0 +>>> max = 4 + +>>> while counter < max: +... print(f"The count is: {counter}") +... +# What happens if we don't update counter? +The count is: 0 +The count is: 0 +The count is: 0 +The count is: 0 +# An infinite loop repeated until we hit Ctrl-C +The count ^CTraceback (most recent call last): + File "", line 2, in +KeyboardInterrupt +``` \ No newline at end of file diff --git a/website/content/02-introduction-to-python/110-control-statements-looping/40-break-continue.md b/website/content/02-introduction-to-python/110-control-statements-looping/40-break-continue.md new file mode 100644 index 0000000..3d99e5b --- /dev/null +++ b/website/content/02-introduction-to-python/110-control-statements-looping/40-break-continue.md @@ -0,0 +1,153 @@ +--- +title: "break, continue, and return" +date: 2019-03-03T16:05:01-08:00 +draft: false +weight: 40 +--- + + + +`break` and `continue` allow you to control the flow of your loops. They're a concept that beginners to Python tend to misunderstand, so pay careful attention. + +### Using `break` + +The `break` statement will completely break out of the *current loop*, meaning it won't run any more of the statements contained inside of it. + + +```python +>>> names = ["Rose", "Max", "Nina", "Phillip"] +>>> for name in names: +... print(f"Hello, {name}") +... if name == "Nina": +... break +... +Hello, Rose +Hello, Max +Hello, Nina +``` + +{{% notice tip %}} +`break` completely **breaks out** of the loop. +{{% /notice %}} + +### Using `continue` + +`continue` works a little differently. Instead, it goes back to the start of the loop, skipping over any other statements contained within the loop. + +```python +>>> for name in names: +... if name != "Nina": +... continue +... print(f"Hello, {name}") +... +Hello, Nina +``` + +{{% notice tip %}} +`continue` continues to the **start of the loop** + +{{% /notice %}} + + +### `break` and `continue` visualized + +What happens when we run the code from this Python file? + +```python +# Python file names.py +names = ["Jimmy", "Rose", "Max", "Nina", "Phillip"] + +for name in names: + if len(name) != 4: + continue + + print(f"Hello, {name}") + + if name == "Nina": + break + +print("Done!") +``` + +![break and continue visualized](/02-introduction-to-python/110-control-statements-looping/images/break-continue.png?classes=shadow,border) + +#### Results + +{{%expand "See if you can guess the results before expanding this section." %}} +```bash +(env) $ python names.py + +Hello, Rose +Hello, Nina +Done! +``` +{{% /expand%}} + +### Using `break` and `continue` in nested loops. + +Remember, `break` and `continue` only work for the **current loop**. *Even though I've been programming Python for years, this is something that still trips me up!* + +```python +>>> names = ["Rose", "Max", "Nina"] +>>> target_letter = 'x' +>>> for name in names: +... print(f"{name} in outer loop") +... for char in name: +... if char == target_letter: +... print(f"Found {name} with letter: {target_letter}") +... print("breaking out of inner loop") +... break +... +Rose in outer loop +Max in outer loop +Found Max with letter: x +breaking out of inner loop +Nina in outer loop +>>> +``` + +{{% notice tip %}} +`break` in the inner loop only breaks out of the inner loop! The outer loop continues to run. +{{% /notice %}} + + +### Loop Control in `while` loops + +You can also use `break` and `continue` in `while` loops. One common scenario is running a loop forever, until a certain condition is met. + +```python +>>> count = 0 +>>> while True: +... count += 1 +... if count == 5: +... print("Count reached") +... break +... +Count reached +``` + +{{% notice note %}} +Be careful that your condition will eventually be met, or else your program will get stuck in an infinite loop. For production use, it's better to use asynchronous programming. +{{% /notice %}} + +### Loops and the `return` statement + +Just like in functions, consider the `return` statement the hard kill-switch of the loop. + +```python +>>> def name_length(names): +... for name in names: +... print(name) +... if name == "Nina": +... return "Found the special name" +... +>>> names = ["Max", "Nina", "Rose"] +>>> name_length(names) +Max +Nina +'Found the special name' +``` \ No newline at end of file diff --git a/website/content/02-introduction-to-python/110-control-statements-looping/50-exercise.md b/website/content/02-introduction-to-python/110-control-statements-looping/50-exercise.md new file mode 100644 index 0000000..f12dc48 --- /dev/null +++ b/website/content/02-introduction-to-python/110-control-statements-looping/50-exercise.md @@ -0,0 +1,438 @@ +--- +title: "Practice" +date: 2019-03-08T00:00:00-08:00 +draft: false +weight: 50 +pre: "⭐️ " +--- + +## Control statements and looping + +### `if`, `else`, and `elif` + +Let's practice our branching statements. Remember that `elif` (short for `else if`) is an optional branch that will let you add another `if` test, and `else` is an optional branch that will catch anything not previously caught by `if` or `elif`. + +```python +>>> def test_number(number): +... if number < 100: +... print("This is a pretty small number") +... elif number == 100: +... print("This number is alright") +... else: +... print("This number is huge!") +... +>>> test_number(5) +>>> test_number(99) +>>> test_number(100) +>>> test_number(8675309) +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} +```python +>>> def test_number(number): +... if number < 100: +... print("This is a pretty small number") +... elif number == 100: +... print("This number is alright") +... else: +... print("This number is huge!") +... +>>> test_number(5) +This is a pretty small number +>>> test_number(99) +This is a pretty small number +>>> test_number(100) +This number is alright +>>> test_number(8675309) +This number is huge! +``` +{{%/expand%}} + +You can also have multiple conditions in an if statement. This function prints "Fizzbuzz!" if the number is divisible by both 3 and 5 (the `%` or modulo operator returns the remainder from the division of two numbers): + +```python +>>> def fizzbuzz(number): +... if number % 3 == 0 and number % 5 == 0: +... print("Fizzbuzz!") +... +>>> fizzbuzz(3) +>>> fizzbuzz(5) +>>> fizzbuzz(15) +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} +```python +>>> def fizzbuzz(number): +... if number % 3 == 0 and number % 5 == 0: +... print("Fizzbuzz!") +... +>>> fizzbuzz(3) +>>> fizzbuzz(5) +>>> fizzbuzz(15) +Fizzbuzz! +``` +{{%/expand%}} + +Let's also practice using `if` to test for an empty list. Remember that an empty list is "Falsey", or resolves to `False`. Write a function to print a list of elements, or an error message if the list is empty. Print a special message if a list item is `None`: + +```python +>>> def my_func(my_list): +... if my_list: +... for item in my_list: +... if item is None: +... print("Got None!") +... else: +... print(item) +... else: +... print("Got an empty list!") +... +>>> my_func([1, 2, 3]) +1 +2 +3 +>>> my_func([2, None, "hello", 42]) +2 +Got None! +hello +42 +>>> my_func([]) +Got an empty list! +``` + + +## The `for` loop, `range()` and `enumerate()` + +Let's try making a list and looping over it: + +```python +>>> my_list = [0, 1, 2] +>>> for num in my_list: +... print(f"Next value: {num}") +... +``` + + +If we're just interested in looping over a list of numbers, we can use the `range()` function instead. Remember that the first argument is inclusive and the second is exclusive: + +```python +>>> for num in range(0, 3): +... print(f"Next value: {num}") +... +``` + +Another useful function is `enumerate()`, which iterates over an iterable (like a list) and also gives you an automatic counter. `enumerate()` returns a tuple in the form of (`counter`, `item`). + +```python +>>> my_list = ["foo", "bar", "baz"] +>>> for index, item in enumerate(my_list): +... print(f"Item {index}: {item}") +... +``` + +We can also loop over a dictionary's keys and/or values. If you try to iterate over the dictionary object itself, what do you get? + +```python +>>> my_dict = {"foo": "bar", "hello": "world"} +>>> for key in my_dict: +... print(f"Key: {key}") +... +# This is equivalent to... +>>> for key in my_dict.keys(): +... print(f"Key: {key}") +... +``` + +The `keys()` method returns the dictionary's keys as a list, which you can then iterate over as you would any other list. This also works for `values()` + +```python +>>> for value in my_dict.values(): +... print(f"Value: {value}") +... +``` + +The most useful function, however, is `items()`, which returns the dictionary's items as tuples in the form of (key, value): + +```python +>>> for key, value in my_dict.items(): +... print(f"Item {key} = {value}") +... +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} +```python +>>> my_list = [1, 2, 3] +>>> for num in my_list: +... print(f"Next value: {num}") +... +Next value: 0 +Next value: 1 +Next value: 2 +``` + +```python +>>> for num in range(0, 3): +... print(f"Next value: {num}") +... +Next value: 0 +Next value: 1 +Next value: 2 +``` + +```python +>>> my_list = ["foo", "bar", "baz"] +>>> for index, item in enumerate(my_list): +... print(f"Item {index}: {item}") +... +Item 0: foo +Item 1: bar +Item 2: baz +``` + +```python +>>> my_dict = {"foo": "bar", "hello": "world"} +>>> for key in my_dict: +... print(f"Key: {key}") +... +Key: foo +Key: hello + +>>> for key in my_dict.keys(): +... print(f"Key: {key}") +... +Key: foo +Key: hello + +>>> for value in my_dict.values(): +... print(f"Value: {value}") +... +Value: bar +Value: world + +>>> for key, value in my_dict.items(): +... print(f"Item {key} = {value}") +... +Item foo = bar +Item hello = world +``` + +{{%/expand%}} + +## `break`, `continue`, and `return` + +`break` and `continue` are important functions for controlling the program flow inside loops. `break` ends the loop immediately and continues executing from outside the loop's scope, and `continue` skips the remainder of the loop and continues executing from the next round of the loop. Let's practice: + +```python +>>> for num in range(0, 100): +... print(f"Testing number {num}") +... if num == 3: +... print("Found number 3!") +... break +... print("Not yet...") +... +``` + +Notice that "Not yet..." doesn't get printed for number 3, because we `break` out of the loop first. Let's try a `continue`: + +```python +>>> for num in range(0, 100): +... print(f"Testing number {num}") +... if num < 3: +... continue +... elif num == 5: +... print("Found number 5!") +... break +... print("Not yet...") +... +``` + +Notice that "Not yet..." doesn't get printed at all until the number is 3, because the `continue` short-circuits the loop back to the beginning. Then we `break` when we hit 5. + +You can also use the `return` keyword to break out of a loop within a function, while optionally returning a value. + +```python +>>> def is_number_in_list(number_to_check, list_to_search): +... for num in list_to_search: +... print(f"Checking {num}...") +... if num == number_to_check: +... return True +... return False +>>> my_list = [1, 2, 3, 4, 5] +>>> is_number_in_list(27, my_list) +>>> is_number_in_list(2, my_list) +``` + +Notice that our function `is_number_in_list` checks all the numbers in `my_list` on the first run, but on the next run, stops immediately when it hits 3 and returns `True`. + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> for num in range(0, 100): +... print(f"Testing number {num}") +... if num == 3: +... print("Found number 3!") +... break +... print("Not yet...") +... +Testing number 0 +Not yet... +Testing number 1 +Not yet... +Testing number 2 +Not yet... +Testing number 3 +Found number 3! +>>> +``` + +```python +>>> for num in range(0, 100): +... print(f"Testing number {num}") +... if num < 3: +... continue +... elif num == 5: +... print("Found number 5!") +... break +... print("Not yet...") +... +Testing number 0 +Testing number 1 +Testing number 2 +Testing number 3 +Not yet... +Testing number 4 +Not yet... +Testing number 5 +Found number 5! +``` + +```python +>>> def is_number_in_list(number_to_check, list_to_search): +... for num in list_to_search: +... print(f"Checking {num}...") +... if num == number_to_check: +... return True +... return False +... +>>> is_number_in_list(27, my_list) +Checking 1... +Checking 2... +Checking 3... +Checking 4... +Checking 5... +False +>>> is_number_in_list(2, my_list) +Checking 1... +Checking 2... +True +``` +{{%/expand%}} + +## `while` loop + +Instead of looping over a sequence, `while` loops continue looping while a certain condition is met (or not met). The condition is checked at the beginning every iteration. + +```python +>>> counter = 0 +>>> while counter < 3: +... print(f"Counter = {counter}") +... counter += 1 +``` + +Notice that the loop ends once `counter` 3, and the remainder of the loop is bypassed. You can also loop forever by using `while True` or `while False`, but you should make sure you have solid `break` conditions, or your program will just loop forever (unless that's what you want). + +```python +>>> counter = 0 +>>> while True: +... print(f"Counter = {counter}") +... if counter == 3: +... break +... counter += 1 +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> counter = 0 +>>> while counter < 3: +... print(f"Counter = {counter}") +... counter += 1 +... +Counter = 0 +Counter = 1 +Counter = 2 +``` + +```python +>>> counter = 0 +>>> while True: +... print(f"Counter = {counter}") +... if counter == 3: +... break +... counter += 1 +... +Counter = 0 +Counter = 1 +Counter = 2 +Counter = 3 +``` + +{{%/expand%}} + +## Nested Loops + +Nesting loops is often necessary and sometimes tricky. The `break` keyword will only get you out of whichever loop you're `break`ing. The only way to exit all loops is with multiple `break` statements (at each level), or the `return` keyword (inside a function). For example: + +```python +names = ["Rose", "Max", "Nina"] +target_letter = 'x' +found = False + +for name in names: + for char in name: + if char == target_letter: + found = True + break + + if found: + print(f"Found {name} with letter: {target_letter}") + break +``` + +Or: + +```python +>>> for x in range(0, 5): +... for y in range(0, 5): +... print(f"x = {x}, y = {y}") +... if y == 2: +... break +... +``` + +Notice how the inner `y` loop never gets above 2, whereas the outer `x` loop continues until the end of its range. + +{{%expand "Here's what you should have seen in your REPL:" %}} +```python +>>> for x in range(0, 5): +... for y in range(0, 5): +... print(f"x = {x}, y = {y}") +... if y == 2: +... break +... +x = 0, y = 0 +x = 0, y = 1 +x = 0, y = 2 +x = 1, y = 0 +x = 1, y = 1 +x = 1, y = 2 +x = 2, y = 0 +x = 2, y = 1 +x = 2, y = 2 +x = 3, y = 0 +x = 3, y = 1 +x = 3, y = 2 +x = 4, y = 0 +x = 4, y = 1 +x = 4, y = 2 +``` +{{%/expand%}} \ No newline at end of file diff --git a/website/content/02-introduction-to-python/110-control-statements-looping/_index.md b/website/content/02-introduction-to-python/110-control-statements-looping/_index.md new file mode 100644 index 0000000..46e76ad --- /dev/null +++ b/website/content/02-introduction-to-python/110-control-statements-looping/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Loops and Control Statements" +date = 2019-01-25T15:16:00-06:00 +weight = 110 +chapter = true +draft = false +pre = "6. " ++++ + +### Chapter 6 + +# Loops and Control Statements + +Loops and control statements allow us to control the logical flow of our program. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/110-control-statements-looping/code/names.py b/website/content/02-introduction-to-python/110-control-statements-looping/code/names.py new file mode 100644 index 0000000..b6a792b --- /dev/null +++ b/website/content/02-introduction-to-python/110-control-statements-looping/code/names.py @@ -0,0 +1,12 @@ +names = ["Jimmy", "Rose", "Max", "Nina", "Phillip"] + +for name in names: + if len(name) != 4: + continue + + print(f"Hello, {name}") + + if name == "Nina": + break + +print("Done!") diff --git a/website/content/02-introduction-to-python/110-control-statements-looping/images/break-continue.png b/website/content/02-introduction-to-python/110-control-statements-looping/images/break-continue.png new file mode 100644 index 0000000..631b9cf Binary files /dev/null and b/website/content/02-introduction-to-python/110-control-statements-looping/images/break-continue.png differ diff --git a/website/content/02-introduction-to-python/175-running-code/10-running-python-programs.md b/website/content/02-introduction-to-python/175-running-code/10-running-python-programs.md new file mode 100644 index 0000000..1316b49 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/10-running-python-programs.md @@ -0,0 +1,116 @@ +--- +title: "How To Run Them" +date: 2019-02-10T18:17:06-08:00 +draft: false +weight: 10 +--- + +### Creating Python Files with the `*.py` extension + +You know a file is a Python program when it ends with a `.py` extension. + +#### Naming Tips + +{{% notice tip %}} +Just like with formatting, Python's [PEP8 guidelines](https://www.python.org/dev/peps/pep-0008/#package-and-module-names) give us a few helpful tips about how to name our Python program files. +{{% /notice %}} + +ℹ️ In Python: + +1. Filenames should be ***all lowercase** +1. Words should be separated with **underscores `_`** +1. Filenames should be **short** + +✅ Some good example filenames: + +- `apis.py` +- `exceptions.py` +- `personal_blog.py` + +⛔️ Some bad example filenames: + +- `MYFILE.PY` +- `CamelCaseFile.py` +- `really_long_over_descriptive_project_file_name.py` + + +#### What are `*.pyc` files? + +{{% notice note %}} +For optimization and other reasons, Python code can be compiled to intermediary `.pyc` files. The good news is you don't have to worry about them. The bad news is, very occasionally stale versions of these compiled files can cause problems. To safely delete them from the current project directory, run `find . -name "*.pyc" -delete` (on linux or macOS). +{{% /notice %}} + +#### `git` tip: use a `.gitignore` for Python + +If you use `git` for source control, you'll want to make sure that these compiled `*.pyc` files are ignored, and not added to your repository. + +The best way to do this is to [add the standard `.gitignore` file for Python](https://github.com/github/gitignore/blob/master/Python.gitignore) to your project. + + +### Running Python Files From VS Code + +Running Python files from VS Code is really quick and easy. + +#### Creating New Python Files + +To create a new file in VS Code, hit `Ctrl+N` on Windows and Linux, and `⌘N` (command + N) on Mac OS. + +This will open a new file. Next, save the file with a `.py` extension. + +{{% notice info %}} +Create a new simple Python program in a file called `hello.py` in our `pyworkshop` direc tory with the following contents: +{{% /notice %}} + +```python +# in file: hello.py +greetings = ["Hello", "Bonjour", "Hola"] + +for greeting in greetings: + print(f"{greeting}, World!") +``` + +#### Opening The VS Code Terminal Window + +Next, you'll need to open your terminal if you don't have it open already. The quick keyboard shortcut to do that is Ctrl - ` + +{{% notice note %}} +If you already had your Python REPL open, you'll need to select a terminal with a shell in it (generally, the one labeled with `1:`). +{{% /notice %}} + +![](/02-introduction-to-python/175-running-code/images/terminal-drop-down.png) +![](/02-introduction-to-python/175-running-code/images/terminal-drop-down-select.png) + +#### Running The File + +Once you've opened your `hello.py` file and selected your new terminal window, open the VS Code command palette. + +{{% notice note %}} +Open the command palette with `Ctrl+Shift+P` on Windows and Linux, and `⌘⇧P` (command + shift + P) on Mac OS. +{{% /notice %}} + +Select Python: Run Python File in Terminal + +![](/02-introduction-to-python/175-running-code/images/vs-code-run-file-command-palette.png) + +You should see: + +```bash +Hello, World! +Bonjour, World! +Hola, World! +``` + +How easy was that? 🎉 + +### Running Python Files From a Non-VS Code Terminal + +If you want to run a Python file without using the command palette, just open your terminal of choice, `cd` into the directory with your code, and type in the command `python` followed by a space, and the name of your Python program. + +```bash +(env) $ python hello.py +Hello, World! +Bonjour, World! +Hola, World! +``` + +This also works in the VS Code terminal. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/20-print-tips.md b/website/content/02-introduction-to-python/175-running-code/20-print-tips.md new file mode 100644 index 0000000..a00da21 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/20-print-tips.md @@ -0,0 +1,169 @@ +--- +title: "Printing Tips" +date: 2019-02-10T18:13:59-08:00 +draft: false +weight: 20 +--- + +One of the nice things about the REPL is we can quickly and easily see the contents of our variables, just by typing their name and pressing enter. Unfortunately, running code from Python files doesn't do quite the same thing. + +In a file named `name.py`: + +```python +# file name.py +name = "Nina" +name +``` + +Output: + +```bash +(env) $ python name.py +``` + +*Notice, there was no output.* + +Now, in a file named `print_name.py`: +```python +# file print_name.py +name = "Nina" +print(name) +``` + +Output: +```bash +(env) $ python name.py +Nina +``` + +Hooray! Now we see some output. 🎉 + +{{% notice tip %}} +If you want to see any output from your Python programs, you'll need to use `print()`. +{{% /notice %}} + +### Debugging Your Code With `print()` + +As your Python programs become more complicated, you'll want to do some basic debugging to figure out what's going on under the hood. For beginners, using `print()` is a great way to accomplish that goal. + +{{% notice note %}} +If you write Python code on a team or plan on sharing it, it's a good idea to *remove* your debugging `print()`s before you share your code with others. +{{% /notice %}} + +In a Python file named `mystery.py`: + +```python +def mystery(): + num = 10 * 3 + + if num == 10: + print("Num was equal to 10") + num = num * 10 + if num == 20: + print("Num was equal to 20") + num = num * 20 + if num == 30: + print("Num was equal to 30") + num = num * 30 + + print(f"Value of returned num is: {num}") + return num + +mystery() +``` + +We'll see the output: + +```python +Num was equal to 30 +Value of returned num is: 900 +``` + +{{% notice tip %}} +Tip: As you continue your Python journey, try using a debugger, like the built-in `pdb` instead of the `print()` function to really dive into what your code is doing under the hood. +{{% /notice %}} + + +### Output Formatting Tips + +If your Python program will have terminal output, you can use these tips to make it a little nicer. + +#### Use new lines and tabs + + +Use control characters in your string to change how your output is represented. + +* `\n` for new line +* `\t` for tab + +In `formatting_example.py`: + +```python +# Use \n to add a new line, before, in the middle of, or after a string. +print("\nExtra New Line Before") +print("One Print\nTwo New Lines!") +print("Extra New Line After\n") + +# Use \t to add a tab. +print("\t Here's some tabbed output.") + +# Or, combine both! +print("\nOne Print\n\tOne Tab") +``` + +Output running: `python3 formatting_example.py`. + +```text +Extra New Line Before +One Print +Two New Lines! +Extra New Line After + + Here's some tabbed output. + +One Print + One Tab +``` + +#### Pretty Printing with `pprint` + +When printing large data structures like long lists or big dictionaries, they come out on one line. It's a bit hard to read. + +If you want a little bit of extra formatting, like having each element of a long list on a new line, you can use the included `pprint` module (stands for pretty print) in your files or in the REPL. + +```python +>>> long_list = list(range(23)) + +>>> print(long_list) +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22] + +>>> from pprint import pprint +>>> pprint(long_list) +[0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22] +``` + +{{% notice tip %}} +This will become more useful as your Python programs become more complex. +{{% /notice %}} \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/30-the-main-method.md b/website/content/02-introduction-to-python/175-running-code/30-the-main-method.md new file mode 100644 index 0000000..c441a8d --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/30-the-main-method.md @@ -0,0 +1,144 @@ +--- +title: "The main Method" +date: 2019-02-10T18:16:47-08:00 +draft: false +weight: 30 +--- + +Once you start writing more comprehensive Python programs, you'll want to include a `main` method in your code. + +The purpose of checking for the `main` method is to make sure that the code in your `main` method is only run when it's executed as a stand-alone program. Because of how Python's import system works, if someone else imports your Python program, any code in it is executed on import. + +We'll talk more about imports and modules on day two, but let's look at a quick example. + +Let's say we had a Python file named `name_lib.py` + +```python +def name_length(name): + return len(name) + +def upper_case_name(name): + return name.upper() + +def lower_case_name(name): + return name.lower() + +name = "Nina" +length = name_length(name) +upper_case = upper_case_name(name) + +print(f"The length is {length} and the uppercase version is: {upper_case}") +``` + +If we ran this code, we'd see exactly what we expect. + +```bash +(env) $ python name_lib.py +name_lib.py +The length is 4 and the uppercase version is: NINA +``` + +### Writing Reusable Code + +We went through all this trouble of writing a useful name library. What if someone else wanted to use our library in their own code by `import`ing it? + +Let's say someone else wrote their own program, in `other_program.py` + +```python +import name_lib + +my_name = "Fred" + +my_length = name_lib.name_length(my_name) +my_lower_case = name_lib.lower_case_name(my_name) + +print(f"In my code, my length is {my_length} and my lower case name is: {my_lower_case}") +``` + +If we ran this, we'd see the following result: + +```bash +(env) $ python other_program.py + +The length is 4 and the uppercase version is: NINA +In my code, my length is 4 and my lower case name is: fred +``` + +If I'm Fred, I just wanted to use this cool library. But all of a sudden, I'm seeing information about Nina! + +To prevent this from happening, we want to write a conditional that will only run *our* code if we're the ones running the program directly. + +To do that, we'll need to check if `__name__ == __main__`. + +Let's unwrap that. + +`__name__` is a special variable that's set by Python that tells it where it was called from. We can tell it's a special variable because it starts and ends with `__`. That's a hint that you don't want to *change* the value of this variable, or it could adversely affect the execution of your Python program. + +{{% notice tip %}} +In Python, `__` is also called double underscore, or *dunder*. +{{% /notice %}} + +Let's comment out our original `print`, and add the following line to the end of `name_lib.py`: + +```python +# Note: add to the bottom of name_lib.py + +# print(f"The length is {length} and the uppercase version is: {upper_case}") +print(f"The value of __name__ is: {__name__}") +``` + +Now, let's run `name_lib.py` again. We should see: + +```bash +(env) $ python name_lib.py +The value of __name__ is: __main__ +``` + +We're getting somewhere. When we run this file directly, we'll see that `__name__` has the *value* of `__main__`. + +What if we run our other program again? + +```bash +(env) $ python other_program.py +The value of __name__ is: name_lib +``` + +When we "run" our library by importing it, we'll see that it's `__name__` is set to the name of the file that it's in, minus the `.py` extension. In this case, `__name__` is set to `name_lib`. + +### Putting Code in a `main` Conditional + +To avoid running our code when it's imported by other modules, we put it in a conditional statement, and explicitly check `if __name__ == "__main__"`. + +Let's update `name_lib.py`, and put our own code inside of the conditional check. + +```python +def name_length(name): + return len(name) + +def upper_case_name(name): + return name.upper() + +def lower_case_name(name): + return name.lower() + +if __name__ == "__main__": + name = "Nina" + length = name_length(name) + upper_case = upper_case_name(name) + + print(f"The length is {length} and the uppercase version is: {upper_case}") +``` + +Now, if we run `other_program.py`, we'll see: + +```bash +(env) $ python other_program.py +In my code, my length is 4 and my lower case name is: fred +``` + +Much better! + +{{% notice note %}} +Using a `main` method is a common pattern that you’ll see in Python programs, and it comes in handy for being able to write programs that work both on their own and when imported into other programs. +{{% /notice %}} + diff --git a/website/content/02-introduction-to-python/175-running-code/50-exceptions.md b/website/content/02-introduction-to-python/175-running-code/50-exceptions.md new file mode 100644 index 0000000..d290ab6 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/50-exceptions.md @@ -0,0 +1,123 @@ ++++ +title = "Exceptions and Tracebacks" +date = 2019-01-25T15:03:40-06:00 +weight = 50 +draft = false ++++ + +We talked about how to read the traceback you see if an error occurs in a Python program earlier in the day, but let's talk about what we can do about it. + +### Uncaught Exceptions Exit Our Program + +Let's make a file called `exceptions.py`: + +```python +# this will throw an exception! +int("a") + +print("End of the program.") +``` + +And run it: +```bash +(env) $ python exceptions.py + +Traceback (most recent call last): + File "/Users/nina/projects/2019-fem-python/python/content/02-introduction-to-python/175-running-code/code/exceptions.py", line 2, in + int("a") +ValueError: invalid literal for int() with base 10: 'a' +``` + +We'll see that "Reached end of the program" was never printed out. + +{{% notice warning %}} +If we're running our Python code from a file, an uncaught exception will quit the program. +{{% /notice %}} + +### Using `try` and `except` to catch Exceptions + +In order to prevent our program from exiting, we'll need to *catch* the Exception with a `try` `except` block. The anatomy of a `try` `except` block: + +```text +try: + +except ExceptionClass: + +``` + +In order to write a `try` `except` block, we'll need to know the class name of the Exception we'd like to catch. Luckily, the name is printed right there, in the traceback! + +Let's update `exceptions.py` + +```python +try: + int("a") +except ValueError: + print("Oops, couldn't convert that value into an int!") + +print("Reached end of the program.") +``` + +And the output: + +```bash +(env) $ python exceptions.py +Oops, couldn't convert that value into an int! +Reached end of the program. +``` + +{{% notice tip %}} +You want to catch Exceptions that are as specific as possible. +{{% /notice %}} + +### Using `as` to Access The Exception + +You can optionally assign a label to the exception, and the exception will be assigned to the variable you specified, so you can look at it's message, or examine it in other ways with `except as `. + +Using this syntax, `variable_name` can be anything. In this case, I picked `error`, but you'll commonly see `e` used for this purpose in Python programs. + +Let's update `exceptions.py` one more time: + +```python +try: + int("a") +except ValueError as error: + print(f"Something went wrong. Message: {error}") + +print("Reached end of the program.") +``` + +If we run this code, we'll see: + +```bash +(env) $ python exceptions.py +Something went wrong. Message: invalid literal for int() with base 10: 'a' +Reached end of the program. +``` + +{{% notice note %}} +This example just scratches the surface. We'll cover Exceptions in much more detail in Day 2. +{{% /notice %}} + +### Anatomy of a Traceback + +When running our code from Python files, we'll need to look at your tracebacks a little more carefully. + +{{% notice tip %}} +Remember, to understand tracebacks, read them from bottom to top. +{{% /notice %}} + +When we ran our first example, we saw: + +```text +Traceback (most recent call last): + File "/Users/nina/projects/2019-fem-python/python/content/02-introduction-to-python/175-running-code/code/exceptions.py", line 2, in + int("a") +ValueError: invalid literal for int() with base 10: 'a' +``` + +If we start reading from the bottom up, we'll notice a lot of useful information. + +1. First, we'll see the exception that was thrown, along with its class `ValueError`. +2. Next, we'll see the code that caused the Exception. +3. One line up from that, we'll see the path and the file the exception originated in, as well as the **line number** to look on. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/60-working-with-files.md b/website/content/02-introduction-to-python/175-running-code/60-working-with-files.md new file mode 100644 index 0000000..f9a15d0 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/60-working-with-files.md @@ -0,0 +1,61 @@ ++++ +title = "Working With Files" +date = 2019-01-25T15:06:17-06:00 +weight = 60 +draft = false ++++ + +Python provides a built-in function for opening files, cleverly titled `open()`. The `open()` method will return an object that you can `read()` to get the data. By default, `open()` will open a file in read-only mode, however you can change this by passing a mode parameter. The list of optional modes is here: + +|Character|Meaning| +|---|---| +|'r'|open for reading (default)| +|'w'|open for writing, truncating the file first| +|'x'|open for exclusive creation, failing if the file already exists| +|'a'|open for writing, appending to the end of the file if it exists| +|'b'|binary mode| +|'t'|text mode (default)| +|'+'|open a disk file for updating (reading and writing)| + +Opening a file would look something like this: + +```python +# Open a file for reading +>>> my_file = open("my_file.txt") + +# Open a file for reading or writing +# This will replace any existing file +>>> my_file = open("my_file.txt", "w") + +# Open a file for reading or writing +# This will append to the end of any existing file +>>> my_file = open("my_file.txt", "a") +``` + +Of course, you always want to call `close()` on your open file object once you're done with it, so that your program doesn't leave open file handles dangling. But what happens if your program exits or crashes before you can close a file? Use a context manager! + + +### Context Managers + +Briefly, a Context Manager is like a wrapper around a block of code that depends on some resource. It's a safer way of handling resources than, say, using `open()` and then remembering to `close()` later (and hoping your program doesn't crash in between). It's similar to using `try... finally`, but cleaner to look at. Context managers can contain code that auto-magically provisions a resource before your code runs, and cleans up afterward. For example, the `open()` function also works as a context manager, so opening a file looks like this: + +```python +>>> with open("my_file.text") as my_file: +... contents = my_file.read() +``` + +You can perform all the functions that require access to the open file within the `with` scope, and once you exit the `with` scope, the context manager will automatically close your file for you, even if your code hits an exception inside the context manager. + + +### Working with Files + +Let's try working with a simple json file. Download this [`cities.json` file](/code/cities.json), which contains a listing of the top five cities in the US by population. We'll import the `json` module to help us parse the data, and we'll use a context manager to open the file. + +```python +>>> import json +>>> with open("cities.json") as cities_file: +... cities_data = json.load(cities_file) +... print(cities_data) +... +[{'name': 'New York', 'pop': 8550405}, {'name': 'Los Angeles', 'pop': 3971883}, {'name': 'Chicago', 'pop': 2720546}, {'name': 'Houston', 'pop': 2296224}, {'name': 'Philadelphia', 'pop': 1567442}] +``` \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/65-libraries.md b/website/content/02-introduction-to-python/175-running-code/65-libraries.md new file mode 100644 index 0000000..88791af --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/65-libraries.md @@ -0,0 +1,37 @@ ++++ +title = "Working with Libraries" +date = 2019-01-25T15:06:06-06:00 +weight = 65 +draft = false ++++ + +Working with external libraries in Python makes use of the `import` keyword. While this can go anywhere in your file, it's almost always best to import libraries at the top of each file where they're used. For example, in the last section, we were able to call upon the built-in `json` library by calling `import json` at the top of our code. + +Importing modules with the `import` keyword is usually the best method, because it preserves the module's namespace. However, you can also use the `from import ` syntax to import a specific object (function, variable, subclass, etc.) from a module into your program's namespace. + +For example, if we wanted a random integer between 0 and 100, we could use `random.randint()`: + +```python +>>> import random +>>> random.randint(0, 100) +42 +``` + +Notice that the namespace is preserved (we needed to call `random.randint()`). If we use `from` instead: + +```python +>>> from random import randint +>>> randint(0, 100) +64 +``` + + +### Installing the requests library with `pip` + +For the next chapter, we'll be using an excellent 3rd part library called `requests` to make light work of retrieving data from web APIs. To install the `requests` library, run this on your command line: + +```bash +(env) $ python -m pip install requests +``` + +This runs the `pip` module and asks it to find the `requests` library on PyPI.org (the Python Package Index) and install it in your local system, so that it becomes available for you to import. We'll dive a little more into this later. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/70-exercise.md b/website/content/02-introduction-to-python/175-running-code/70-exercise.md new file mode 100644 index 0000000..968ebe8 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/70-exercise.md @@ -0,0 +1,181 @@ +--- +title: "Practice" +date: 2019-03-08T00:00:00-08:00 +draft: false +weight: 70 +pre: "⭐️ " +--- + +## Running Code + +Let's create a basic program that we can run as a file on the command line. We'll start with a basic framework using a `main()` function. + +```python +def main(): + pass + +if __name__ == "__main__": + main() +``` + +Save your file as `file_exercise.py` and run it from the command line using `python file_exercise.py`. Note: we are concentrating on Python 3 for this class, so if you have Python 2 installed, you may need to explicitly use `python3 file_exercise.py`. + +What happened? Because you ran the file directly, the file's `__name__` variable is set to `__main__`, which triggers the `if` statement to run the `main()` function. This is a common pattern that you'll see in Python programs, and it comes in handy for being able to write programs that work both on their own and when imported into other programs. The `pass` keyword does nothing, it's just there to keep the empty `main()` function from throwing a syntax error. + +Let's start filling in our `main()` function. We have a json file named `cities.json` which contains the top five cities in the US, sorted by population. You can [download `cities.json` here](/code/cities.json). Let's open the file and load in the data. + +```python +import json + +def main(): + cities_file = open("cities.json") + cities_data = json.load(cities_file) + print(cities_data) + +if __name__ == "__main__": + main() +``` + +First, we imported the built-in `json` library to help us decode the json file. Then, we opened the file using the `open()` function, and passed the open file handle to the `json.load()` function. The `load()` function read our data in and spit it out as a Python representation - in this case, a list of dictionaries. We then print this list. + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python file_execise.py +[{'name': 'New York', 'pop': 8550405}, {'name': 'Los Angeles', 'pop': 3971883}, {'name': 'Chicago', 'pop': 2720546}, {'name': 'Houston', 'pop': 2296224}, {'name': 'Philadelphia', 'pop': 1567442}] +``` + +{{%/expand%}} + +This list is a little hard to make sense of in its raw form, let's print it a little nicer. Use `enumerate()` to go through the list and print it nicely: + +```python +import json + +def main(): + cities_file = open("cities.json") + cities_data = json.load(cities_file) + + print("Largest cities in the US by population:") + + for index, entry in enumerate(cities_data): + print(f"{index + 1}: {entry['name']} - {entry['pop']}") + +if __name__ == "__main__": + main() +``` + +A few new things here: first, remember that `enumerate()` outputs a tuple of (index, entry), so we use `index` and `entry` variables to capture those. Then, for every item in the list, we print the index (+ 1, because zero-indexed lists are sometimes hard to read), and we pull the name and population out of each entry dictionary using the dictionary `[]` syntax. + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python file_execise.py +Largest cities in the US by population: +1: New York - 8550405 +2: Los Angeles - 3971883 +3: Chicago - 2720546 +4: Houston - 2296224 +5: Philadelphia - 1567442 +``` + +{{%/expand%}} + +One more thing to clean up - using the `open()` keyword on its own is frowned upon, because it won't automatically close any resources you might open. Even if you call the `close()` keyword yourself, there's no guarantee your program won't crash, leaving important resources dangling. It's safer to open files inside a context using the `with` keyword. Once your code exits the scope of the context, your file is automatically closed. Note: our reading and formatting code has shifted to the right because of the change in scope. + +```python +import json + +def main(): + with open("cities.json") as cities_file: + cities_data = json.load(cities_file) + + print("Largest cities in the US by population:") + for index, entry in enumerate(cities_data): + print(f"{index + 1}: {entry['name']} - {entry['pop']}") + + print("The file is now closed.") + +if __name__ == "__main__": + main() +``` + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python file_execise.py +Largest cities in the US by population: +1: New York - 8550405 +2: Los Angeles - 3971883 +3: Chicago - 2720546 +4: Houston - 2296224 +5: Philadelphia - 1567442 +The file is now closed. +``` + +{{%/expand%}} + +## Handling Exceptions + +Parsing files - especially if you didn't create them - is often tricky, and you're going to have to deal with less-than-perfect data. For example, go into your `cities.json` file and delete the last `]` character. Run your program again. + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python file_execise.py +Traceback (most recent call last): + File "file_execise.py", line 14, in + main() + File "file_execise.py", line 5, in main + cities_data = json.load(cities_file) + File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py", line 296, in load + parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw) + File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py", line 348, in loads + return _default_decoder.decode(s) + File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/decoder.py", line 337, in decode + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/decoder.py", line 353, in raw_decode + obj, end = self.scan_once(s, idx) +json.decoder.JSONDecodeError: Expecting ',' delimiter: line 21 column 1 (char 234) +``` + +{{%/expand%}} + +Helpfully, the library told you (on the last line) approximately what is wrong and where. It also provides a Traceback to help you see what happened, starting with your `main()` function, which called `json.load(cities_file)`, and into the functions used internally to the `json` library. This will become more useful once you start writing your own libraries, so practice reading and understanding your Tracebacks. + +But let's say we're writing a web app or user-facing app and don't want our users to see Tracebacks (they can be scary if you're not a programmer, as well as risk your security by leaking information about your software). Let's catch that `JSONDecodeError` and return something prettier. + +```python +import json + +def main(): + with open("cities.json") as cities_file: + try: + cities_data = json.load(cities_file) + + print("Largest cities in the US by population:") + for index, entry in enumerate(cities_data): + print(f"{index + 1}: {entry['name']} - {entry['pop']}") + + except json.decoder.JSONDecodeError as error: + print("Sorry, there was an error decoding that json file:") + print(f"\t {error}") + + print("The file is now closed.") + +if __name__ == "__main__": + main() +``` + +Here, we've wrapped our business logic in another scope - the `try - except` block. For the `except`, we reach into the `json` library and reference the `JSONDecodeError` that's part of the `decoder` module. We assign it to `error` so that we can reference it later. We then print out the entire error, prefixed with a tab character `\t` to make it a little easier to read. Voilà, we've caught our error and reported it to the user with (hopefully) helpful information (but not too much information). + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python file_execise.py +Sorry, there was an error decoding that json file: + Expecting ',' delimiter: line 21 column 1 (char 234) +The file is now closed. +``` + +{{%/expand%}} \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/_index.md b/website/content/02-introduction-to-python/175-running-code/_index.md new file mode 100644 index 0000000..8a66e52 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Working With Python Programs" +date = 2019-01-25T15:05:49-06:00 +weight = 175 +chapter = true +draft = false +pre = "7. " ++++ + +### Chapter 7 + +# Working With Python Programs + +The REPL is an incredibly useful tool to quickly learn new Python concepts and test your assumptions. Unfortunately, it's not a great way to save or share our code. Let's learn how to save our Python code into project files, so we can run it again and again. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/exceptions.py b/website/content/02-introduction-to-python/175-running-code/code/exceptions.py new file mode 100644 index 0000000..ef87999 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/exceptions.py @@ -0,0 +1,6 @@ +try: + int("a") +except ValueError as error: + print(f"Something went wrong. Message: {error}") + +print("Reached end of the program.") \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/exceptions_one.py b/website/content/02-introduction-to-python/175-running-code/code/exceptions_one.py new file mode 100644 index 0000000..1f99016 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/exceptions_one.py @@ -0,0 +1,4 @@ +# this will throw an exception! +int("a") + +print("Reached end of the program.") \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/exceptions_two.py b/website/content/02-introduction-to-python/175-running-code/code/exceptions_two.py new file mode 100644 index 0000000..7a73e5b --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/exceptions_two.py @@ -0,0 +1,6 @@ +try: + int("a") +except ValueError: + print("Oops, couldn't convert that value into an int!") + +print("Reached end of the program.") \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/formatting_example.py b/website/content/02-introduction-to-python/175-running-code/code/formatting_example.py new file mode 100644 index 0000000..e75847f --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/formatting_example.py @@ -0,0 +1,10 @@ +# Use \n to add a new line, before, in the middle of, or after a string. +print("\nExtra New Line Before") +print("One Print\nTwo New Lines!") +print("Extra New Line After\n") + +# Use \t to add a tab. +print("\t Here's some tabbed output.") + +# Or, combine both! +print("\nOne Print\n\tOne Tab") \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/hello.py b/website/content/02-introduction-to-python/175-running-code/code/hello.py new file mode 100644 index 0000000..5ca4f59 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/hello.py @@ -0,0 +1,4 @@ +greetings = ["Hello", "Bonjour", "Hola"] + +for greeting in greetings: + print(f"{greeting}, World!") \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/mystery.py b/website/content/02-introduction-to-python/175-running-code/code/mystery.py new file mode 100644 index 0000000..df148bb --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/mystery.py @@ -0,0 +1,17 @@ +def mystery(): + num = 10 * 3 + + if num == 10: + print("Num was equal to 10") + num = num * 10 + if num == 20: + print("Num was equal to 20") + num = num * 20 + if num == 30: + print("Num was equal to 30") + num = num * 30 + + print(f"Value of returned num is: {num}") + return num + +mystery() \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/name.py b/website/content/02-introduction-to-python/175-running-code/code/name.py new file mode 100644 index 0000000..d3c35e4 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/name.py @@ -0,0 +1,2 @@ +name = "Nina" +name \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/name_lib.py b/website/content/02-introduction-to-python/175-running-code/code/name_lib.py new file mode 100644 index 0000000..5c30667 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/name_lib.py @@ -0,0 +1,15 @@ +def name_length(name): + return len(name) + +def upper_case_name(name): + return name.upper() + +def lower_case_name(name): + return name.lower() + +name = "Nina" +length = name_length(name) +upper_case = upper_case_name(name) + +print(f"The length is {length} and the uppercase version is: {upper_case}") +# print(f"The value of __name__ is: {__name__}") \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/name_lib_main.py b/website/content/02-introduction-to-python/175-running-code/code/name_lib_main.py new file mode 100644 index 0000000..480ee99 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/name_lib_main.py @@ -0,0 +1,15 @@ +def name_length(name): + return len(name) + +def upper_case_name(name): + return name.upper() + +def lower_case_name(name): + return name.lower() + +if __name__ == "__main__": + name = "Nina" + length = name_length(name) + upper_case = upper_case_name(name) + + print(f"The length is {length} and the uppercase version is: {upper_case}") \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/other_program.py b/website/content/02-introduction-to-python/175-running-code/code/other_program.py new file mode 100644 index 0000000..5300928 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/other_program.py @@ -0,0 +1,8 @@ +import name_lib + +my_name = "Fred" + +my_length = name_lib.name_length(my_name) +my_lower_case = name_lib.lower_case_name(my_name) + +print(f"In my code, my length is {my_length} and my lower case name is: {my_lower_case}") \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/other_program_main.py b/website/content/02-introduction-to-python/175-running-code/code/other_program_main.py new file mode 100644 index 0000000..f7817b8 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/other_program_main.py @@ -0,0 +1,8 @@ +import name_lib_main + +my_name = "Fred" + +my_length = name_lib_main.name_length(my_name) +my_lower_case = name_lib_main.lower_case_name(my_name) + +print(f"In my code, my length is {my_length} and my lower case name is: {my_lower_case}") \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/code/print_name.py b/website/content/02-introduction-to-python/175-running-code/code/print_name.py new file mode 100644 index 0000000..bd85767 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/code/print_name.py @@ -0,0 +1,2 @@ +name = "Nina" +print(name) \ No newline at end of file diff --git a/website/content/02-introduction-to-python/175-running-code/images/.gitattributes b/website/content/02-introduction-to-python/175-running-code/images/.gitattributes new file mode 100644 index 0000000..24a8e87 --- /dev/null +++ b/website/content/02-introduction-to-python/175-running-code/images/.gitattributes @@ -0,0 +1 @@ +*.png filter=lfs diff=lfs merge=lfs -text diff --git a/website/content/02-introduction-to-python/175-running-code/images/terminal-drop-down-select.png b/website/content/02-introduction-to-python/175-running-code/images/terminal-drop-down-select.png new file mode 100644 index 0000000..91d8f43 Binary files /dev/null and b/website/content/02-introduction-to-python/175-running-code/images/terminal-drop-down-select.png differ diff --git a/website/content/02-introduction-to-python/175-running-code/images/terminal-drop-down.png b/website/content/02-introduction-to-python/175-running-code/images/terminal-drop-down.png new file mode 100644 index 0000000..c92a052 Binary files /dev/null and b/website/content/02-introduction-to-python/175-running-code/images/terminal-drop-down.png differ diff --git a/website/content/02-introduction-to-python/175-running-code/images/vs-code-run-file-command-palette.png b/website/content/02-introduction-to-python/175-running-code/images/vs-code-run-file-command-palette.png new file mode 100644 index 0000000..48ab7c2 Binary files /dev/null and b/website/content/02-introduction-to-python/175-running-code/images/vs-code-run-file-command-palette.png differ diff --git a/website/content/02-introduction-to-python/190-APIs/00-requests-and-responses.md b/website/content/02-introduction-to-python/190-APIs/00-requests-and-responses.md new file mode 100644 index 0000000..a5e12fa --- /dev/null +++ b/website/content/02-introduction-to-python/190-APIs/00-requests-and-responses.md @@ -0,0 +1,113 @@ +--- +title: "Working with APIs" +date: 2019-03-16T21:38:49-07:00 +draft: false +weight: 2 +--- + +{{% notice note %}} +Zapier has an *excellent* intro to APIs by by Brian Cooksey. The images and diagrams used on this page is from his post. Please read it [here](https://zapier.com/learn/apis/chapter-1-introduction-to-apis/). +{{% /notice %}} + + +### Requests and Responses + +Working with APIs using HTTP depends on the request and response cycle. You send a request to the server, and it lets you know if your request was successful or by sending an HTTP Status Code with a special meaning, and will optionally send back data. + +![](/02-introduction-to-python/190-APIs/images/request-response.jpeg?width=40pc) + +### HTTP Methods + +The HTTP Method (or verb) is how you tell the server which *type* of operation you'd like to perform. + +- `GET` - Ask the server to get a resource. +- `POST` - Ask the server to create a resources, with the data that you've provided. +- `PUT` - Edit or update a resource. +- `DELETE` - Delete a resource. + +### Headers, Body, Parameters + +We can add additional to our request through headers, a body or URL parameters. + +For today, we'll only be using URL parameters. They look like `https://example.com?var1=foo&var2=bar`. + +### Response Types + +Servers can respond in a variety of file formats, like JSON, XML, and others. + +These days, JSON is the most common format. For many servers, it's the default if you don't specify a response type. + +JSON is a common format of capturing data, and it's easy to read and generate from a variety of programming languages. + +A JSON example: + +```json +[ + { + "name": "New York", + "pop": 8550405 + }, + { + "name": "Los Angeles", + "pop": 3971883 + }, + { + "name": "Chicago", + "pop": 2720546 + }, + { + "name": "Houston", + "pop": 2296224 + }, + { + "name": "Philadelphia", + "pop": 1567442 + } +] +``` + + +### HTTP Status Codes + +HTTP Status Codes is a numerical response from the sever, indicating the status of your request. + +They tend to fall into these categories: + +- **`1xx` : Informational** + - Not commonly used. +- **`2xx` : Success** + - **`200 OK`** - Standard response for successful HTTP requests. + - **`201 CREATED`** - A new resource was created successfully. +- **`3xx` : Redirection** + - **`301 Moved Permanently`** - This and all future requests should be directed to the new URL. +- **`4xx` : A Client Error** + - **`404 Not Found`** - An entry wasn't found based on the information the client gave. +- **`5xx` : A Server Error** + - **`500 Internal Server Error`** - Something went wrong with the server.\ + + +#### Because HTTP Status Codes are boring, we can try to remember them with cats instead! + +| code | cat | +|--------------------------------- |------------------------------------------------------------------------------------------- | +| **`200: OK`** | ![](/02-introduction-to-python/190-APIs/images/200.jpeg?classes=shadow&outline&width=20pc) | +| **`301 Moved Permanently`** | ![](/02-introduction-to-python/190-APIs/images/301.jpeg?classes=shadow&outline&width=20pc) | +| **`404 Not Found`** | ![](/02-introduction-to-python/190-APIs/images/404.jpeg?classes=shadow&outline&width=20pc) | +| **`500 Internal Server Error`** | ![](/02-introduction-to-python/190-APIs/images/500.jpeg?classes=shadow&outline&width=20pc) | + +Provided by [this awesome API](https://http.cat/). + +#### Easter Egg - I'm a Teapot + +One of my favorite bits of internet trivia is that in 1998 as an April fool's joke, HTTP Status Code `418 I'm a teapot`, a.k.a the Hyper Text Coffee Pot Control Protocol was implemented. You can pass the `BREW` command to it, to signal starting brewing a cup of coffee. + +![](/02-introduction-to-python/190-APIs/images/Htcpcp_teapot.jpg?classes=shadow&outline&width=30pc) + +### Authentication + +There are several ways of authenticating to APIs, but they are out of the scope of this class. + +To learn more about authentication, visit: + +- (Concepts) Zapier - [Authentication Part 1](https://zapier.com/learn/apis/chapter-4-authentication-part-1/) and[Authentication Part 2](https://zapier.com/learn/apis/chapter-5-authentication-part-2/) by Brian Cooksey +- Using [authentication in the `requests` library](http://docs.python-requests.org/en/master/user/authentication/) documentation. \ No newline at end of file diff --git a/website/content/02-introduction-to-python/190-APIs/00-the-requests-library.md b/website/content/02-introduction-to-python/190-APIs/00-the-requests-library.md new file mode 100644 index 0000000..1822107 --- /dev/null +++ b/website/content/02-introduction-to-python/190-APIs/00-the-requests-library.md @@ -0,0 +1,99 @@ +--- +title: "Using The Requests Library" +date: 2019-03-16T21:38:59-07:00 +weight: 3 +--- + +### What is The `requests` Library? + +Python is "batteries included", but sometimes the included libraries available in the standard library can be hard to understand. The standard library focuses on functionality, but not necessarily ease of use. + +That's where external libraries come in. The external `requests` library was developed by Kenneth Reitz to make working with APIs in Python a lot easier. He calls it "HTTP, for humans." It's become of the (if not the most) popular Python library! + +### Our First Request With The `requests` Library + +{{% notice tip %}} +If you didn't install the `requests` library in the working with libraries chapter, you'll need to do that first before running this code. Use: `python -m pip install requests` +{{% /notice %}} + +Let's make a request to shibe.online to get ourselves some dog pictures. Create a file called `shibe.py` in our `pyworkshop` directory and copy this: + +```python +# First thing we'll do is import the requests library +import requests + +# Define a variable with the URL of the API +api_url = "http://shibe.online/api/shibes?count=1" + +# Call the root of the api with GET, store the answer in a response variable +# This call will return a list of URLs that represent dog pictures +response = requests.get(api_url) + +# Get the status code for the response. Should be 200 OK +# Which means everything worked as expected +print(f"Response status code is: {response.status_code}") + +# Get the result as JSON +response_json = response.json() + +# Print it. We should see a list with one image URL. +print(response_json) +``` + +```bash +(env) $ python shibe.py +Response status code is: 200 +['https://cdn.shibe.online/shibes/28d7c372ea7defdb315ef845285d4ac3906ccea4.jpg'] +``` + +### Dealing with Errors + +When dealing with HTTP requests, your first indication of error is usually the HTTP status code. You saw some of the common status codes in the last chapter. The most common status codes are probably `200` - Success, and `404` - Not found. You can find the status code in the `status_code` property of the `response` object: + +```python +# Passing in a non-existant URL will result in a 404 (not found) +bad_response = requests.get("http://shibe.online/api/german-shepards") +print(f"Bad Response Status Code is: {bad_response.status_code}") # Status code is 404, meaning that resource doesn’t exist. +``` + +### Passing in Parameters + +```python +# We'll store our base URL here and pass in the count parameter later +api_url = "http://shibe.online/api/shibes" + +params = { + "count": 10 +} + +# Pass those params in with the request. +api_response = requests.get(api_url, params=params) + +print(f"Shibe API Response Status Code is: {api_response.status_code}") # should be 200 OK + +json_data = api_response.json() + +print("Here is a list of URLs for dog pictures:") +for url in json_data: + print(f"\t {url}") +``` + +```bash +$ shibe.py +Shibe API Response Status Code is: 200 +Here is a list of URLs for dog pictures: + https://cdn.shibe.online/shibes/dfb2af0b2ac1f057750da32f0ea0e154afc160cf.jpg + https://cdn.shibe.online/shibes/4989daad2c805ec62b0fb09a80280ba2262f1b08.jpg + https://cdn.shibe.online/shibes/a9360b8262c586af2cf53a2d68bb6ec34b87fe25.jpg + https://cdn.shibe.online/shibes/a168cc7f2524c73b433afd7c02f698884738daff.jpg + https://cdn.shibe.online/shibes/3fbe49908948718c521b756f31dc155ed22941f6.jpg + https://cdn.shibe.online/shibes/846bb52389cf9af8a54eb12f48e0e7d0883b17da.jpg + https://cdn.shibe.online/shibes/d11ed7f57c5a882f047b921a73f0b95714626bb3.jpg + https://cdn.shibe.online/shibes/0fd1dcc9f5866cefaa3040de1be0f8971b0530cd.jpg + https://cdn.shibe.online/shibes/cd668ca05d0ec78863f3c30b08b9cd4ff7f5669c.jpg + https://cdn.shibe.online/shibes/32bf0797e5a4c5bfb6fc06edc57ddfbf4e08f98f.jpg +``` + +#### More about `requests` + +To learn more about the `requests` library *after* class, look at the [quick start](http://docs.python-requests.org/en/master/user/quickstart/). diff --git a/website/content/02-introduction-to-python/190-APIs/00-what-is-an-API.md b/website/content/02-introduction-to-python/190-APIs/00-what-is-an-API.md new file mode 100644 index 0000000..ed4246f --- /dev/null +++ b/website/content/02-introduction-to-python/190-APIs/00-what-is-an-API.md @@ -0,0 +1,31 @@ +--- +title: "What Is an API?" +date: 2019-03-16T21:38:31-07:00 +draft: false +weight: 1 +--- + +Per the dictionary, an API is: + +> a set of functions and procedures allowing the creation of applications that access the features or data of an operating system, application, or other service. + +An API is a standardized way of accessing information across the web, between clients and servers. These days most APIs are *RESTful*. That means they follow a common set of paradigms and practices. + +There are many types of APIs, but these days they're commonly known to refer to *web* APIs. + +#### Authentication + +Some, but not all APIs require you to authenticate. Methods of authentication are out of the scope of this class, but you'll be happy to know that there are plenty of free APIs available that require no authentication at all. + +#### Rate Limiting + +Some APIs allow unauthenticated requests, but they're usually *rate limited*. Rate limiting means prevent the same client (usually by IP address) from making too many requests at once and overloading the server. *The GitHub API allows 50 unauthenticated requests per hour per IP, or 10 unauthenticated requests to their Search API.* + +Note: After the class, you can find a detailed list of APIs in this [public-apis repo](https://github.com/toddmotto/public-apis). + +#### Free APIs + +{{% notice note %}} +Free APIs are... free. That means that they may go down if their owner decides to drop their upkeep. If the API used in these examples doesn't work in the future, try a different one listed in the `public-apis` repo linked to above. +{{% /notice %}} + diff --git a/website/content/02-introduction-to-python/190-APIs/_index.md b/website/content/02-introduction-to-python/190-APIs/_index.md new file mode 100644 index 0000000..37df93c --- /dev/null +++ b/website/content/02-introduction-to-python/190-APIs/_index.md @@ -0,0 +1,14 @@ ++++ +title = "APIs" +date = 2019-01-25T15:06:53-06:00 +weight = 190 +chapter = true +draft = false +pre = "8. " ++++ + +### Chapter 8 + +# Working with APIs + +How do we interact with APIs from Python? \ No newline at end of file diff --git a/website/content/02-introduction-to-python/190-APIs/final-exercise.md b/website/content/02-introduction-to-python/190-APIs/final-exercise.md new file mode 100644 index 0000000..ac47c18 --- /dev/null +++ b/website/content/02-introduction-to-python/190-APIs/final-exercise.md @@ -0,0 +1,344 @@ ++++ +title = "Practice" +date = 2019-01-25T15:07:04-06:00 +weight = 200 +draft = false +pre = "⭐️ " ++++ + +Let's review what we learned today and put it all together. + +For the final exercise of today, we're going to write a small program that requests the top repositories from GitHub, ordered by the number of stars each repository has, then we're going to print the results to our terminal. Create a new file called `day_one.py`. + +{{% notice note %}} +You may need to install the `requests` library using `python -m pip install requests`. You may see `pip` used directly, but using `python -m pip` is [recommended by Python](https://docs.python.org/3/installing/index.html). +{{% /notice %}} + +Let's start with our key function, the one that gets the data from the [GitHub API](https://developer.github.com/v3/search/). Use the `requests` library to do a GET request on the GitHub search API URL ("https://api.github.com/search/repositories"). Use `if __name__ == "__main__"` to check to make sure we're running the file directly, and to call our function. Don't forget to `import requests` + +{{%expand "You should have something like this:" %}} + +```python +import requests + +def repos_with_most_stars(): + gh_api_repo_search_url = "https://api.github.com/search/repositories" + + response = requests.get(gh_api_repo_search_url) + + print(response.text) + + +if __name__ == "__main__": + repos_with_most_stars() +``` + +{{%/expand%}} + + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python day_one.py +{"message":"Validation Failed","errors":[{"resource":"Search","field":"q","code":"missing"}],"documentation_url":"https://developer.github.com/v3/search"} +``` + +{{%/expand%}} + +### Getting a Response + +Looks like we got a response from the GitHub API! Looks like we hit an error - we're missing search parameter. Checking the `documentation_url` that GitHub helpfully provides, we can see that we're missing the parameter `q`, which contains search keywords. Let's hardcode a query string to find repos with more than 50,000 stars and try again. We'll add our query string to the `parameters` dict as `q`, and pass it to the `params` argument of `requests.get()` + +{{%expand "You should have something like this:" %}} + +```python +import requests + +def repos_with_most_stars(): + gh_api_repo_search_url = "https://api.github.com/search/repositories" + + parameters = {"q": "stars:>50000"} + response = requests.get(gh_api_repo_search_url, params=parameters) + + print(response.text) + + +if __name__ == "__main__": + repos_with_most_stars() +``` + +{{%/expand%}} + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python day_one.py +{"total_count":33,"incomplete_results":false,"items":[{"id":28457823,"node_id":"MDEwOlJlcG9zaXRvcnkyODQ1NzgyMw==","name":"freeCodeCamp"... +``` +{{%/expand%}} + + +### Response Parsing + +Woah, we got a huge response from GitHub, including metadata for 33 repos. Let's parse it out so we can make better sense of what we have - use `response.json()` to get the returned data in json format. We see that GitHub returns a list called `items` in our response, so let's `return` that. Then, in your main function, loop through it and print out the important bits. + +{{%expand "You should have something like this:" %}} + +```python +import requests + +def repos_with_most_stars(): + gh_api_repo_search_url = "https://api.github.com/search/repositories" + + parameters = {"q": "stars:>50000"} + response = requests.get(gh_api_repo_search_url, params=parameters) + response_json = response.json() + + return response_json["items"] + + +if __name__ == "__main__": + results = repos_with_most_stars() + + for result in results: + language = result["language"] + stars = result["stargazers_count"] + name = result["name"] + + print(f"-> {name} is a {language} repo with {stars} stars.") +``` +{{%/expand%}} + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python day_one.py +-> freeCodeCamp is a JavaScript repo with 298059 stars. +-> bootstrap is a JavaScript repo with 131410 stars. +-> vue is a JavaScript repo with 130168 stars. +-> react is a JavaScript repo with 124029 stars. +-> tensorflow is a C++ repo with 122328 stars. +-> free-programming-books is a None repo with 118241 stars. +-> awesome is a None repo with 103392 stars. +-> You-Dont-Know-JS is a None repo with 97587 stars. +... +``` + +{{%/expand%}} + + +### Narrowing it Down + +We should now have a much more readable list of 33 or so repos, along with their number of stars. Let's narrow down our search a bit. To use multiple search keywords, we'll have to programatically construct our query string. Using the GitHub API documentation, let's make a new function to construct a query string for the repository search endpoint that searches for any number of languages, and limits our query to repos with more than 50,000 stars: + +{{%expand "You should have something like this:" %}} + +```python +def create_query(languages, min_stars=50000): + # An example search query looks like: + # stars:>50000 language:python language:javascript + + query = f"stars:>{min_stars} " + + for language in languages: + query += f"language:{language} " + + return query +``` + +{{%/expand%}} + + +Now, let's call our new `create_query()` function from `repos_with_most_stars()`, replacing our hardcoded query string. Add a `languages` argument so that we can pass in a list of languages to use to create our query. Also add `sort` and `order` parameters, which we'll hardcode to "stars" and "desc" for now. + +{{%expand "You should have something like this:" %}} + +```python +def repos_with_most_stars(languages): + gh_api_repo_search_url = "https://api.github.com/search/repositories" + + query = create_query(languages) + sort = "stars" + order = "desc" + parameters = {"q": query, "sort": sort, "order": order} + + response = requests.get(gh_api_repo_search_url, params=parameters) + response_json = response.json() + + return response_json["items"] +``` + +{{%/expand%}} + +Finally, let's add a `languages` list to limit which languages we're interested in, and pass it to `repos_with_most_stars()`. Now, when we call our `repos_with_most_stars()` function with `["python", "javascript", "ruby"]` as our languages, the `create_query()` function will output create a query string that looks like `q=stars:>50000+language:python+language:javascript+language:ruby+&sort=stars&order=desc`. Because this is a simple GET request, this gets appended to our `gh_api_repo_search_url`, so our actual request URL is `https://api.github.com/search/repositories?q=stars:>50000+language:python+language:javascript+language:ruby+&sort=stars&order=desc`. + +Run your program. + +{{%expand "You should have something like this:" %}} + +```python +if __name__ == "__main__": + languages = ["python", "javascript", "ruby"] + results = repos_with_most_stars(languages) + + for result in results: + language = result["language"] + stars = result["stargazers_count"] + name = result["name"] + + print(f"-> {name} is a {language} repo with {stars} stars.") +``` + +{{%/expand%}} + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python day_one.py +-> freeCodeCamp is a JavaScript repo with 298059 stars. +-> bootstrap is a JavaScript repo with 131410 stars. +-> vue is a JavaScript repo with 130169 stars. +-> react is a JavaScript repo with 124029 stars. +-> d3 is a JavaScript repo with 82945 stars. +-> javascript is a JavaScript repo with 82531 stars. +-> react-native is a JavaScript repo with 74828 stars. +-> create-react-app is a JavaScript repo with 64748 stars. +-> awesome-python is a Python repo with 63734 stars. +-> angular.js is a JavaScript repo with 59413 stars. +-> Font-Awesome is a JavaScript repo with 59051 stars. +-> system-design-primer is a Python repo with 58972 stars. +-> node is a JavaScript repo with 58863 stars. +-> axios is a JavaScript repo with 56121 stars. +-> public-apis is a Python repo with 53212 stars. +-> jquery is a JavaScript repo with 51040 stars. +``` + +{{%/expand%}} + +### Cleaning Up and Handling Errors + +Looking good, we now have a sorted list of the top python, javascript, and ruby repos. Let's do a little bit of clean up and error handling. We might not always want to sort by "stars" or order by "desc", so move those to keyword arguments. That way, they'll be good defaults, but if someone calling our `repos_with_most_stars` function wants to override them, they can. + +{{%expand "You should have something like this:" %}} + +```python +def repos_with_most_stars(languages, sort="stars", order="desc"): + gh_api_repo_search_url = "https://api.github.com/search/repositories" + + query = create_query(languages) + parameters = {"q": query, "sort": sort, "order": order} + + response = requests.get(gh_api_repo_search_url, params=parameters) + response_json = response.json() + + return response_json["items"] +``` + +{{%/expand%}} + +Next, we should handle any errors we might run into with the API. Maybe you've gotten one already. Let's add some basic error handling on the response's HTTP status code. We'll check for a `403`, a common error that GitHub uses to tell you that you're hitting their API too quickly, and `raise` and error. We'll also `raise` an error if the status code is anything but `200` (success). + +{{%expand "You should have something like this:" %}} + +```python +def repos_with_most_stars(languages, sort="stars", order="desc"): + gh_api_repo_search_url = "https://api.github.com/search/repositories" + query = create_query(languages) + + # Define the parameters we want to be part of our URL + parameters = {"q": query, "sort": sort, "order": order} + + # Pass in the query and the parameters as part of the request. + response = requests.get("https://api.github.com/search/repositories", params=parameters) + status_code = response.status_code + + # Check if the rate limit was hit. Applies only for students running this code + # in the in-person course. + if status_code == 403: + raise RuntimeError("Rate limit reached. Please wait a minute and try again.") + if status_code != 200: + raise RuntimeError(f"An error occurred. HTTP Status Code was: {status_code}.") + else: + response_json = response.json() + records = response_json["items"] + return records +``` +{{%/expand%}} + +There, your code should do the same thing, but should handle errors much better. + +{{%expand "The final code, with additional comments, can be found here:" %}} + + +```python +""" +A small Python program that uses the GitHub search API to list +the top projects by language, based on stars. + +GitHub Search API documentation: https://developer.github.com/v3/search/ + +Additional parameters for searching repos can be found here: +https://help.github.com/en/articles/searching-for-repositories#search-by-number-of-stars + +Note: The API will return results found before a timeout occurs, +so results may not be the same across requests, even with the same query. + +Requests to this endpoint are rate limited to 10 requests per +minute per IP address. +""" + +import requests + + +def create_query(languages, min_stars=50000): + """ + Create the query string for the GitHub search API, + based on the minimum amount of stars for a project, and + the provided programming languages. + + An example search query looks like: + stars:>50000 language:python language:javascript + """ + query = f"stars:>{min_stars} " + + for language in languages: + query += f"language:{language} " + + return query + + +def repos_with_most_stars(languages, sort="stars", order="desc"): + gh_api_repo_search_url = "https://api.github.com/search/repositories" + query = create_query(languages) + + # Define the parameters we want to be part of our URL + parameters = {"q": query, "sort": sort, "order": order} + + # Pass in the query and the parameters as part of the request. + response = requests.get(gh_api_repo_search_url, params=parameters) + status_code = response.status_code + + # Check if the rate limit was hit. Applies only for students running this code + # in the in-person course. + if status_code == 403: + raise RuntimeError("Rate limit reached. Please wait a minute and try again.") + if status_code != 200: + raise RuntimeError(f"An error occurred. HTTP Status Code was: {status_code}.") + else: + response_json = response.json() + records = response_json["items"] + return records + + +if __name__ == "__main__": + languages = ["python", "javascript", "ruby"] + results = repos_with_most_stars(languages) + + for result in results: + language = result["language"] + stars = result["stargazers_count"] + name = result["name"] + + print(f"-> {name} is a {language} repo with {stars} stars.") +``` +{{%/expand%}} diff --git a/website/content/02-introduction-to-python/190-APIs/images/200.jpeg b/website/content/02-introduction-to-python/190-APIs/images/200.jpeg new file mode 100644 index 0000000..504bd49 Binary files /dev/null and b/website/content/02-introduction-to-python/190-APIs/images/200.jpeg differ diff --git a/website/content/02-introduction-to-python/190-APIs/images/301.jpeg b/website/content/02-introduction-to-python/190-APIs/images/301.jpeg new file mode 100644 index 0000000..ba7927d Binary files /dev/null and b/website/content/02-introduction-to-python/190-APIs/images/301.jpeg differ diff --git a/website/content/02-introduction-to-python/190-APIs/images/404.jpeg b/website/content/02-introduction-to-python/190-APIs/images/404.jpeg new file mode 100644 index 0000000..beab571 Binary files /dev/null and b/website/content/02-introduction-to-python/190-APIs/images/404.jpeg differ diff --git a/website/content/02-introduction-to-python/190-APIs/images/500.jpeg b/website/content/02-introduction-to-python/190-APIs/images/500.jpeg new file mode 100644 index 0000000..266681f Binary files /dev/null and b/website/content/02-introduction-to-python/190-APIs/images/500.jpeg differ diff --git a/website/content/02-introduction-to-python/190-APIs/images/Htcpcp_teapot.jpg b/website/content/02-introduction-to-python/190-APIs/images/Htcpcp_teapot.jpg new file mode 100644 index 0000000..b6168b7 Binary files /dev/null and b/website/content/02-introduction-to-python/190-APIs/images/Htcpcp_teapot.jpg differ diff --git a/website/content/02-introduction-to-python/190-APIs/images/request-response.jpeg b/website/content/02-introduction-to-python/190-APIs/images/request-response.jpeg new file mode 100644 index 0000000..9a1d293 Binary files /dev/null and b/website/content/02-introduction-to-python/190-APIs/images/request-response.jpeg differ diff --git a/website/content/02-introduction-to-python/200-conclusion/00-wrapping-up.md b/website/content/02-introduction-to-python/200-conclusion/00-wrapping-up.md new file mode 100644 index 0000000..6c9eb53 --- /dev/null +++ b/website/content/02-introduction-to-python/200-conclusion/00-wrapping-up.md @@ -0,0 +1,17 @@ +--- +title: "Wrapping Up" +date: 2019-02-10T18:39:32-08:00 +draft: false +--- + +Today you've gotten a whirlwind tour of the Python programming language. + +During Day 2, we're going to put our knowledge to work writing real programs. + +## Source Control + +I highly recommend that you use source control while working on your Python projects. GitHub is a popular and free option. + +Source control lets you track changes to your project. You can use that to your advantage to commit early and often and track changes to your project. + +If you're not familiar with how to use git or GitHub, you can watch my [Git In-depth Frontend Masters class](https://frontendmasters.com/courses/git-in-depth/). For Python projects, make sure that you use the correct `.gitignore` file. GitHub provides a [free template that you can use](https://github.com/github/gitignore/blob/master/Python.gitignore). diff --git a/website/content/02-introduction-to-python/_index.md b/website/content/02-introduction-to-python/_index.md new file mode 100644 index 0000000..d3a3034 --- /dev/null +++ b/website/content/02-introduction-to-python/_index.md @@ -0,0 +1,28 @@ +--- +pre: "Day 1. " +title: "Intro to Python" +date: 2019-01-24T08:21:04-08:00 +draft: false +chapter: true +weight: 2 +--- + +### Day 1 + +# An Introduction to Python + +Let's talk about the anatomy of a Python program, along with Python best practices like naming and commenting. + +Today you'll be learning all the foundational basics of Python. How to work with different data types, the Python control structures, best practices, and lastly an exercise in interacting with APIs. + +# Day 1 Overview + +By coding along with me in this workshop, you'll: + +* You’ll learn about the origins of Python, and how it can help you solve complex problems quickly. +* Learn about the different data types Python has to offer, including tips on when and how to use each one. +* Understand the control flow of Python programs - loops, boolean statements, if statements, and return statements. +* Make your code more concise by writing reusable functions. +* Learn to use Python packages from the standard library, as well as how to find and install external libraries. +* Learn how to work with files on your filesystem by reading and writing to them. +* Write programs that interact with APIs by sending requests and receiving responses. \ No newline at end of file diff --git a/website/content/03-intermediate-python/00-final-project-preview.md b/website/content/03-intermediate-python/00-final-project-preview.md new file mode 100644 index 0000000..734fbd1 --- /dev/null +++ b/website/content/03-intermediate-python/00-final-project-preview.md @@ -0,0 +1,10 @@ ++++ +title = "Final Project Preview" +date = 2019-03-03T11:56:24-08:00 +weight = 1 +draft = false ++++ + +### Final Project Preview + +Our final project on Day 2 will involve creating a simple Flask web application from our Day 1 final exercise. \ No newline at end of file diff --git a/website/content/03-intermediate-python/10-introduction/00-getting-ready.md b/website/content/03-intermediate-python/10-introduction/00-getting-ready.md new file mode 100644 index 0000000..b31f4e6 --- /dev/null +++ b/website/content/03-intermediate-python/10-introduction/00-getting-ready.md @@ -0,0 +1,41 @@ ++++ +title = "Getting Ready" +date = 2019-03-03T11:56:24-08:00 +weight = 1 +draft = false ++++ + +### Get Ready For Day 2 - Navigate To Your Project + +Building on the project structure from Day 1, enter your `~/pyworkshop` directory, and start your already existing virtual environment. + +Windows: + +```powershell +> cd $home +> cd pyworkshop +> env\scripts\activate +``` + +Mac: + +```bash +$ cd ~/pyworkshop +$ source env/bin/activate +``` + +### Open VS Code + +If VS Code isn't already open, type the following in the same PowerShell or Terminal window where you activated your virtual environment. + +```text +$ code . +``` + +### Start The REPL + +Open the Command Palette in VS Code, and type: "Python: Start REPL" + +{{% notice tip %}} +Remember, open the VS Code command palette (cmd + shift + P on Mac, ctrl + shift + P on Windows) and +{{% /notice %}} diff --git a/website/content/03-intermediate-python/10-introduction/00-python-philosophy.md b/website/content/03-intermediate-python/10-introduction/00-python-philosophy.md new file mode 100644 index 0000000..d5ec497 --- /dev/null +++ b/website/content/03-intermediate-python/10-introduction/00-python-philosophy.md @@ -0,0 +1,53 @@ ++++ +title = "Python Philosophy" +date = 2019-03-03T11:56:24-08:00 +weight = 3 +draft = false ++++ + +The Zen of Python is a collection of 19 software principles written in a poem that influences the design of Python Programming Language. It was published on the Python mailing list in June 1999 by Tim Peters. [*](https://en.wikipedia.org/wiki/Zen_of_Python) + +### Zen of Python in The Python Interpreter + +The Zen of Python is included as an easter egg in the Python REPL. You can read it by typing `import this` in our REPL, to learn a little more about the principles and philosophy behind Python. + +```python +>>> import this +The Zen of Python, by Tim Peters + +Beautiful is better than ugly. +Explicit is better than implicit. +Simple is better than complex. +Complex is better than complicated. +Flat is better than nested. +Sparse is better than dense. +Readability counts. +``` + +#### Simple is Better Than Complex + +Generally, Python programmers prefer to be explicit and write *simple*, *understandable*, and *maintainable* code instead of ego flexing and writing unnecessarily complex code. + +#### Readability Counts + +Make your code easy to read. Avoid single character variable names. Call your functions with named parameters where applicable. Use good variable names. + +#### More Easter Eggs + +{{% notice note %}} +To see another Python easter egg, type the following into your REPL: `from __future__ import braces` +{{% /notice %}} + +{{%expand "You'll see the following: 🤣" %}} +```python +>>> from __future__ import braces + File "", line 1 +SyntaxError: not a chance +``` +{{% /expand%}} + +{{% notice note %}} +My favorite Easter Egg? Type `import antigravity` into the REPL. +{{% /notice %}} + +![](/03-intermediate-python/10-introduction/images/python.png) diff --git a/website/content/03-intermediate-python/10-introduction/15-converting-between-types/04-string-operations.md b/website/content/03-intermediate-python/10-introduction/15-converting-between-types/04-string-operations.md new file mode 100644 index 0000000..c39c3dc --- /dev/null +++ b/website/content/03-intermediate-python/10-introduction/15-converting-between-types/04-string-operations.md @@ -0,0 +1,23 @@ +--- +title: "String Operations" +date: 2019-02-10T18:30:43-08:00 +draft: false +weight: 4 +--- + +### Split and Join + +Strings have two functions for splitting and joining - `split()` and `join()`. Calling `split()` on a string will split the string into a list, creating a new element for every instance of the character(s) you pass in. `join()` accepts a list of strings, and uses the string you call it on to join the list together into one string. For example: + +```python +>>> my_data = "this,is,comma,separated,data" +>>> my_data = my_data.split(",") +>>> print(my_data) +['this', 'is', 'comma', 'separated', 'data'] + +>>> ":".join(my_data) +'this:is:comma:separated:data' + +>>> ", ".join(my_data) +'this, is, comma, separated, data' +``` \ No newline at end of file diff --git a/website/content/03-intermediate-python/10-introduction/15-converting-between-types/10-exercise.md b/website/content/03-intermediate-python/10-introduction/15-converting-between-types/10-exercise.md new file mode 100644 index 0000000..861fde6 --- /dev/null +++ b/website/content/03-intermediate-python/10-introduction/15-converting-between-types/10-exercise.md @@ -0,0 +1,120 @@ +--- +title: "Practice" +date: 2019-03-08T00:00:00-08:00 +draft: false +weight: 10 +pre: "⭐️ " +--- + +## Converting Between Types + +Converting between types in Python is one of the most powerful language features. + +You can quickly convert between strings, numbers, and various data-types to supercharge quickly solving problems. +You can even use powerful data structures like sets to your advantage. + +## Converting Between Numbers and Strings + +Converting between numbers and strings is easy with `str()` and `int()`: + +```python +>>> my_string = str(100) +>>> my_string +>>> type(my_string) +>>> my_int = int(my_string) +>>> my_int +>>> type(my_int) +``` + +You can also use `float()` to convert strings into floating point numbers: + +```python +>>> float("3.1415") +3.1415 +``` + +Bonus tip: `int()` works great for converting floats as well, as long as you don't care about the mantissa (the part after the decimal point): + +```python +>>> int(3.1415) +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> my_string = str(100) +>>> my_string +'100' +>>> type(my_string) + +>>> my_int = int(my_string) +>>> my_int +100 +>>> type(my_int) + +``` + +```python +>>> float("3.1415") +3.1415 +``` + +```python +>>> int(3.1415) +3 +``` +{{%/expand%}} + +## Converting Between Lists and Strings + +A `str`ing can be considered as just a list of characters, so converting back and forth is easy: + +```python +>>> my_list = list("hello") +>>> my_list +>>> str(my_list) +``` + +Oops, that wasn't quite what we wanted. Running any object through `str()` will usually return a literal string of that object. What we want is to *join* the elements of the list (into a string). We can do that using string's built-in `join()` method. In this case, we'll use an empty string: + +```python +>>> ''.join(my_list) +# Note: we can use any string we want to join the characters! +>>> ','.join(my_list) +>>> '-'.join(my_list) +``` + +Another common way of converting a string into a list is with the string's `split()` method. This is useful for lightweight parsing of, for example, CSV (comma separated value) data. + +```python +>>> my_string = "the,quick,brown,fox" +>>> my_string.split(",") +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> my_list = list("hello") +>>> my_list +['h', 'e', 'l', 'l', 'o'] +>>> str(my_list) +"['h', 'e', 'l', 'l', 'o']" +``` + +```python +>>> ''.join(my_list) +'hello' + +>>> ','.join(my_list) +'h,e,l,l,o' +>>> '-'.join(my_list) +'h-e-l-l-o' +``` + +```python +>>> my_string = "the,quick,brown,fox" +>>> my_string.split(",") +['the', 'quick', 'brown', 'fox'] +``` + +{{%/expand%}} diff --git a/website/content/03-intermediate-python/10-introduction/15-converting-between-types/_index.md b/website/content/03-intermediate-python/10-introduction/15-converting-between-types/_index.md new file mode 100644 index 0000000..bd849c4 --- /dev/null +++ b/website/content/03-intermediate-python/10-introduction/15-converting-between-types/_index.md @@ -0,0 +1,8 @@ ++++ +title = "Converting Between Types" +date = 2019-01-25T15:05:14-06:00 +weight = 4 +draft = false ++++ + +Easily converting between different datatypes is one of Python's superpowers! Let's see how... diff --git a/website/content/03-intermediate-python/10-introduction/_index.md b/website/content/03-intermediate-python/10-introduction/_index.md new file mode 100644 index 0000000..6cb5760 --- /dev/null +++ b/website/content/03-intermediate-python/10-introduction/_index.md @@ -0,0 +1,14 @@ ++++ +title = "More Python Ideas" +date = 2019-01-25T15:21:05-06:00 +weight = 10 +chapter = true +draft = false +pre = "1. " ++++ + +### Chapter 1 + +# More Python Ideas + +Let's learn a little more about Python, and talk about more best practices, to help you get an idea of what "good" Python code should look like. \ No newline at end of file diff --git a/website/content/03-intermediate-python/10-introduction/images/python.png b/website/content/03-intermediate-python/10-introduction/images/python.png new file mode 100644 index 0000000..23a4c6e Binary files /dev/null and b/website/content/03-intermediate-python/10-introduction/images/python.png differ diff --git a/website/content/03-intermediate-python/20-advanced-looping/10-list-comprehensions.md b/website/content/03-intermediate-python/20-advanced-looping/10-list-comprehensions.md new file mode 100644 index 0000000..4d7a686 --- /dev/null +++ b/website/content/03-intermediate-python/20-advanced-looping/10-list-comprehensions.md @@ -0,0 +1,99 @@ +--- +title: "List Comprehensions" +date: 2019-03-10T19:13:32-07:00 +draft: false +weight: 1 +--- + +List comprehensions are a unique way to create lists in Python. A list comprehension consists of brackets containing an expression followed by a `for` clause, then zero or more `for` or `if` clauses. The expressions can be any kind of Python object. List comprehensions will commonly take the form of `[ for in ]`. + +A simple case: Say we want to turn a list of strings into a list of string *lengths.* We could do this with a `for` loop: + +```python +>>> names = ["Nina", "Max", "Rose", "Jimmy"] +>>> my_list = [] # empty list +>>> for name in names: +... my_list.append(len(name)) +... +>>> print(my_list) +[4, 3, 4, 5] +``` + +We can do this much easier with a list comprehension: + +```python +>>> names = ["Nina", "Max", "Rose", "Jimmy"] +>>> my_list = [len(name) for name in names] +>>> print(my_list) +[4, 3, 4, 5] +``` + +We can also use comprehensions to perform operations, and the lists we assemble can be composed of any type of Python object. For example: + +```python +>>> names = ["Nina", "Max", "Rose", "Jimmy"] +>>> my_list = [("length", len(name) * 2) for name in names] +>>> print(my_list) +[('length', 8), ('length', 6), ('length', 8), ('length', 10)] +``` + +In the above example, we assemble a list of tuples - each tuple contains the element "length" as well as each number from the `len()` function multiplied by two. + + +### Conditionals + +You can also use conditionals (`if` statements) in your list comprehensions. For example, to quickly make a list of only the even lengths, you could do: + +```python +>>> names = ["Nina", "Max", "Rose", "Jimmy"] +>>> my_list = [len(name) for name in names if len(name) % 2 == 0] +>>> print(my_list) +[4, 4] +``` + +Here, we check divide every string length by 2, and check to see if the remainder is 0 (using the modulo operator). + + +### String Joining with a List Comprehension + +Back in our exercise on converting between types, we introduced the `string.join()` function. You can call this function on any string, pass it a list, and it will spit out a string with every element from the list "joined" by the string. For example, to get a comma-delimited list of numbers, you might be tempted to do: + +```python +>>> my_string = ",".join([0, 1, 2, 3, 4]) +Traceback (most recent call last): + File "", line 1, in +TypeError: sequence item 0: expected str instance, int found +``` + +Unfortunately, you can't join a list of numbers without first converting them to strings. But you can do this easily with a list comprehension: + +```python +>>> my_list = [0, 1, 2, 3, 4] +>>> my_string = ",".join([str(num) for num in my_list]) +>>> print(my_string) +0,1,2,3,4 +``` + + +### `sum`, `min`, `max` + +Some mathematical functions, such as `sum`, `min`, and `max`, accept lists of numbers to operate on. For example, to get the sum of numbers between zero and five, you could do: + +```python +my_sum = sum([0, 1, 2, 3, 4]) +print(my_sum) +``` + +But remember, anywhere you can use a list, you can use a list comprehension. Say you want to get sum, minimum, and maximum of every number between 0 and 100 that is evenly divisible by 3? No sense typing out a whole list in advance, just use a comprehension: + +```python +>>> my_sum = sum([num for num in range(0, 100) if num % 3 == 0]) +>>> print(my_sum) +1683 +>>> my_min = min([num for num in range(0, 100) if num % 3 == 0]) +>>> print(my_min) +0 +>>> my_max = max([num for num in range(0, 100) if num % 3 == 0]) +>>> print(my_max) +99 +``` \ No newline at end of file diff --git a/website/content/03-intermediate-python/20-advanced-looping/30-other-comprehensions.md b/website/content/03-intermediate-python/20-advanced-looping/30-other-comprehensions.md new file mode 100644 index 0000000..a84c119 --- /dev/null +++ b/website/content/03-intermediate-python/20-advanced-looping/30-other-comprehensions.md @@ -0,0 +1,95 @@ +--- +title: "Other Comprehensions" +date: 2019-03-10T19:13:39-07:00 +draft: false +weight: 3 +--- + +### Dictionary Comprehensions + +Dictionary comprehensions are a quick and easy way of assembling dictionaries in Python. They work just like list comprehensions, and look almost the same. They use curly braces instead of square brackets, and they contain two variables (for `key` and `value`), separated by a colon. + +For example, to assemble a `dict` in which the keys are numbers between 0 and 10, and the values are the same number squared, we could do: + +```python +>>> squares = {num:num * num for num in range(10)} +>>> print(squares) +{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} +``` + +Or we could use f-strings to assemble a `dict` to keep game scores: + +```python +>>> scores = {f"player-{num}":0 for num in range(0, 5)} +>>> print(scores) +{'player-0': 0, 'player-1': 0, 'player-2': 0, 'player-3': 0, 'player-4': 0} +``` + +In the above example, the f-string gets turned into the `dict` keys (`player-0`, etc.) and each value is set to 0. You can also operate on tuples for setting keys and values. For example, we'll use a list comprehension to create a list of tuples, then turn the tuples into `dict` keys and values: + +```python +>>> my_list = [(f"player-{num}", num * 2) for num in range(0, 5)] +>>> print(my_list) +[('player-0', 0), ('player-1', 2), ('player-2', 4), ('player-3', 6), ('player-4', 8)] +>>> scores = {key:value for (key, value) in my_list} +>>> print(scores) +{'player-0': 0, 'player-1': 2, 'player-2': 4, 'player-3': 6, 'player-4': 8} +``` + +### Set Comprehensions + +Set comprehensions are another great operation in Python - they look like a cross between `list` and `dict` comprehensions, and they create `set` objects. + +For example: +```python +>>> my_set = {num for num in [1, 2, 1, 0, 3]} +>>> print(my_set) +{0, 1, 2, 3} +``` + +Notice that instead of returning the same list of numbers (as `num for num` would have done in a list comprehension), you instead get a `set` (note the curly braces) of unique numbers from the list (you only get one `1`). + + +### Generator Expressions + +Generator expressions are a little more advanced. A generator is a type of iterable object - like a list, you can iterate through each element - however, unlike a list, generators evaluate elements on demand, instead of assembling them all at once. + +A generator comprehension looks just like a list comprehension, except we use parenthesis instead of brackets. For example, to get a list of the square of every even number between 0 and 10, we could do: + +```python +# List comprehension +>>> list_comp = [x ** 2 for x in range(10) if x % 2 == 0] +>>> print(list_comp) +[0, 4, 16, 36, 64] + +# Generator expression +>>> gen_exp = (x ** 2 for x in range(10) if x % 2 == 0) +>>> print(gen_exp) + at 0x10d48cc00> +>>> for num in gen_exp: +... print(num) +... +0 +4 +16 +36 +64 +``` + +Generator comprehensions can be beneficial in circumstances where you want to iterate over very large lists without storing the entire list in memory. For example, if you tried to assemble a list of every number between 0 and 10 ** 8 (10 to the 8th power), Python will try to assemble the entire list in memory. Using the `timeit` library to create this list only once, we can see how long this takes: + +```python +>>> list_comp = "[num for num in range(0, 10 ** 8)]" +>>> import timeit +>>> timeit.timeit(list_comp, number=1) +7.578090285999998 +# Over 7 seconds just to assemble one huge list +# Let's do the same with a generator comprehension instead: +>>> gen_comp = "(num for num in range(0, 10 ** 8))" +>>> timeit.timeit(gen_comp, number=1) +9.919999996554907e-06 +>>> timeit.timeit(gen_comp, number=10000000) +7.211805443999992 +``` + +As you can see, assembling the generator is almost instantaneous, in fact we can run the generator expression over 10 million times in less time than it takes to assemble the full list once, and the generator will take far less memory. \ No newline at end of file diff --git a/website/content/03-intermediate-python/20-advanced-looping/50-slicing.md b/website/content/03-intermediate-python/20-advanced-looping/50-slicing.md new file mode 100644 index 0000000..e7aa91c --- /dev/null +++ b/website/content/03-intermediate-python/20-advanced-looping/50-slicing.md @@ -0,0 +1,72 @@ +--- +title: "Slicing" +date: 2019-03-10T19:13:03-07:00 +draft: false +weight: 5 +--- + +Slicing is a easy way to create sub-lists from larger lists. If you remember back to our exercise on data types, we can use a slice to obtain a subset of items from a `list`. Remember that a string is just a list of characters. For example: + +```python +>>> my_string = "Hello, world!" +>>> my_string[7:12] # from 7 to 12 +'world' +``` + +### Lopsided Slicing + +You can also leave out one of the numbers in the slice. Leaving out the first number is equivalent to using a zero - you can think of this as "from the beginning." Leaving out the last number is equivalent to using the length of the list you're slicing - you can think of this as "until the end." For example: + +```python +>>> my_string = "Hello, world!" +>>> my_string[:5] # from zero to 5 +'Hello' +>>> my_string[7:] # from 7 to the end +'world!' +``` + +You can also leave out both sides of the slice! You can think of this as "from the beginning, until the end." Why? This is an easy way to copy a list! + +```python +>>> my_new_string = my_string[:] +>>> my_new_string +'Hello, world!' +``` + +### Negative Indexing + +You aren't limited to positive numbers for your slicing, either. A negative number on the left side will wrap around to the other side of your list. A negative number on the right side is equivalent to the length of the list minus your number. For example: + +```python +>>> my_string = "Hello, world!" +>>> my_string[-6:] # from the end - 6 to the end +'world!' +>>> my_string[-10:-4] # from the end - 10 to the end - 4 +'lo, wo' +``` + +You can also use just a single negative number to get an item counting *backwards* from the end of a `list`. For example, to get the last item from a list: + +```python +>>> my_list = [1, 3, 3, 7] +>>> my_list[-1] +7 +``` + +### Stride or Step + +Python slices also have a *third*, optional argument, called "step" or "stride", separated by a second colon. This lets you skip elements of a list or even reverse them. For example: + +```python +>>> my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> my_list[::2] # move forward by 2, or skip every other index +[0, 2, 4, 6, 8] +>>> my_list[::-1] # move backward by 1, and easy way to reverse a list +[9, 8, 7, 6, 5, 4, 3, 2, 1, 0] +>>> my_list[1:7:2] # get every other index between 1 and 7 +[1, 3, 5] +``` + +{{% notice note %}} +You can use a slice to get a subset of items from any data type that maintains an order, such as a `list` or `tuple`, but not from any non-ordered data types, such as `dict` or `set`. +{{% /notice %}} \ No newline at end of file diff --git a/website/content/03-intermediate-python/20-advanced-looping/70-zip.md b/website/content/03-intermediate-python/20-advanced-looping/70-zip.md new file mode 100644 index 0000000..b769ada --- /dev/null +++ b/website/content/03-intermediate-python/20-advanced-looping/70-zip.md @@ -0,0 +1,48 @@ +--- +title: "The zip function" +date: 2019-03-03T18:03:03-08:00 +draft: false +weight: 7 +--- + +It's often necessary to iterate over multiple lists simultaneously. Suppose we're keeping score of a game and we have two lists, one for names and one for scores: + +```python +>>> names = ["Bob", "Alice", "Eve"] +>>> scores = [42, 97, 68] +``` + +The `zip` function takes any number of iterable arguments and steps through +all of them at the same time until the end of the *shortest* iterable has been reached: + +```python +>>> for name, score in zip(names, scores): +>>> print(f"{name} had a score of {score}.") +... +Bob had a score of 42. +Alice had a score of 97. +Eve had a score of 68. +``` + +What will the above loop print after removing the last element from `scores`? + +```python +>>> scores.pop(-1) +68 +>>> for name, score in zip(names, scores): +>>> print(f"{name} had a score of {score}.") +... +Bob had a score of 42. +Alice had a score of 97. +``` + +The loop terminates even though there are more values in `names`. Here, Eve isn't included because `scores` only has two elements. + +We can also use `zip()` to quickly and easily create a `dict` from two lists. For example: + +```python +>>> scores = [42, 97, 68] +>>> score_dict = dict(zip(names, scores)) +>>> print(score_dict) +{'Bob': 42, 'Alice': 97, 'Eve': 68} +``` \ No newline at end of file diff --git a/website/content/03-intermediate-python/20-advanced-looping/90-exercise.md b/website/content/03-intermediate-python/20-advanced-looping/90-exercise.md new file mode 100644 index 0000000..8fee479 --- /dev/null +++ b/website/content/03-intermediate-python/20-advanced-looping/90-exercise.md @@ -0,0 +1,102 @@ +--- +title: "Practice" +date: 2019-03-09T00:00:00-08:00 +draft: false +weight: 10 +pre: "⭐️ " +--- + +### Comprehensions + +Let's practice our comprehensions. Create a list of only odd numbers between 0 and 100 using a list comprehension. Then, use a comprehension to create a dictionary where the keys are the even numbers from your list, and the values are random integers between 0 and 100 (hint: try `random.randint(min, max)`). Finally, use a comprehension to create a set of every unique value from the above dictionary. + +```python +>>> my_list = [num for num in range(0, 100) if num % 2 == 0] +>>> print(my_list) + +>>> import random +>>> my_dict = {num:random.randint(0, 100) for num in my_list} +>>> print(my_dict) + +>>> my_set = {num for num in my_dict.values()} +>>> print(my_set) +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> my_list = [num for num in range(0, 100) if num % 2 == 0] +>>> print(my_list) +[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98] + +>>> import random +>>> my_dict = {num:random.randint(0, 100) for num in my_list} +>>> print(my_dict) +{0: 37, 2: 84, 4: 56, 6: 45, 8: 63, 10: 57, 12: 39, 14: 25, 16: 18, 18: 10, 20: 52, 22: 95, 24: 93, 26: 89, 28: 96, 30: 77, 32: 16, 34: 91, 36: 19, 38: 14, 40: 92, 42: 35, 44: 85, 46: 86, 48: 44, 50: 32, 52: 38, 54: 34, 56: 23, 58: 71, 60: 37, 62: 100, 64: 98, 66: 15, 68: 84, 70: 40, 72: 47, 74: 30, 76: 42, 78: 36, 80: 62, 82: 49, 84: 11, 86: 58, 88: 60, 90: 6, 92: 41, 94: 28, 96: 16, 98: 93} + +>>> my_set = {num for num in my_dict.values()} +>>> print(my_set) +{6, 10, 11, 14, 15, 16, 18, 19, 23, 25, 28, 30, 32, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 45, 47, 49, 52, 56, 57, 58, 60, 62, 63, 71, 77, 84, 85, 86, 89, 91, 92, 93, 95, 96, 98, 100} +``` + +{{%/expand%}} + + +### Slicing + +You know how to create a list of even or odd numbers with a list comprehension. Make a list of numbers between 0 and 100, then try making a list of even numbers between 30 and 70, by taking a slice from the first list. Then, make a new list in the reverse order. + +```python +>>> my_list = [num for num in range(0, 100)] +>>> my_slice = my_list[30:70:2] +>>> print(my_slice) +>>> my_backwards_slice = my_slice[::-1] +>>> print(my_backwards_slice) +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> my_list = [num for num in range(0, 100)] +>>> my_slice = my_list[30:70:2] +>>> print(my_slice) +[30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68] +>>> my_backwards_slice = my_slice[::-1] +>>> print(my_backwards_slice) +[68, 66, 64, 62, 60, 58, 56, 54, 52, 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30] +``` + +{{%/expand%}} + + +### `zip` + +Make a list of all the names you can think of, called "names". Make a second list of numbers, called "scores", using a list comprehension and `random.randint(min, max)` as before. Use the first list in your comprehension to make it the same length. Then, use `zip()` to output a simple scoreboard of one score per name. + +```python +>>> names = ["Nina", "Max", "Floyd", "Lloyd"] +>>> scores = [random.randint(0, 100) for name in names] +>>> scores +[41, 38, 96, 81] +>>> for name, score in zip(names, scores): +... print(f"{name} got a score of {score}") +... +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> names = ["Nina", "Max", "Floyd", "Lloyd"] +>>> scores = [random.randint(0, 100) for name in names] +>>> scores +[41, 38, 96, 81] +>>> for name, score in zip(names, scores): +... print(f"{name} got a score of {score}") +... +Nina got a score of 41 +Max got a score of 38 +Floyd got a score of 96 +Lloyd got a score of 81 +``` + +{{%/expand%}} diff --git a/website/content/03-intermediate-python/20-advanced-looping/_index.md b/website/content/03-intermediate-python/20-advanced-looping/_index.md new file mode 100644 index 0000000..b4a0622 --- /dev/null +++ b/website/content/03-intermediate-python/20-advanced-looping/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Advanced Looping" +date = 2019-03-03T11:57:04-08:00 +weight = 20 +draft = false +chapter = true +pre = "2. " ++++ + +### Chapter 2 + +# Advanced Looping with List Comprehensions + +Learn about advanced looping with list comprehensions, and other types of comprehensions. \ No newline at end of file diff --git a/website/content/03-intermediate-python/30-oop-classes-inheritance/10-concept.md b/website/content/03-intermediate-python/30-oop-classes-inheritance/10-concept.md new file mode 100644 index 0000000..2999a32 --- /dev/null +++ b/website/content/03-intermediate-python/30-oop-classes-inheritance/10-concept.md @@ -0,0 +1,19 @@ +--- +title: "Concept" +date: 2019-03-10T19:29:28-07:00 +draft: false +weight: 1 +--- + +Object-oriented Programming (OOP) is a language model (or *paradigm*) in which properties or behaviors are organized into "objects". Some languages encourage a more procedural style, like if you were writing a recipe - some popular examples are COBOL and BASIC. Languages that adopt an Object-oriented style organize things into objects, and provide methods for objects to communicate with one another. + +### What is an object? + +An object can be a function, a variable, a property, a class... everything in Python is an object. You can think of an object as a generic container - a `list` object might contain a sequence of `int` objects, along with some function objects. The `int` objects contain integer numbers. The function objects contain code that can be executed on the `list` object or maybe on the items in the `list`. + + +### Python and OOP + +Python buys heavily into the OOP model - you'll find that everything in Python is an object of some kind, and almost everything has attributes and methods. + +This doesn't mean you *have* to use OOP in your programs - Python works perfectly well as a procedural or "script" language, where one command is executed after another, like a recipe. But getting familiar with OOP will not only help you read other Python code, but it will help you learn to encapsulate your code into objects for better organization and readability, as well as increase efficiency by making your code easily reusable. Objects are center-stage in Python, representing not only the data, but the overall structure of your programs as well. \ No newline at end of file diff --git a/website/content/03-intermediate-python/30-oop-classes-inheritance/30-classes.md b/website/content/03-intermediate-python/30-oop-classes-inheritance/30-classes.md new file mode 100644 index 0000000..478587c --- /dev/null +++ b/website/content/03-intermediate-python/30-oop-classes-inheritance/30-classes.md @@ -0,0 +1,77 @@ +--- +title: "Classes" +date: 2019-03-10T19:29:24-07:00 +weight: 3 +draft: false +--- + +Every thing or object in Python is an instance of a *class*. The number `42` is an instance of the class `int`. The string `Hello, world` is an instance of the `str` (or string) class. These classes, in turn, are subclasses of the master `object` class. + + +### Classes vs Instances + +The Object-oriented Programming paradigm is structured around Classes and Instances. You can think of a class as a "type" of something, like "Car." You can think of an instance as a specific thing, such as "my Subaru," which is a type of "Car." Both classes and instances can have variables and methods. Changing a class variable will change what is returned when you get that variable from an instance, however changing an instance variable only applies to that one instance. We'll come back to this in a second. + + +### `self` + +You might have seen the `self` keyword peppered around in examples of Python objects. `self` is used inside classes to refer to a bound instance variable or object. For example, let's say we have a class called `Car`: + +```python +class Car: + runs = True + + def start(self): + if self.runs: + print("Car is started. Vroom vroom!") + else: + print("Car is broken :(") +``` + +Now, let's make a specific instance of our Car class and call the `start()` method on it to see if it starts. + +```python +>>> my_car = Car() +>>> my_car.runs +True +>>> my_car.start() +Car is started. Vroom vroom! +``` + +We can see that the `runs` variable of our `my_car` instance is `True`. Now, what if we set `runs` to `False`? + +```python +>>> my_car.runs = False +>>> my_car.start() +Car is broken :( +``` + +But if we make a new instance of the `Car` class... + +```python +>>> my_other_car = Car() +>>> my_other_car.start() +Car is started. Vroom vroom! +``` + +When we run the `start()` function, the `self` keyword points to the bound instance of `Car` - so when we call `start()` on `my_car`, self points to `my_car` and sees an instance variable `runs` that is `False`, but when we call `my_other_car.start()`, it returns `True`. Why? + + +### `self` refers to an instance + +Back to what we were saying in the last section, `runs` is a class variable on the Car class, meaning that it exists for all instances of type Car. When we set `runs` to `False`, we created an instance variable on `my_car`, and when we called `start()`, `self` told the interpreter to look for an instance variable in `my_car` called `runs`. + +When we called `my_other_car.start()`, the interpreter looked for an instance variable called `runs`, but didn't find it, so it looked at the next level up, the class, and found the class variable `Car.runs`, which returned `True`. + +You may have noticed, but all instance methods within classes take `self` as their first argument, such as `def start(self):` above. + +What happens if we call `start()` on the Car class instead of an instance? + +```python +>>> Car.start() +Traceback (most recent call last): + File "", line 1, in +TypeError: start() missing 1 required positional argument: 'self' +``` + +If you try to call `start()` on the Car class, `self` doesn't have an instance to bind to, so we get an error that the required argument `self` wasn't passed in to the `start()` function. \ No newline at end of file diff --git a/website/content/03-intermediate-python/30-oop-classes-inheritance/50-methods.md b/website/content/03-intermediate-python/30-oop-classes-inheritance/50-methods.md new file mode 100644 index 0000000..23dbd99 --- /dev/null +++ b/website/content/03-intermediate-python/30-oop-classes-inheritance/50-methods.md @@ -0,0 +1,172 @@ +--- +title: "Methods" +date: 2019-03-10T19:29:24-07:00 +draft: false +weight: 5 +--- + +You've just seen the difference between class and instance variables. Classes can also have class methods - methods that are shared among all instances of a certain type. As with variables, they can be overriden in a specific instance or subclass. + +Let's add a class method to our Car class: + +```python +class Car: + runs = True + number_of_wheels = 4 + + @classmethod + def get_number_of_wheels(cls): + return cls.number_of_wheels + + def start(self): + if self.runs: + print("Car is started. Vroom vroom!") + else: + print("Car is broken :(") +``` + +And call it: + +```python +>>> my_car = Car() +>>> print(f"Cars have {Car.get_number_of_wheels()} wheels.") +Cars have 4 wheels. +# Of course, we can override this in our instance: +>>> my_car.number_of_wheels = 6 +# And when we access our new instance variable: +>>> print(f"My car has {my_car.number_of_wheels} wheels.") +My car has 6 wheels. +# But, when we call our class method on our instance: +>>> print(f"My car has {my_car.get_number_of_wheels()} wheels.") +My car has 4 wheels. +``` + +Why? Because `get_number_of_wheels()` is a class method, and when it's called, the *class* (Car) gets passed in, and the value of `Car.number_of_wheels` is returned. Although we can access the instance variable (with a value of 6), the `get_number_of_wheels()` class method still returns the class variable, which is 4. + + +## `type`, `isinstance`, and `issubclass` + +Python comes with some built-in functions for inspecting classes and types: + +As we've seen throughout the workshop, the `type()` function returns the type of the object you pass it, or it's class. For example: + +```python +>>> type(42) + +>>> type("Hello world!") + +>>> type(my_car) + +``` + +The `isinstance()` function takes an object and a class, and returns `True` if the object you pass it is an instance of the class. For example: + +```python +>>> isinstance(42, int) +True +>>> isinstance("Hello world!", str) +True +>>> isinstance(my_car, float) +False +>>> isinstance(my_car, Car) +True +``` + +The `issubclass` function takes two classes, and returns `True` if the first class is a subclass of the second. For example: + +```python +# bool is a subclass of int +>>> issubclass(bool, int) +True +# int is a subclass of object +>>> issubclass(int, object) +True +# technically, everything is a subclass of object +>>> issubclass(bool, object) +True +``` + + +### `__init__` + +Classes can have an optional magic method called `__init__()` that gets run when you instantiate an instance of a class. You can use the `__init__()` method to do any special thing you want to happen when your instance is instantiated, including setting instance variables. `__init__` can take arguments, too. + +{{% notice note %}} +Methods that are bracketed by underscores are sometimes called "magic methods." We won't be covering magic methods in this class, but we will point out a few of the interesting ones. +{{% /notice %}} + +For example: + +```python +class Car: + runs = True + + def __init__(self, make, model): + self.make = make + self.model = model + + def start(self): + if self.runs: + print(f"Your {self.make} {self.model} is started. Vroom vroom!") + else: + print(f"Your {self.make} {self.model} is broken :(" +``` + +```python +>>> my_car = Car("Ford", "Thunderbird") +>>> my_car.start() +Your Ford Thunderbird is started. Vroom vroom! +``` + +Here, we accept two required variables, `make` and `model` in our `__init__()` method, and set instance variables of the same names using `self`. Later, when we call `start()`, we can grab `self.make` and `self.model` from the bound instance and use them in our string. + + +### `__str__` and `__repr__` + +Classes have two other magic methods that come in handy for debugging, `__str__()` and `__repr__()`. Both functions return a string representation of an object. `__str__()` should return readable end-user output, and `__repr__()` should return the Python code necessary to rebuild the object. `__str__()` maps to the built-in function `str()` and `__repr__()` maps to the built-in function `repr()`. + +For example, we'll use the `datetime` library to generate a `datetime` object for right now: + +```python +>>> import datetime +>>> now = datetime.datetime.now() +>>> str(now) +'2019-03-16 21:04:01.396256' +>>> repr(now) +'datetime.datetime(2019, 3, 16, 21, 4, 1, 396256)' +``` + +You can see that `str()` has returned a human-readable date/time, and `repr()` has returned a string that represents the Python code we would need to run to recreate this object. + +We can, of course, set our own `__str__()` and `__repr__()` methods in our custom classes: + +```python +class Car: + def __init__(self, make, model): + self.make = make + self.model = model + + def __str__(self): + return f"<>" + + def __repr__(self): + return f"Car('{self.make}', '{self.model}')" +``` + +```python +>>> my_car = Car("Ford", "Thunderbird") +>>> print(f"This object is a {str(my_car)}") +This object is a <> +>>> print(f"To reproduce it, type: {repr(my_car)}") +To reproduce it, type: Car('Ford', 'Thunderbird') +``` + +### Bonus + +You don't have to instantiate everything by hand, you can instantiate objects in `for` loops or even comprehensions. This is useful for running a function on a list of objects. For example, to convert a list of number-strings into a list of integers, you could do: + +```python +>>> my_ints = [int(str_num) for str_num in ["1", "2", "3"]] +>>> print(my_ints) +[1, 2, 3] +``` \ No newline at end of file diff --git a/website/content/03-intermediate-python/30-oop-classes-inheritance/70-inheritance.md b/website/content/03-intermediate-python/30-oop-classes-inheritance/70-inheritance.md new file mode 100644 index 0000000..8e9de80 --- /dev/null +++ b/website/content/03-intermediate-python/30-oop-classes-inheritance/70-inheritance.md @@ -0,0 +1,94 @@ +--- +title: "Inheritance" +date: 2019-03-10T19:29:19-07:00 +draft: false +weight: 7 +--- + +Class inheritance is a very useful Object-oriented Programming construct for sharing and reusing code. Inheritance makes it possible to break up and organize your code into a hierarchy, from generic to specific. Objects that belong to classes that are higher up in the hierarchy (more generic) are accessible by subclasses, but not vice versa. + +Earlier, we saw that `bool` is a subclass of `int`, thus, it *inherited* the properties and methods of the `int` class, and then *extended* it to be more specific to booleans. + +We can do the same with our own classes, too. In a file called `vehicle.py`, let's create a parent `Vehicle` class, and have our `Car` class be a subclass. + +```python +class Vehicle: + + def __init__(self, make, model, fuel="gas"): + self.make = make + self.model = model + self.fuel = fuel + + +class Car(Vehicle): + + def __init__(self, make, model, fuel="gas"): + super().__init__(make, model, fuel) +``` + +When we instantiate a Car instance, the interpreter calls `__init__()`, where we pass in two arguments (`make` and `model`) and an optional 3rd (`fuel`, which defaults to "gas"). In `__init__()`, we call `super().__init__()`, which resolves to our parent class, `Vehicle`, and runs *its* `__init__` function, where the variables are stored. Note that even though the variables are stored at the `Vehicle` level, they are instance variables because `self` is bound to `my_car`, which is a `Car`, which is a `Vehicle`. Don't forget to import your Vehicle and Car classes. Behold: + +```python +>>> from vehicle import Vehicle, Car +>>> my_car = Car("Ford", "Thunderbird") +>>> print(type(my_car)) + +>>> print(my_car.fuel) +gas +>>> print(isinstance(my_car, Car)) +True +>>> print(isinstance(my_car, Vehicle)) +True +>>> print(issubclass(Car, Vehicle)) +True +``` + + +### Overriding Variables in a Subclass + +We can, of course, use a subclass to override variables that belong to a parent class. Let's update our `vehicle.py` file: + +```python +class Vehicle: + number_of_wheels = 4 + + def __init__(self, make, model, fuel="gas"): + self.make = make + self.model = model + self.fuel = fuel + +class Car(Vehicle): + + def __init__(self, make, model, fuel="gas"): + super().__init__(make, model, fuel) + +class Truck(Vehicle): + number_of_wheels = 6 + + def __init__(self, make, model, fuel="diesel"): + super().__init__(make, model, fuel) + +class Motorcycle(Vehicle): + number_of_wheels = 2 +``` + +```python +>>> my_truck = Truck("Ford", "F350") +>>> print(type(my_truck)) + +>>> print(my_truck.fuel) +diesel +>>> print(my_truck.number_of_wheels) +6 +``` + +Note how our Truck's class variable `number_of_wheels` overrode the parent class `Vehicle`'s `number_of_wheels` (or, to be more specific, the interpreter found `number_of_wheels` in a closer scope, the `Truck` class, and did not need to continue searching up the hierarchy). Likewise, `Motorcycle` overrides just the `number_of_wheels` variable to equal 2. Notice there is no `__init__()` function in `Motorcycle` - the `number_of_wheels` variable is overridden but instantiating a Motorcycle just goes straight to the `Vehicle.__init__()` method. + + +### Multiple Inheritance in Python + +Can Python classes inherit from *multiple* parent classes? Yes, this is called *multiple inheritance*. It's not as commonly used for simple programs, but you'll see it more often as you start using libraries. + +One common use case for multiple inheritance in Python is for a type of class called a *Mixin*. *Mixin* classes tend to be used to quickly and easily add additional properties and methods into a class. This type of design pattern encourages code with composable architecture. + +Unfortunately, we won't have time to cover the topic of Multiple inheritance in this workshop... because it's *out of scope*. 🤣 \ No newline at end of file diff --git a/website/content/03-intermediate-python/30-oop-classes-inheritance/90-exercise.md b/website/content/03-intermediate-python/30-oop-classes-inheritance/90-exercise.md new file mode 100644 index 0000000..073618c --- /dev/null +++ b/website/content/03-intermediate-python/30-oop-classes-inheritance/90-exercise.md @@ -0,0 +1,153 @@ ++++ +title = "Practice" +date = 2019-01-25T15:07:04-06:00 +weight = 10 +draft = false +pre = "⭐️ " ++++ + +## `class`es and `self` + +Let's practice making a simple class. Open a new file and save it as `class_example.py`, we'll be running this from the command line rather than the REPL. Pass in several variables and save them to the instance by using `self`: + +```python +class Vehicle: + + def __init__(self, make, model, fuel="gas"): + self.make = make + self.model = model + self.fuel = fuel + +daily_driver = Vehicle("Subaru", "Crosstrek") +# By default, this is how python represents our object: +print(daily_driver) + +# The variables we saved to the instance are available like this: +print(f"I drive a {daily_driver.make} {daily_driver.model}. It runs on {daily_driver.fuel}.") +``` + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python class_example.py +<__main__.Vehicle object at 0x10555c780> +I drive a Subaru Crosstrek. It runs on gas. +``` + +{{% /expand%}} + +## `class` Variables + +We can also add class variables - variables that exist for all instances of a class. Let's add a variable called `number_of_wheels` to the class scope: + +```python +class Vehicle: + + number_of_wheels = 4 + + def __init__(self, make, model, fuel="gas"): + self.make = make + self.model = model + self.fuel = fuel +``` + +Let's query both the instance and class variables. Note that we set the instance variable to 3, but the higher-level class variable is still set to 4. + +```python +daily_driver = Vehicle("Subaru", "Crosstrek") + +daily_driver.number_of_wheels = 3 + +# Instance variables +print(f"I drive a {daily_driver.make} {daily_driver.model}. It runs on {daily_driver.fuel}.") +print(f"My {daily_driver.model} has {daily_driver.number_of_wheels} wheels.") + +# Class variable +print(f"Most vehicles have {Vehicle.number_of_wheels} wheels.") +``` + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python class_example.py +I drive a Subaru Crosstrek. It runs on gas. +My Crosstrek has 3 wheels. +Most vehicles have 4 wheels. +``` + +{{% /expand%}} + +## Inheritance + +Class inheritance in Python is super useful - you can easily create a hierarchy of classes to make your life easier and maximize code reuse. Let's subclass our `Vehicle` class and extend it by breaking out Cars and Trucks. + +```python +class Vehicle: + + def __init__(self, make, model, fuel="gas"): + self.make = make + self.model = model + self.fuel = fuel + + +class Car(Vehicle): + + number_of_wheels = 4 + + +class Truck(Vehicle): + + number_of_wheels = 6 + + def __init__(self, make, model, fuel="diesel"): + super().__init__(make, model, fuel) +``` + +Note: we've moved the `number_of_wheels` variable to the subclasses. Our `Car` subclass sets this variable but instantiating a `Car` just passes through to `Vehicle.__init__()`. We do, however, provide a `__init__()` for `Truck`, which changes the default `fuel` to `diesel` and then calls `super().__init__()` which redirects to `Vehicle.__init__()`. This lets us make changes that are specific to `Truck` instances (but we can still call them `Vehicles`). Let's instantiate our subclasses: + +```python +daily_driver = Car("Subaru", "Crosstrek") +print(f"I drive a {daily_driver.make} {daily_driver.model}. " + f"It uses {daily_driver.fuel} and has {daily_driver.number_of_wheels} wheels.") + +truck = Truck("Ford", "F350") +print(f"I also have a {truck.make} {truck.model}. " + f"It uses {truck.fuel} and has {truck.number_of_wheels} wheels.") +``` + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python class_example.py +I drive a Subaru Crosstrek. It uses gas and has 4 wheels. +I also have a Ford F350. It uses diesel and has 6 wheels. +``` + +{{% /expand%}} + +## `type`, `isinstance`, and `issubclass` + +The `type()` command tells us the type of an object - for example, a `Truck` or a `Car`. Note that it doesn't know anything about inheritance, so you can't use `type()` to check if a `Car` is a `Vehicle`. For that, we can use `isinstance()`. `issubclass()` is another useful function that we can use to see if a *class* (rather than an instance) is a subclass of another class. Add this to your code: + +```python +print(f"My daily driver is a {type(daily_driver)} and my truck is a {type(truck)}") + +print(f"Is my daily driver a car? {isinstance(daily_driver, Car)}") +print(f"Is my truck a Vehicle? {isinstance(truck, Vehicle)}") +print(f"Is my truck a Car? {isinstance(truck, Car)}") + +print(f"Is a Truck a subclass of Vehicle? {issubclass(Truck, Vehicle)}") +``` + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python class_example.py +My daily driver is a and my truck is a +Is my daily driver a car? True +Is my truck a Vehicle? True +Is my truck a Car? False +Is a Truck a subclass of Vehicle? True +``` + +{{% /expand%}} diff --git a/website/content/03-intermediate-python/30-oop-classes-inheritance/_index.md b/website/content/03-intermediate-python/30-oop-classes-inheritance/_index.md new file mode 100644 index 0000000..4038c9b --- /dev/null +++ b/website/content/03-intermediate-python/30-oop-classes-inheritance/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Object Oriented Programming" +date = 2019-03-03T11:58:39-08:00 +weight = 30 +chapter = true +draft = false +pre = "3. " ++++ + +### Chapter 3 + +# Classes, Objects, Object Oriented programming + +Object oriented programming allows us to represent our code in a different way, organized into `class`es \ No newline at end of file diff --git a/website/content/03-intermediate-python/40-exceptions/10-all-about-exceptions.md b/website/content/03-intermediate-python/40-exceptions/10-all-about-exceptions.md new file mode 100644 index 0000000..65494a7 --- /dev/null +++ b/website/content/03-intermediate-python/40-exceptions/10-all-about-exceptions.md @@ -0,0 +1,47 @@ +--- +title: "All About Exceptions" +date: 2019-03-10T19:15:54-07:00 +draft: false +weight: 1 +--- + +Built-in exceptions and easy exception handling is one of the shining features of Python. Technically, errors that happen during parsing are called `SyntaxError`s - these will probably be the most common errors you see, and usually happen because of a mistake in whitespace, a syntax misunderstanding, or a simple typo. + +Even if the syntax is correct, errors can still occur when your program is run. We call these Exceptions, and there a many different types (this is a good thing, because the more specifically we know what went wrong, the better we can handle it). + +An un-handled exception is fatal: it will print debugging information (called a traceback), stop the interpreter, and exit your program. However, once you learn to handle Exceptions, you can cover your bases and write programs that are robust in the face of issues. + + +## Types of Exceptions + +Python has many useful built-in exceptions that you'll probably encounter in your travels. Some of the more common ones that you'll run into are: + +|Exception|Cause of Error| +|---|---| +|AttributeError|Raised when attribute assignment or reference fails.| +|ImportError|Raised when the imported module is not found.| +|IndexError|Raised when index of a sequence is out of range.| +|KeyError|Raised when a key is not found in a dictionary.| +|KeyboardInterrupt|Raised when the user hits interrupt key (Ctrl+c or delete).| +|NameError|Raised when a variable is not found in local or global scope.| +|SyntaxError|Raised by parser when syntax error is encountered.| +|IndentationError|Raised when there is incorrect indentation.| +|ValueError|Raised when a function gets argument of correct type but improper value.| + +You can find a more detailed list of built-in exceptions in the [Python documentation](https://docs.python.org/3/library/exceptions.html). + +## Exception Hierarchy + +An important thing to know is that exceptions, like everything else in Python, are just objects. They follow an inheritance hierarchy, just like classes do. For example, the `ZeroDivisionError` is a subclass of `ArithmeticError`, which is a subclass of `Exception`, itself a subclass of `BaseException`. + +So, if you wanted to catch a divide-by-zero error, you could use `except ZeroDivisionError`. But you could also use `except ArithmeticError`, which would catch not only `ZeroDivisionEror`, but also `OverflowError` and `FloatingPointError`. You could use `except Exception`, but this is not a good idea, as it will catch almost *every* type of error, even ones you weren't expecting. We'll discuss this a bit later. Again, a full chart of the hierarchy for built-in exceptions can be found at the bottom of the (Python documentation)[https://docs.python.org/3/library/exceptions.html#exception-hierarchy]. + +## Exiting your Program + +As we mentioned, exceptions that are allowed to bubble up to the top level (called *unhandled* exceptions) will cause your program to exit. This is generally unwanted - even if an error is unrecoverable, we still want to provide more detailed information about the error for later inspection, or a pretty error for the user if our program is user-facing, and in most cases, we want the program to go back to doing what it was doing. + +What if we want our program to stop, though? You may already be familiar with `ctrl-c`, the age-old posix method of sending SIGINT (an interrupt signal) to a program. You may be surprised to know asking your operating system to send SIGINT to Python causes, yes, an exception - `KeyboardInterrupt`. And yes, you can catch `KeyboardInterrupt`, but this will make your program a little harder to kill. + +You can also use `sys.exit()` from the built-in `sys` library. It's generally not a good idea to pepper `sys.exit()` around your code, as it makes it harder to control when your program exits, but this can be a handy function for controlling how and when your program exits. By default, `sys.exit()` with no parameters will exit with a `0` return code, which, by posix convention, signals success. You can pass an integer to `sys.exit()` if you'd like to exit with a non-zero return code (usually signaling some sort of failure condition). You can also pass a string to `sys.exit()`, which will get printed to the command line, along with a return code of `1`. + +`sys.exit()` generates a `SystemExit` exception, which inherits from the master `BaseException` class, which makes it possible for clean-up handlers (such as `finally` statements) to run. \ No newline at end of file diff --git a/website/content/03-intermediate-python/40-exceptions/30-try-except-else-finally.md b/website/content/03-intermediate-python/40-exceptions/30-try-except-else-finally.md new file mode 100644 index 0000000..312f9a2 --- /dev/null +++ b/website/content/03-intermediate-python/40-exceptions/30-try-except-else-finally.md @@ -0,0 +1,66 @@ +--- +title: "Try Except" +date: 2019-03-10T19:15:54-07:00 +draft: false +weight: 3 +--- + +Many languages have the concept of the "Try-Catch" block. Python uses four keywords: `try`, `except`, `else`, and `finally`. Code that can possibly throw an exception goes in the `try` block. `except` gets the code that runs if an exception is raised. `else` is an optional block that runs if no exception was raised in the `try` block, and `finally` is an optional block of code that will run last, regardless of if an exception was raised. We'll focus on `try` and `except` for this chapter. + +A basic example looks like this: + +```python +>>> try: +... x = int(input("Enter a number: ")) +... except ValueError: +... print("That number was invalid") +``` + +First, the `try` clause is executed. If no exception occurs, the `except` clause is skipped and execution of the `try` statement is finished. If an exception occurs in the `try` clause, the rest of the clause is skipped. If the exception's type matches the exception named after the `except` keyword, then the `except` clause is executed. If the exception doesn't match, then the exception is *unhandled* and execution stops. + + +### The `except` Clause + +An `except` clause may have multiple exceptions, given as a parenthesized tuple: + +```python +try: + # Code to try + +except (RuntimeError, TypeError, NameError): + # Code to run if one of these exceptions is hit +``` + +A `try` statement can also have more than one `except` clause: + +```python +try: + # Code to try + +except RuntimeError: + # Code to run if there's a RuntimeError + +except TypeError: + # Code to run if there's a TypeError + +except NameError: + # Code to run if there's a NameError +``` + +### Finally + +Finally, we have `finally`. `finally` is an optional block that runs after `try`, `except`, and `else`, regardless of if an exception is thrown or not. This is good for doing any cleanup that you want to happen, whether or not an exception is thrown. + +```python +>>> try: +... raise KeyboardInterrupt +... finally: +... print("Goodbye!") +... +Goodbye! +Traceback (most recent call last): + File "", line 2, in +KeyboardInterrupt +``` + +As you can see, our Goodbye! gets printed just before the unhandled `KeyboardInterrupt` gets propagated up and triggers the traceback. \ No newline at end of file diff --git a/website/content/03-intermediate-python/40-exceptions/50-exception-hierarchy.md b/website/content/03-intermediate-python/40-exceptions/50-exception-hierarchy.md new file mode 100644 index 0000000..9565a65 --- /dev/null +++ b/website/content/03-intermediate-python/40-exceptions/50-exception-hierarchy.md @@ -0,0 +1,27 @@ +--- +title: "Exception Hierarchy" +date: 2019-03-10T19:15:44-07:00 +draft: true +weight: 5 +--- + +Python has many useful built-in exceptions that you'll probably encounter in your travels. Some of the more common ones that you'll run into are listed in the [All About Exceptions](../10-all-about-exceptions) section. You can find a more detailed list of built-in exceptions in the [Python documentation](https://docs.python.org/3/library/exceptions.html). + +An important thing to know is that exceptions, like everything else in Python, are just objects. They follow an inheritance hierarchy, just like classes do. For example, the `ZeroDivisionError` is a subclass of `ArithmeticError`, which is a subclass of `Exception`, itself a subclass of `BaseException`. + +```python +>>> issubclass(ZeroDivisionError, ArithmeticError) +True +>>> issubclass(ArithmeticError, Exception) +True +>>> issubclass(Exception, BaseException) +True + +# Thus, +>>> issubclass(ZeroDivisionError, BaseException) +True +``` + +So, if you wanted to catch a divide-by-zero error, you could use `except ZeroDivisionError`. But you could also use `except ArithmeticError`, which would catch not only `ZeroDivisionEror`, but also `OverflowError` and `FloatingPointError`. You could use `except Exception`, but this is not a good idea, as it will catch almost *every* type of error, even ones you weren't expecting. We'll discuss this a bit later. + +A full chart of the hierarchy for built-in exceptions can be found at the bottom of the [Python documentation](https://docs.python.org/3/library/exceptions.html#exception-hierarchy). \ No newline at end of file diff --git a/website/content/03-intermediate-python/40-exceptions/70-best-practices.md b/website/content/03-intermediate-python/40-exceptions/70-best-practices.md new file mode 100644 index 0000000..3608489 --- /dev/null +++ b/website/content/03-intermediate-python/40-exceptions/70-best-practices.md @@ -0,0 +1,32 @@ +--- +title: "Best Practices" +date: 2019-03-10T19:15:44-07:00 +draft: false +weight: 7 +--- + +### Catch More Specific Exceptions First + +Remember, your `except` handlers are evaluated in order, so be sure to put more specific exceptions first. For example: + +```python +>>> try: +... my_value = 3.14 / 0 +... except ArithmeticError: +... print("We had a general math error") +... except ZeroDivisionEror: +... print("We had a divide-by-zero error") +... +We had a general math error +``` + +When we tried to divide by zero, we inadvertently raised a ZeroDivisionError. However, because ZeroDivisionError is a subclass of ArithmeticError, and `except ArithemticError` came first, the information about our specific error was swallowed by the `except ArithemticError` handler, and we lost more detailed information about our error. + +### Don't Catch `Exception` + +It's bad form to catch the general `Exception` class. This will catch every type of exception that subclasses the `Exception` class, which is almost all of them. You may have errors that you don't care about, and don't affect the operation of your program, or maybe you're dealing with a flaky API and want to swallow errors and retry. By catching `Exception`, you run the risk of hitting an unexpected exception that your program actually can't recover from, or worse, swallowing an important exception without properly logging it - a huge headache when trying to debug programs that are failing in weird ways. + + +### Definitely don't catch `BaseException` + +Catching `BaseException` is a really bad idea, because you'll swallow every type of Exception, including `KeyboardInterrupt`, the exception that causes your program to exit when you send a SIGINT (Ctrl-C). Don't do it. \ No newline at end of file diff --git a/website/content/03-intermediate-python/40-exceptions/90-custom-exceptions.md b/website/content/03-intermediate-python/40-exceptions/90-custom-exceptions.md new file mode 100644 index 0000000..e1ce024 --- /dev/null +++ b/website/content/03-intermediate-python/40-exceptions/90-custom-exceptions.md @@ -0,0 +1,57 @@ +--- +title: "Custom Exceptions" +date: 2019-03-10T19:16:01-07:00 +draft: false +weight: 9 +--- + +As we mentioned, exceptions are just regular classes that inherit from the `Exception` class. This makes it super easy to create our own custom exceptions, which can make our programs easier to follow and more readable. An exception need not be complicated, just inherit from `Exception`: + +```python +>>> class MyCustomException(Exception): +... pass +... +>>> raise MyCustomException() +Traceback (most recent call last): + File "", line 1, in +__main__.MyCustomException +``` + +It's OK to have a custom `Exception` subclass that only `pass`-es - your exception doesn't need to do anything fancy to be useful. Having custom exceptions - tailored to your specific use cases and that you can raise and catch in specific circumstances - can make your code much more readable and robust, and reduce the amount of code you write later to try and figure out what exactly went wrong. + +Of course, you can get as fancy as you want. You can send additional information, like messages, to your exceptions. Just add an `__init__()` method to your exception class, with whatever arguments you want. + +```python +class IncorrectValueError(Exception): +... def __init__(self, value): +... message = f"Got an incorrect value of {value}" +... super().__init__(message) +... +>>> my_value = 9999 +>>> if my_value > 100: +... raise IncorrectValueError(my_value) +... +Traceback (most recent call last): + File "", line 2, in +__main__.IncorrectValueError: Got an incorrect value of 9999 +``` + +`Exception` takes an optional string argument message that gets printed with your exception. We pass our erroneous value to our `IncorrectValueError` object, which constructs a special message and passes it its parent class, `Exception`, via `super().__init__()`. The custom message string, along with the value for context, gets printed along with our error traceback. + +### A Custom Exception for our GitHub API app + +If we wanted to write a custom Exception for our GitHub API app, it might look something like this. + +```python +class GitHubApiException(Exception): + + def __init__(self, status_code): + if status_code == 403: + message = "Rate limit reached. Please wait a minute and try again." + else: + message = f"HTTP Status Code was: {status_code}." + + super().__init__(message) +``` + +Notice how it takes the HTTP status code into account, and displays a custom error message for the 403, rate limited reached status code. \ No newline at end of file diff --git a/website/content/03-intermediate-python/40-exceptions/99-exercise.md b/website/content/03-intermediate-python/40-exceptions/99-exercise.md new file mode 100644 index 0000000..5f49851 --- /dev/null +++ b/website/content/03-intermediate-python/40-exceptions/99-exercise.md @@ -0,0 +1,232 @@ +--- +title: "Practice" +date: 2019-03-09T00:00:00-08:00 +draft: false +weight: 10 +pre: "⭐️ " +--- + +### Syntax Errors + +Let's get more comfortable with exceptions. First, you've probably seen this one already: The `IndentationError`. + +```python +>>> def my_function(): +... print("Hello!") + File "", line 2 + print("Hello!") + ^ +IndentationError: expected an indented block +``` + +Notice that we started a new function scope with the `def` keyword, but didn't indent the next line of the function, the `print()` argument. + +You've probably also seen the more general `SyntaxError`. This one's probably obvious - something is misspelled, or the syntax is otherwise wrong. Python gives us a helpful little caret `^` under the earliest point where the error was detected, however you'll have to learn to read this with a critical eye as sometimes the actual mistake precedes the invalid syntax. For example: + +```python +>>> a = [4, +... x = 5 + File "", line 2 + x = 5 + ^ +SyntaxError: invalid syntax +``` + +Here, the invalid syntax is `x = 5`, because assignment statements aren't valid list elements, however the actual error is the missing right bracket `]` on the line above. + +### Common Exceptions + +You'll get plenty of practice triggering syntax errors on your own. Let's practice triggering some exceptions. Type this *perfectly valid* code into your REPL and see what happens: + +```python +>>> a = 1 / 0 +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} +```python +>>> a = 1 / 0 +Traceback (most recent call last): + File "", line 1, in +ZeroDivisionError: division by zero +``` +{{% /expand%}} + +Of course, you'll get a divide-by-zero error, or as Python calls it, `ZeroDivisionError`. Some other common errors are `TypeError` when trying to perform an action on two unrelated types, `KeyError` when trying to access a dictionary key that doesn't exist, and `AttributeError` when trying to access a variable or call a function that doesn't exist on an object. + +```python +>>> 2 + "3" + +>>> my_dict = {"hello": "world"} +>>> my_dict["foo"] + +>>> my_dict.append("foo") +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} +```python +>>> 2 + "3" +Traceback (most recent call last): + File "", line 1, in +TypeError: unsupported operand type(s) for +: 'int' and 'str' + +>>> my_dict = {"hello": "world"} +>>> my_dict["foo"] +Traceback (most recent call last): + File "", line 1, in +KeyError: 'foo' + +>>> my_dict.append("foo") +Traceback (most recent call last): + File "", line 1, in +AttributeError: 'dict' object has no attribute 'append' +``` +{{% /expand%}} + + +### Raising our own Exceptions + +Making our own Exceptions is cheap and easy, and useful for keeping track of various error states that are specific to your application. Simply inherit from the general `Exception` class: + +```python +>>> class MyException(Exception): +... pass +>>> raise MyException() +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> class MyException(Exception): +... pass +>>> raise MyException() +Traceback (most recent call last): + File "", line 1, in +__main__.MyException +``` +{{% /expand%}} + +It's also sometimes helpful to change the default behavior for your custom Exceptions. In this case, you can simply provide your own `__init__()` method inside your Exception subclass: + + +```python +class MyException(Exception): +... def __init__(self, message): +... new_message = f"!!!ERROR!!! {message}" +... super().__init__(new_message) +... +>>> raise MyException("Something went wrong!") +``` +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +class MyException(Exception): +... def __init__(self, message): +... new_message = f"!!!ERROR!!! {message}" +... super().__init__(new_message) +... +>>> raise MyException("Something went wrong!") +Traceback (most recent call last): + File "", line 1, in +__main__.MyException: !!!ERROR!!! Something went wrong! +``` +{{% /expand%}} + +### `try`, `except` + +In Python, the "try-catch" statements use `try` and `except`. As we discussed, `try` is the code that could possibly throw an Exception, and `except` is the code that runs if the error is raised. Practice catching a `KeyError` by `try`ing to access a fake dictionary key: + +```python +>>> try: +... my_dict = {"hello": "world"} +... print(my_dict["foo"]) +... except KeyError: +... print("Oh no! That key doesn't exist") +... +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> try: +... my_dict = {"hello": "world"} +... print(my_dict["foo"]) +... except KeyError: +... print("Oh no! That key doesn't exist") +... +Oh no! That key doesn't exist +``` +{{% /expand%}} + +Let's add in catching the specific `KeyError` object so that we can access it during the `except` block: + +```python +>>> try: +... my_dict = {"hello": "world"} +... print(my_dict["foo"]) +... except KeyError as key_error: +... print(f"Oh no! The key {key_error} doesn't exist!") +... +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> try: +... my_dict = {"hello": "world"} +... print(my_dict["foo"]) +... except KeyError as key_error: +... print(f"Oh no! The key {key_error} doesn't exist!") +... +Oh no! The key 'foo' doesn't exist! +``` +{{% /expand%}} + + +### Re-Raising + +Sometimes it's helpful to catch an error, perform an action, and then pass the error on rather than swallowing it. This is useful when, for example, something goes wrong deep inside your code and you need to perform a special action, but also let code further up the chain know that something is wrong and the program can't continue. Let's divide one number by other, decrementing until we hit zero. Catch that error and immediately raise a `RuntimeError`: + +```python +>>> while True: +... for divisor in range(5, -1, -1): +... try: +... quotient = 10 / divisor +... print(f"10 / {divisor} = {quotient}") +... except ZeroDivisionError: +... print("Oops! We tried to divide by zero!") +... raise RuntimeError +... +``` + +{{%expand "Here's what you should have seen in your REPL:" %}} + +```python +>>> while True: +... for divisor in range(5, -1, -1): +... try: +... quotient = 10 / divisor +... print(f"10 / {divisor} = {quotient}") +... except ZeroDivisionError: +... print("Oops! We tried to divide by zero!") +... raise RuntimeError +... +10 / 5 = 2.0 +10 / 4 = 2.5 +10 / 3 = 3.3333333333333335 +10 / 2 = 5.0 +10 / 1 = 10.0 +Oops! We tried to divide by zero! +Traceback (most recent call last): + File "", line 4, in +ZeroDivisionError: division by zero + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + File "", line 8, in +RuntimeError +``` + +What happened here? We got two exceptions! First, our code hit the `ZeroDivisionError`, which we caught, and printed our "Oops!" message. Then, the interpreter saw that we raised a `RuntimeError`, which we didn't catch, so it broke us out of our `while True` loop and ended the program with a Traceback. + +{{% /expand%}} diff --git a/website/content/03-intermediate-python/40-exceptions/_index.md b/website/content/03-intermediate-python/40-exceptions/_index.md new file mode 100644 index 0000000..674d991 --- /dev/null +++ b/website/content/03-intermediate-python/40-exceptions/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Exceptions" +date = 2019-03-03T11:57:29-08:00 +weight = 40 +chapter = true +pre = "4. " +draft = false ++++ + +### Chapter 4 + +# Exceptions + +In this chapter you'll learn all about Exceptions in Python. We'll start by reviewing the exception hierarchy, raising exceptions, examining `try` and `catch`, and finally how to create our own Exception classes. \ No newline at end of file diff --git a/website/content/03-intermediate-python/50-libraries-modules/10-standard-library.md b/website/content/03-intermediate-python/50-libraries-modules/10-standard-library.md new file mode 100644 index 0000000..ac5828a --- /dev/null +++ b/website/content/03-intermediate-python/50-libraries-modules/10-standard-library.md @@ -0,0 +1,39 @@ +--- +title: "Standard Library" +date: 2019-03-10T19:16:56-07:00 +draft: false +weight: 1 +--- + +Python has always had a "batteries included" philosophy - having a rich and versatile standard library which is immediately available, without making the user download separate packages. This is thought to have contributed to Python's early success. No matter what you're trying to accomplish, there's probably a built-in library that can help you do what you want. + +The downside to this is that the standard libraries need to maintain backwards compatibility. Some were quick hacks, hard to use, poorly designed and now impossible to fix, or have simply been rendered obsolete. Luckily, Python also makes it easy to install and use external libraries - we'll cover this later. + + +### The Standard Library + +There are some great libraries included with Python that you'll probably end up seeing or using frequently. `sys` provides system-specific parameters and functions, such as `exit()`. `os` has miscellaneous operating system interfaces, and provides the excellent `os.path` submodule for handling file paths on any operating system. `math` gives you all the advanced math function. `json` is an easy-to-use json parser and encoder. Python even gives you built-in libraries for database access, logging, internet protocols, multimedia, debugging, and even libraries for extending Python itself. The full list of standard libraries can be found in the [Python documentation](https://docs.python.org/3/library/). + +As a quick example, let's look at Python's `datetime` library. You can easily make a `datetime` object that represents any given point in time. For example: + +```python +>>> import datetime +>>> right_now = datetime.datetime.now() +>>> print(right_now) +2019-03-17 13:41:10.994700 +>>> repr(right_now) +'datetime.datetime(2019, 3, 17, 13, 41, 10, 994700)' +``` + +We can even make a `datetime` object for an arbitrary date, and subtract it from right now to get a `timedelta` object: + +```python +>>> new_years = datetime.datetime(2019, 1, 1, 0, 0) +>>> print(new_years) +2019-01-01 00:00:00 +>>> delta = right_now - new_years +>>> print(delta) +75 days, 13:41:10.994700 +``` + +We can easily see that it's been over 75 days from `new_years` until `right_now`. We'll come back to `datetime` later in this chapter. \ No newline at end of file diff --git a/website/content/03-intermediate-python/50-libraries-modules/30-modules-and-imports.md b/website/content/03-intermediate-python/50-libraries-modules/30-modules-and-imports.md new file mode 100644 index 0000000..3b70234 --- /dev/null +++ b/website/content/03-intermediate-python/50-libraries-modules/30-modules-and-imports.md @@ -0,0 +1,71 @@ +--- +title: "Modules and Imports" +date: 2019-03-10T19:16:35-07:00 +draft: false +weight: 3 +--- + +Python has a simple package structure. Any directory with a file named `__init__.py` can be considered a Python module. + +{{% notice info %}} +Note: a `__init__.py` file is no longer *required* for Python 3 modules, but it's still supported and can be useful. +{{% /notice %}} + +For example, let's make a basic function and start a new module to house it: + +```python +def add_numbers(x, y): + return x + y +``` + +Let's put our function in a file called `__init__.py` and place it in a folder called `my_math_functions`. Now, as long as the folder `my_math_functions` is somewhere in our `PYTHONPATH`, we can `import my_math_functions` and reach `add_numbers()`. If we start our REPL from the folder that contains `my_math_functions`, we can import it: + +```python +>>> import my_math_functions +>>> my_math_functions.add_numbers(1, 2) +3 +``` + +### Best Practices + +There a few different ways to import modules or even just specific objects from modules. You can import *everything* from a module into the local namespace using `*`: + +```python +>>> from my_math_functions import * +>>> add_numbers(1, 2) +3 +``` + +This isn't a good practice, because it's hard to tell where a specific function is coming from without the namespace context. Also, function names can conflict, and this can make things very difficult to debug. + +Better is to import functions specifically: + +```python +>>> from my_math_functions import add_numbers +>>> add_numbers(1, 2) +3 +``` + +This make things a little clearer, as we can look at the top and see where the `add_number()` function came from. However, an even better way is to just import the module and use it in calls to maintain the namespace context: + +```python +>>> import my_math_functions +>>> my_math_functions.add_numbers(1, 2) +3 +``` + +This can be slightly more verbose, but unless it makes your function calls ridiculously long, it generally makes things much easier to debug. + +{{% notice tip %}} +You can use the `as` keyword to make things a little easier on yourself. +{{% /notice %}} + +```python +>>> import my_math_functions as mmf +>>> mmf.add_numbers(1, 2) +3 +``` + +### PYTHONPATH + +What is the `PYTHONPATH` we mentioned earlier? This is a list of paths on your system where Python will look for packages. Python will always look first in the working directory (the folder you're in when you start the REPL or run your program), so if your module folder is there, you can import it. You can also install your modules just like any other external modules, using a `setup.py` file. It's also possible to change or add paths to your `PYTHONPATH` list if you need to store modules elsewhere, but this isn't a very portable solution. \ No newline at end of file diff --git a/website/content/03-intermediate-python/50-libraries-modules/50-pypi.md b/website/content/03-intermediate-python/50-libraries-modules/50-pypi.md new file mode 100644 index 0000000..c7a0a2d --- /dev/null +++ b/website/content/03-intermediate-python/50-libraries-modules/50-pypi.md @@ -0,0 +1,28 @@ +--- +title: "PyPI" +date: 2019-03-10T19:17:03-07:00 +draft: false +weight: 5 +--- + +PyPI (the Python Package Index) is an awesome service that helps you find and install software developed and shared by the Python community. Almost every user-contributed Python package has been published to PyPI. You can browse the site at [pypi.org](https://pypi.org/) but most of the time you will probably interact with it through Python's `pip` tool. + +### Basic Usage + +You can use the `pip` tool to install the latest version of a module and its dependencies from the Python Packaging Index: + +```bash +(env) $ python -m pip install SomePackage +``` + + +### Know your Packages + +There are a lot of packages on PyPI, and they're not always up-to-date. Sometimes it helps to look at a package before installing it. Simply search for a package name on PyPI.org - for example, here's the page for the [redis package](https://PyPI.org/project/redis/). If you follow the Homepage link, you'll be taken to the project's [GitHub page](https://github.com/andymccurdy/redis-py), where you can see that the latest commit was very recently. So you know this package is actively maintained, and will probably work in your up-to-date version of Python. + +{{% notice warning %}} +When you're first starting out, it's a good idea to copy the `pip install` text from the website, otherwise bad actors could take advantage of your typos. +{{% /notice %}} + + +We'll practice installing a package in the exercise for this chapter. \ No newline at end of file diff --git a/website/content/03-intermediate-python/50-libraries-modules/90-exercise.md b/website/content/03-intermediate-python/50-libraries-modules/90-exercise.md new file mode 100644 index 0000000..aac00cf --- /dev/null +++ b/website/content/03-intermediate-python/50-libraries-modules/90-exercise.md @@ -0,0 +1,87 @@ ++++ +title = "Practice" +date = 2019-01-25T15:07:04-06:00 +weight = 10 +draft = false +pre = "⭐️ " ++++ + +## The Standard Library + +The Python standard library has a huge number of packages - no matter what you want to do, there's probably a package included. Let's practice using some of the more common ones. Create a new file and use the `os` module to see if you can get a file listing for the folder your new file is in. + +```python +import os + +my_folder = os.getcwd() +print(f"Here are the files in {my_folder}:") + +with os.scandir(my_folder) as folder: + for entry in folder: + print(f" - {entry.name}") +``` + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python libraries_exercise.py +Here are the files in /Users/nina/Desktop/libraries_exercise: + - libraries_exercise.py +``` + +{{% /expand%}} + +`sys` is another commonly useful library, giving you access to some variables and functions used or maintained by the Python interpreter. Let's try using `sys` to get the arguments passed into our program from the command line, and to figure out what kind of computer we're using: + +```python +import sys + +arguments = sys.argv +print(f"We received the following arguments:") + +for arg in arguments: + print(f" - {arg}") + +print(f"We are running on a '{sys.platform}' machine") +``` + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python libraries_exercise.py argument1 hello world "this is one argument" +We received the following arguments: + - libraries_exercise.py + - argument1 + - hello + - world + - this is one argument +We are running on a 'darwin' machine +``` +Note: if the string returned by `sys.platform` isn't what you expect, take a look at the [sys documentation](https://docs.python.org/3/library/sys.html). + +{{% /expand%}} + +## Pypi + +PyPI (the Python Package Index) is an awesome service that helps you find and install almost any 3rd party Python package. You can browse the site at [PyPI.org](https://PyPI.org/) but most of the time you will probably interact with it through Python's `pip` command line tool. + +For example, earlier you may have installed the `requests` module. If you search `pip` for `requests`, you'll see every package in the index containing the word requests: + +```bash +(env) $ python -m pip search requests +requests-hawk (1.0.0) - requests-hawk +requests-dump (0.1.3) - `requests-dump` provides hook functions for requests. +requests-aws4auth (0.9) - AWS4 authentication for Requests +... +``` + +We just want the one named `requests`, so we'll install it with the `install` keyword. If you don't have it installed, `pip` will install it for you. If you installed it earlier, you'll see something like this: + +```bash +(env) $ python -m pip install requests +Requirement already satisfied: requests in /usr/local/lib/python3.7/site-packages (2.21.0) +Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /usr/local/lib/python3.7/site-packages (from requests) (3.0.4) +Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/site-packages (from requests) (2019.3.9) +Requirement already satisfied: idna<2.9,>=2.5 in /usr/local/lib/python3.7/site-packages (from requests) (2.8) +Requirement already satisfied: urllib3<1.25,>=1.21.1 in /usr/local/lib/python3.7/site-packages (from requests) (1.24.1) +``` \ No newline at end of file diff --git a/website/content/03-intermediate-python/50-libraries-modules/_index.md b/website/content/03-intermediate-python/50-libraries-modules/_index.md new file mode 100644 index 0000000..356e2bb --- /dev/null +++ b/website/content/03-intermediate-python/50-libraries-modules/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Libraries and Modules" +date = 2019-03-03T12:07:12-08:00 +weight = 50 +chapter = true +draft = false +pre = "5. " ++++ + +### Chapter 5 + +# Libraries and Modules + +The Python language was designed to be "batteries included" -- it has a rich and versatile standard library which is immediately available, without making the user download separate packages. This has given the Python language a head start in many projects. Let's explore some of the helpful packages in the standard library, and learn how to make our own modules. \ No newline at end of file diff --git a/website/content/03-intermediate-python/60-command-line-tools/10-advanced-strings.md b/website/content/03-intermediate-python/60-command-line-tools/10-advanced-strings.md new file mode 100644 index 0000000..ec671e6 --- /dev/null +++ b/website/content/03-intermediate-python/60-command-line-tools/10-advanced-strings.md @@ -0,0 +1,104 @@ +--- +title: "Advanced Strings" +date: 2019-02-10T18:17:18-08:00 +draft: false +weight: 1 +--- + +## Advanced f-strings + +### Decimal Formatting + +Formatting decimal or floating point numbers with f-strings is easy - you can pass in both a field width and a precision. The format is {value:width.precision}. Let's format pi (3.1415926) to two decimal places - we'll set the width to 1 because we don't need padding, and the precision to 3, giving us the one number to the left of the decimal and the two numbers to the right: + +```python +>>> print(f"Pi to two decimal places is {3.1415926:1.3}") +Pi to two decimal places is 3.14 + +# We'll break it out into variables to make it clearer: +>>> value = 3.1415926 +>>> width = 1 +>>> precision = 3 +>>> print(f"Pi to two decimal places is: {value:{width}.{precision}}") +Pi to two decimal places is: 3.14 + +# Let's change the width to 10 +>>> value = 3.1415926 +>>> width = 10 +>>> precision = 3 +>>> print(f"Pi to two decimal places is: {value:{width}.{precision}}") +Pi to two decimal places is: 3.14 +``` + +Note how the second one is padded with extra spaces - the number is four characters long (including the period), so the formatter added six extra spaces to equal the total width of 10. + + +### Multiline Strings + +Sometimes it's easier to break up large statements into multiple lines. Just prepend every line with `f`: + +```python +>>> name = 'Nina' +>>> pi = 3.14 +>>> food = 'pie' +>>> message = ( +... f"Hello, my name is {name}. " +... f"I can calculate pi to two places: {pi:4.3}. " +... f"But I would rather be eating {food}." +... ) +>>> print(message) +Hello, my name is Nina. I can calculate pi to two places: 3.14. But I would rather be eating pie. +``` + +### Trimming a string + +Python strings have some very useful functions for trimming whitespace. `strip()` returns a new string after removing any leading and trailing whitespace. `rstrip()` and does the same but only removes trailing whitespace, and `lstrip()` only trims leading whitespace. We'll print our string inside `><` characters to make it clear: + +```python +>>> my_string = " Hello World! " +>>> print(f">{my_string.lstrip()}<") +>Hello World! < +>>> print(f">{my_string.rstrip()}<") +> Hello World!< +>>> print(f">{my_string.strip()}<") +>Hello World!< +``` + +Note the different spaces inside of the brackets. These functions also accept an optional argument of characters to remove. Let's remove all leading or trailing commas: + +```python +>>> my_string = "Hello World!,,," +>>> print(my_string.strip(",")) +Hello World! +``` + + +### Replacing Characters + +Strings have a useful function for replacing characters - just call `replace()` on any string and pass in what you want replace, and what you want to replace it with: + +```python +>>> my_string = "Hello, world!" +>>> my_string.replace("world", "Nina") +'Hello, Nina!' +``` + +### `str.format()` and `%` formatting + +Python has two older methods for string formatting that you'll probably come across at some point. `str.format()` is the more verbose older cousin to `f-strings` - variables appear in brackets in the string but must be passed in to the `format()` call. For example: + +```python +>>> name = "Nina" +>>> print("Hello, my name is {name}".format(name=name)) +Hello, my name is Nina +``` + +Note that the variable name inside the string is local to the string - it must be assigned to an outside variable inside the `format()` call, hence `.format(name=name)`. + +%-formatting is a much older method of string interpolating and isn't used much anymore. It's very similar to the methods used in C/C++. Here, we'll use `%s` as our placeholder for a string, and pass the `name` variable in to the formatter by placing it after the `%` symbol. + +```python +>>> name = "Nina" +>>> print("Hello, my name is %s" % name) +Hello, my name is Nina +``` \ No newline at end of file diff --git a/website/content/03-intermediate-python/60-command-line-tools/30-accepting-user-input.md b/website/content/03-intermediate-python/60-command-line-tools/30-accepting-user-input.md new file mode 100644 index 0000000..b7cab6a --- /dev/null +++ b/website/content/03-intermediate-python/60-command-line-tools/30-accepting-user-input.md @@ -0,0 +1,58 @@ +--- +title: "Accepting User Input" +date: 2019-03-10T19:27:09-07:00 +draft: false +weight: 3 +--- + +### Accepting Command Line Arguments + +To accept basic arguments from the command line, we can use `sys.argv`. `argv` is a list that gets passed in to your program that contains whatever arguments your program was started with. Start a new Python file called `cli_exercise.py` and enter the following: + +```python +import sys + +args = sys.argv + +print(args) +``` + +Now run it: + +```bash +(env) $ python cli_exercise.py +['cli_exercise.py'] +``` + +You should see a list with one item: the name of your program. Pass in additional arguments by adding them after your program name on the command line, separated by spaces: + +```bash +python cli_exercise.py argument1 argument2 "hello world" +['cli_exercise.py', 'argument1', 'argument2', 'hello world'] +``` + +Note that the name of the file you're running is rarely useful, so it's common to see this omitted with using slices, for example `sys.argv[1:]` + +{{% notice warning %}} +`sys.argv` is never empty - the first element in the list will always be the name of the Python file you're running. +{{% /notice %}} + + +### Accepting User Input with `input` + +You can also accept user data inside a running program by using `input()`. Let's make a simple interactive command line program that asks for a user's name and birthday: + +```python +name = input("Hello, what is your name? ") + +birthday_string = input(f"Hello {name}. Please enter your birthday in MM/DD/YYYY format: ") + +print(f"Hello {name}. Your birthday is on {birthday_string}.") +``` + +```bash +(env) $ python cli_exercise.py +Hello, what is your name? Floyd +Hello Floyd. Please enter your birthday in MM/DD/YYYY format: 01/20/1990 +Hello Floyd. Your birthday is on 01/20/1990. +``` \ No newline at end of file diff --git a/website/content/03-intermediate-python/60-command-line-tools/90-exercise.md b/website/content/03-intermediate-python/60-command-line-tools/90-exercise.md new file mode 100644 index 0000000..6516c18 --- /dev/null +++ b/website/content/03-intermediate-python/60-command-line-tools/90-exercise.md @@ -0,0 +1,88 @@ ++++ +title = "Practice" +date = 2019-01-25T15:07:04-06:00 +weight = 200 +draft = false +pre = "⭐️ " ++++ + +## Accepting User Input with Args + +To accept basic arguments from the command line, we can use `sys.argv`. Start a new Python file called `cli_exercise.py` and enter the following. + +```python +import sys + +args = sys.argv + +print(args) +``` + +Then, run it from the command line: + +```bash +(env) $ python cli_exercise.py +``` + + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python cli_exercise.py +['cli_exercise.py'] +``` +{{% /expand%}} + +You should see a list with one item: the name of your program. Pass in additional arguments by adding them after your program name on the command line, separated by spaces: + +```bash +(env) $ python cli_exercise.py argument1 argument2 "hello world" +``` + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python cli_exercise.py argument1 argument2 "hello world" +['cli_exercise.py', 'argument1', 'argument2', 'hello world'] +``` + +{{% /expand%}} + +Note that the name of the file you're running is rarely useful, so it's common to see this omitted with using slices, for example `sys.argv[1:]` + +## Accepting User Input with `input` + +You can also accept user data inside a running program by using `input()`. Let's make a simple interactive command line program that asks for a user's name and birthday. Call it `cli_exercise_input.py`. Use `input()` to get the user's name and birthday, and greet the user (call `strip()` on their name to remove any extra whitespace). + +{{%expand "You should have written something like this:" %}} + +```python +name = input("Hello, what is your name? ") + +birthday_string = input(f"Hello {name.strip()}. Please enter your birthday in MM/DD/YYYY format: ") + +print(f"Hello {name}. Your birthday is on {birthday_string}.") +``` +{{% /expand%}} + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python cli_exercise_input.py +Hello, what is your name? Floyd +Hello Nina. Please enter your birthday in MM/DD/YYYY format: 01/20/1990 +Hello Floyd. Your birthday is on 01/20/1990. +``` + +{{% /expand%}} + +### Optional Advanced Exercise + +{{% notice note %}} +If you thought this exercise was a breeze, try this optional advanced exercises. +{{% /notice %}} + + +Refactor the final exercise from Intro to Python, using custom exceptions and a class to store the information about a GitHub Repo. Accept the list of languages as user input. + +You can see an example implementation in the repo for this course at [git.io/python3](https://git.io/python3) \ No newline at end of file diff --git a/website/content/03-intermediate-python/60-command-line-tools/_index.md b/website/content/03-intermediate-python/60-command-line-tools/_index.md new file mode 100644 index 0000000..7eb0e53 --- /dev/null +++ b/website/content/03-intermediate-python/60-command-line-tools/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Writing Command Line Tools" +date = 2019-03-03T11:56:49-08:00 +weight = 60 +chapter = true +draft = false +pre = "6. " ++++ + +### Chapter 6 + +# Command Line Tools + +Learn how to write command line tool scripts in Python that accept input from a user. \ No newline at end of file diff --git a/website/content/03-intermediate-python/70-tests/10-concept.md b/website/content/03-intermediate-python/70-tests/10-concept.md new file mode 100644 index 0000000..37d1c3a --- /dev/null +++ b/website/content/03-intermediate-python/70-tests/10-concept.md @@ -0,0 +1,24 @@ +--- +title: "Concept" +date: 2019-03-10T19:30:29-07:00 +draft: false +weight: 1 +--- + +Unit testing is a software testing method by which individual functions are tested in an automated fashion to determine if they are fit for use. Automated unit testing not only helps you discover and fix bugs quicker and easier than if you weren't testing, but by writing them alongside or even *before* your functions, they can help you write cleaner and more bug-free code from the very start. + + +### Types of Tests + +There are several different kinds of automated tests that can be performed at different abstraction levels. + +* Unit tests operate on the smallest testable unit of code, usually a function that performs a single action or operation. +* Integration tests check to see if different units or modules of code work together as a group. +* Functional tests operate on units of functionality, to make sure a specific function of the software is working, which may involve several units of software or whole systems working together. + +For this class, we'll just be focusing on unit tests. + + +### Tests in the Real World™ + +How is this helpful in the real world? Many companies that invest in software development maintain a CI/CD (Continuous Integration or Continuous Deployment) pipeline. This usually involves extensive unit tests, integration tests, and maybe even functional tests, which are set up to run automatically after (and sometimes even *before*) code is committed. If the tests fail, deployment can be stopped and the developers get notified before broken code ever makes it to production servers. This can be complicated to set up properly, but saves an enormous amount of time in the long run, and helps to keep bugs from ever reaching your users. diff --git a/website/content/03-intermediate-python/70-tests/30-assertions.md b/website/content/03-intermediate-python/70-tests/30-assertions.md new file mode 100644 index 0000000..04f8e1a --- /dev/null +++ b/website/content/03-intermediate-python/70-tests/30-assertions.md @@ -0,0 +1,33 @@ +--- +title: "Assertions" +date: 2019-03-10T19:30:41-07:00 +draft: false +weight: 3 +--- + +Python comes with a handy-dandy `assert` keyword that you can use for simple sanity checks. An assertion is simply a boolean expression that checks if its conditions return true or not. If the assertion is true, the program continues. If it's false, it throws an `AssertionError`, and your program will stop. Assertions are also a great debugging tool, as they can stop your program in exactly the spot where an error has occurred. This is great for rooting out unreliable data or faulty assumptions. + +To make an assertion, just use the `assert` keyword followed by a condition that should be `True`: + +```python +>>> input_value = 25 +>>> assert input_value > 0 +``` + +If our assertion fails, however: + +```python +>>> input_value = 25 +>>> assert input_value > 100 +Traceback (most recent call last): + File "", line 1, in +AssertionError +``` + +### `assert` Is For Sanity Checks, Not For Production! + +Assertions are great for simple self-checks and sanity tests. You shouldn't, however, use assertions for failure cases that can occur because of bad user input or operating system/environment failures, such as a file not being found. These situations are much better suited to an exception or an error message. + +{{% notice warning %}} +Assertions can be disabled at run time, by starting the program with `python -O`, so you shouldn't rely on assertions to run in production code. Don't use them for validation! +{{% /notice %}} diff --git a/website/content/03-intermediate-python/70-tests/50-writing-tests.md b/website/content/03-intermediate-python/70-tests/50-writing-tests.md new file mode 100644 index 0000000..0574e65 --- /dev/null +++ b/website/content/03-intermediate-python/70-tests/50-writing-tests.md @@ -0,0 +1,166 @@ +--- +title: "Writing Tests" +date: 2019-03-10T19:30:36-07:00 +draft: false +weight: 5 +--- + +There are a few different frameworks for writing unit tests in Python, but they're all very similar. We'll focus on the built-in `unittest` library. `unittest` is both a framework for writing tests, as well as a test runner, meaning it can execute your tests and return the results. In order to write `unittest` tests, you must: + +* Write your tests as methods within classes +* Use a series of built-in assertion methods + +Let's start with a simple function to test, `multiply()`, which takes two numbers and multiplies them. + +```python +def multiply(x, y): + return x * y +``` + +Easy enough. Let's write a test case for it. Usually this will be broken out into a separate file, but we'll combine them for this contrived example. We'll create a `TestMultiply` class that derives from `unittest.TestCase`, with a method inside that does the actual testing. Lastly, we'll call `unittest.main()` to tell `unittest` to find and run our TestCase. We'll put all this in a file called `test_multiply.py` and run it from the command line: + +```python +import unittest + +def multiply(x, y): + return x * y + +class TestMultiply(unittest.TestCase): + + def test_multiply(self): + test_x = 5 + test_y = 10 + self.assertEqual(multiply(test_x, test_y), 50, "Should be 50") + +if __name__ == "__main__": + unittest.main() +``` + +```bash +(env) $ python test_multiply.py +. +---------------------------------------------------------------------- +Ran 1 test in 0.000s + +OK +``` + +### Failing Tests + +Let's introduce a bug into our `multiply()` function and see what happens when we run the test: + +```python +import unittest + +def multiply(x, y): + return x * x + +class TestMultiply(unittest.TestCase): + + def test_multiply(self): + test_x = 5 + test_y = 10 + self.assertEqual(multiply(test_x, test_y), 50, "Should be 50") + +if __name__ == "__main__": + unittest.main() +``` + +```bash +(env) $ python test_multiply.py +F +====================================================================== +FAIL: test_multiply (__main__.TestMultiply) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "test_multiply.py", line 11, in test_multiply + self.assertEqual(multiply(test_x, test_y), 50, "Should be 50") +AssertionError: 25 != 50 : Should be 50 + +---------------------------------------------------------------------- +Ran 1 test in 0.001s + +FAILED (failures=1) +``` + +### Important Concepts + +* `TestCase` class must subclass `unittest.TestCase` +* Names of test functions must begin with `test_` +* Import the code to be tested + +Your `TestCase` class can be called whatever you want, but you must subclass `unittest.TestCase` in order for it to work. + +{{% notice info %}} +Your test functions in your TestCase must begin with `test_`, otherwise they won't be run as tests. +{{% /notice %}} + +This can be useful if you need to include utility functions inside your TestCase. + +Lastly, your test code will need access to your code to be tested. For a small project, this is easily done by putting your tests in a `test.py` file alongside your code. For larger projects, you usually want to have multiple test files inside a `test` folder. In this case, you'll need to make sure your code to be tested is available on your `PYTHONPATH`. + +### Running your Tests + +As we saw, one common way of running your tests is by calling `unittest.main()`: + +```python +if __name__ == "__main__": + unittest.main() +``` + +Add this to your test file, run it, and you're off to the races. You can also skip this bit, and call `unittest` directly from the command line: + +```bash +(env) $ python -m unittest test_module +``` + +Here, you don't need to make your test file runnable (by using `unittest.main()`), instead you're running `unittest` directly and telling it where to find your tests. + +{{% notice tip %}} +Use the `-v` (or `--verbose`) flag can give you more information about which tests were run +{{% /notice %}} + + +```bash +(env) $ python -m unittest test_multiply -v +test_multiply (test_multiply.TestMultiply) ... FAIL + +====================================================================== +FAIL: test_multiply (test_multiply.TestMultiply) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/nina/Desktop/test_multiply.py", line 11, in test_multiply + self.assertEqual(multiply(test_x, test_y), 50, "Should be 50") +AssertionError: 25 != 50 : Should be 50 + +---------------------------------------------------------------------- +Ran 1 test in 0.001s + +FAILED (failures=1) +``` + + +### TestCase Assertions + +Subclassing the TestCase class gives you a bunch of useful assertions that you can use to check the validity of your code. Here's the list from the [Python documentation](https://docs.python.org/3/library/unittest.html): + + +|Method|Checks that| +|---|---| +|assertEqual(a, b)|a == b| +|assertNotEqual(a, b)|a != b| +|assertTrue(x)|bool(x) is True| +|assertFalse(x)|bool(x) is False| +|assertIs(a, b)|a is b| +|assertIsNot(a, b)|a is not b| +|assertIsNone(x)|x is None| +|assertIsNotNone(x)|x is not None| +|assertIn(a, b)|a in b| +|assertNotIn(a, b)|a not in b| +|assertIsInstance(a, b)|isinstance(a, b)| +|assertNotIsInstance(a, b)|not isinstance(a, b)| + + +### Growing your Tests + +Standard `unittest` tests are fine for most projects. As your programs grow and organization becomes more complex, you might want to consider an alternative testing framework or test runner. The 3rd party `nose2` and `pytest` modules are compatible with `unittest` but do things slightly differently. You can find more information in the [nose2 documentation](https://nose2.readthedocs.io/en/latest/) and [pytest documentation](https://docs.pytest.org/en/latest/). \ No newline at end of file diff --git a/website/content/03-intermediate-python/70-tests/90-exercise.md b/website/content/03-intermediate-python/70-tests/90-exercise.md new file mode 100644 index 0000000..25dc8f2 --- /dev/null +++ b/website/content/03-intermediate-python/70-tests/90-exercise.md @@ -0,0 +1,76 @@ ++++ +title = "Practice" +date = 2019-01-25T15:07:04-06:00 +weight = 10 +draft = false +pre = "⭐️ " ++++ + +## Tests + +Python comes with a very easy-to-use `unittest` library built in. Write a simple function that accepts two numbers, and returns `True` if the first number is evenly divisible by the second. + +```python +def divisible_by(check_number, divisor): + return check_number % divisor == 0 +``` + +Save your file as `divisible.py`. In a second file called `test_divisible.py`, create a `TestCase` using the `unittest` framework and use asserts to verify that the `divisible_by()`function returns the correct result. Don't forget to import your `divisible_by()` function. + +```python +import unittest +from divisible import divisible_by + +class TestCase(unittest.TestCase): + + def test_divisible_by(self): + self.assertTrue(divisible_by(10, 2)) + self.assertTrue(divisible_by(10, 3)) + + +if __name__ == '__main__': + unittest.main() +``` + +Name your file `test_divisible.py` and run it: + +```bash +(env) $ python test_divisible.py --verbose +``` + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +(env) $ python test_divisible.py --verbose +test_divisible_by (__main__.TestCase) ... FAIL + +====================================================================== +FAIL: test_divisible_by (__main__.TestCase) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "test_divisible.py", line 8, in test_divisible_by + self.assertTrue(divisible_by(10, 3)) +AssertionError: False is not true + +---------------------------------------------------------------------- +Ran 1 test in 0.001s + +FAILED (failures=1) +``` +{{% /expand%}} + +You should have gotten an error: `AssertionError: False is not true` caused by `self.assertTrue(divisible_by(10, 3))`. Makes sense, because 10 is not evenly divisible by 3. + +{{% notice tip %}} +Change `self.assertTrue` to `self.assertFalse` and your test should pass. +{{% /notice %}} + +```bash +(env) $ python test_divisible.py --verbose +test_divisible_by (__main__.TestCase) ... ok + +---------------------------------------------------------------------- +Ran 1 test in 0.000s + +OK +``` \ No newline at end of file diff --git a/website/content/03-intermediate-python/70-tests/_index.md b/website/content/03-intermediate-python/70-tests/_index.md new file mode 100644 index 0000000..d00c3a8 --- /dev/null +++ b/website/content/03-intermediate-python/70-tests/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Tests" +date = 2019-03-03T12:00:04-08:00 +weight = 70 +chapter = true +draft = false +pre = "7. " ++++ + +### Chapter 7 + +# Testing in Python + +Tests are a cornerstone of a solid Python program. Thankfully, because of Python's "batteries included" philosophy, all the tools we need for unit testing are included in the standard library. Let's learn the basics of Unit Testing. diff --git a/website/content/03-intermediate-python/80-web-frameworks/_index.md b/website/content/03-intermediate-python/80-web-frameworks/_index.md new file mode 100644 index 0000000..5bc4e77 --- /dev/null +++ b/website/content/03-intermediate-python/80-web-frameworks/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Web Frameworks" +date = 2019-03-03T12:00:11-08:00 +weight = 80 +chapter = true +draft = false +pre = "8. " ++++ + +### Chapter 8 + +# Web Frameworks + +In this final chapter, we'll learn about web frameworks, and practice all our new Python knowledge by building a basic web application with unit tests using Python and the `flask` framework. \ No newline at end of file diff --git a/website/content/03-intermediate-python/80-web-frameworks/basic-flask.md b/website/content/03-intermediate-python/80-web-frameworks/basic-flask.md new file mode 100644 index 0000000..84ebef0 --- /dev/null +++ b/website/content/03-intermediate-python/80-web-frameworks/basic-flask.md @@ -0,0 +1,144 @@ +--- +title: "Basic Flask" +date: 2019-03-10T19:30:36-07:00 +draft: false +weight: 1 +--- + +### Types of Web Frameworks in Python + +#### Django + +Django is a full-featured, high-level framework for building web apps. Django focuses on automating as much as possible, and many large-scale sites run on Django. + + +#### Flask + +Flask is a "microframework" for Python, allowing users to make basic backend APIs and webapps with a minimum of code. Flask is easy for beginners and not opinionated, so we'll be focusing on it for today's exercises. + +{{% notice note %}} +There are many more different frameworks for Python. You can find a more detailed list [here](https://wiki.python.org/moin/WebFrameworks). +{{% /notice %}} + +#### Pyramid + +Pyramid is a fast, yet advanced framework, and a successor to the older Pylons framework. Pyramid is open-source and actively developed. + + +## A Basic Flask App + +{{% notice note %}} +Make sure you have Flask installed: `python -m pip install flask` +{{% /notice %}} + +A very basic Flask app looks something like this: + +```python +from flask import Flask +app = Flask(__name__) + +@app.route("/") +def hello(): + return "Hello World!" +``` + +First, we import `flask`, and create the `app` object using the name of the file (`__name__`). We add a route using `@app.route("/")` above our `hello()` function - this tells Flask to respond to requests for "/" (the root of your webserver) by running `hello()` and returning "Hello World!" to the user. + +To run this, you could copy this code into a file called `hello.py`, set the `FLASK_APP` environment variable, and run it. Then just point your browser to the URL it gives you. + +```bash +$ FLASK_APP=hello.py flask run + * Running on http://localhost:5000/ +``` + +### Routing + +![](/03-intermediate-python/80-web-frameworks/images/request-response.jpeg?width=40pc) +Image from [Zapier Guide to APIs](https://zapier.com/learn/apis/chapter-1-introduction-to-apis/). + +Flask uses the `route()` decorator to declare routes. For example, the above code uses `app.route("/")` to declare a route for "/" that resolves to `hello()`, but you can use any path, or even accept variables in your routes: + +```python +@app.route("/my/secret/page") +def secret(): + return "Shh!" + +@app.route("/user/") +def user_page(username): + return f"Welcome, {username}!" + +@app.route("/blog/post/") +def show_post(post_id): + return f"This is the page for post # {post_id}" +``` + +More information about routing can be found in the [Flask documentation](http://flask.pocoo.org/docs/1.0/quickstart/#routing). + +### Returning Data + +The simplest way to return data is to return a string with `return` at the end of your function. This pushes the string back to the user, who sees it as plain text in their browser. You'll probably want to make use of HTML in your webapps though, so you'll want to look at template rendering. + +A template is just an HTML file that lives in a folder called `templates` next to your flask app Python file. To return an HTML file instead of plain text, just return the `render_template()` function. For example: + +```python +from flask import render_template + +@app.route('/') +def index(): + return render_template('index.html') +``` + +Flask also supports a template language called `Jinja` that allows you to populate your HTML files with data from your Flask app at render time. A very simple HTML template might look like this: + +```html + +Hello from Flask +{% if name %} +

Hello {{ name }}!

+{% else %} +

Hello, World!

+{% endif %} +``` + +Notice the special code in `{% brackets %}`, this acts as a very simple programming language. Let's add a matching Flask function: + +```python +from flask import render_template + +@app.route('/hello/') +@app.route('/hello/') +def hello(name=None): + return render_template('hello.html', name=name) +``` + +Now, if we call `/hello`, we'll see `Hello, World!`, and if we call `/hello/nina`, we'll see `Hello nina!`. This is a very simple example, you can find more details in the [Flask documentation](http://flask.pocoo.org/docs/1.0/quickstart/#rendering-templates) and the [Jinja documentation](http://jinja.pocoo.org/docs/2.10/templates/). + + +### Static Files + +Serving static files alongside your dynamic Flask code is easy - just create a folder called `static` next to your Flask code, and any files you put in there will be available at `/static/`. + + +### Debug Mode + +Flask has a very handy built-in debugger that makes it very easy to see what went wrong when you have an error in your application. You can activate the debugger by setting the `FLASK_ENV` variable: + +```bash +$ export FLASK_APP=my_application +$ export FLASK_ENV=development +$ flask run +``` + +### Using a Database + +Flask provides a useful mechanism for accessing database objects. This makes it easy to use databases to store data for your dynamic webapp. More information is available in the [Flask documentation](http://flask.pocoo.org/docs/1.0/tutorial/database/). + +### Deploying your Web App + +Is your app ready for the big time? There are many different options for deploying your Flask app to a real webserver - you can read about some of your options in the [Flask documentation](http://flask.pocoo.org/docs/1.0/deploying/#deployment). + +#### More About Flask + +{{% notice note %}} +We'll be covering just the basics of `flask` today. To deep dive and learn more, the [`flask` Mega Tutorial](https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world) is a great resource to check out *after* class. +{{% /notice %}} \ No newline at end of file diff --git a/website/content/03-intermediate-python/80-web-frameworks/final-exercise.md b/website/content/03-intermediate-python/80-web-frameworks/final-exercise.md new file mode 100644 index 0000000..8b0b065 --- /dev/null +++ b/website/content/03-intermediate-python/80-web-frameworks/final-exercise.md @@ -0,0 +1,392 @@ ++++ +title = "Practice" +date = 2019-01-25T15:07:04-06:00 +weight = 200 +draft = false +pre = "⭐️ " ++++ + +### Installing Requirements + +Our web application has two required external libraries, `flask`, and `requests`. As our list of dependencies becomes more complicated, we want to list them in a file called `requirements.txt` and include it with our project. That way, our code can be reused by others. + +Open and look at the `requirements.txt` file. The name of each dependency is on a new line. + +{{% notice note %}} +As you advance in your Python journey, you can use the more advanced [`pipenv`](https://pipenv.readthedocs.io/en/latest/) tool to handle complicated dependencies. +{{% /notice %}} + +To install all the dependencies from our requirements file, pass the `-r` flag to `pip`, and the name of the file (in this case, it's `requirements.txt`): + +```bash +(env)$ python -m pip install -r requirements.txt +``` + +### Review + +Let's review what we learned over the last two days and put it all together. + +For our final exercise today, we're going to build on yesterday's final exercise, where we wrote a program to query the GitHub API for a list of repos for certain programming languages, sorted by number of stars. We'll be turning yesterday's exercise into a Flask webapp. Flask is a simple and popular framework for creating basic web apps in Python. + +First, create a new folder for this exercise, called `day_two_final`. You'll need two folders of static content - CSS and HTML files - to make this work. You can [download them here](https://learnpython.dev/code/day_two_final_exercise/static_files.zip). Unzip your `static_files.zip` file and copy your `static` and `template` folders to your `day_two_final` folder. + +Next, create a folder called `repos`. This is where we'll create our custom module. Inside this folder we'll create three files: `exceptions.py`, `models.py`, and `api.py`. + +In `exceptions.py`, we'll create a custom exception class to handle errors with the GitHub API. +In `models.py`, we'll create a `GitHubRepo` class to more easily represent the results from the GitHub API search. +And `api.py` will hold our functions for querying the GitHub API. + +Finally, we'll add an `app.py` file in the root level, to run our Flask app. Your folder should look like this: + +```text +day_two_final +├── app.py +├── repos +│ ├── exceptions.py +│ ├── models.py +│ └── api.py +├── static +│ ├── favicon.png +│ └── style.css +└── templates + ├── error.html + └── index.html +``` + +### `exceptions.py` + +Let's start with building a custom exception to handle API errors. Remember that is `response.status_code` is anything but `200`, you can consider that an error. Create a `GitHubApiException` class that subclasses `Exception`. Have it accept a `status_code` argument, and use that to create a custom message (you can copy the error strings we used yesterday). Pass the message to `Exception` + +{{%expand "You should have something like this:" %}} + +```python +class GitHubApiException(Exception): + + def __init__(self, status_code): + if status_code == 403: + message = "Rate limit reached. Please wait a minute and try again." + else: + message = f"HTTP Status Code was: {status_code}." + + super().__init__("A GitHub API Error Occurred: " + message) +``` + +{{%/expand%}} + + +### `models.py` + +Next, let's build our "model", the `GitHubRepo` class. For this, we want to accept three arguments (`name`, `language`, and `num_stars`) and store them as instance variables (using `self`). To have a user-friend way to print our repo information, add a `__str__()` method that prints a message with the three repo parameters. For completeness, see if you can add a `__repr__()` method that returns the Python code needed to recreate this object. + +{{%expand "You should have something like this:" %}} + +```python +class GitHubRepo: + + def __init__(self, name, language, num_stars): + self.name = name + self.language = language + self.num_stars = num_stars + + def __str__(self): + return f"-> {self.name} is a {self.language} repo with {self.num_stars} stars." + + def __repr__(self): + return f'GitHubRepo({self.name}, {self.language}, {self.num_stars})' +``` + +{{%/expand%}} + + +### `api.py` + +In our `api.py` file, we're going to copy in our `create_query()` function and the `repos_with_most_stars()` function from yesterday. + +In `create_query()`, see if you can clean up the code a little by replacing the `for` loop with a string join that accepts a list comprehension. + +Clean up your `repos_with_most_stars()` function by using `raise` to throw your `GitHubAPIException` if the `status_code` does not equal `200`. Then, instead of returning items directly from the response json, see if you can use a list comprehension to create and return a list of `GitHubRepo` objects. + +Don't forget to import your `GitHubApiException`, your `GitHubRepo` class, and the `requests` module. + +{{%expand "You should have something like this:" %}} + +```python +from repos.exceptions import GitHubApiException +from repos.models import GitHubRepo +import requests +GITHUB_API_URL = "https://api.github.com/search/repositories" + +def create_query(languages, min_stars): + """ + Create the query string for the GitHub search API, + based on the minimum amount of stars for a project, and + the provided programming languages. + """ + # Notice we are calling .strip() on each language, to clear it of leading + # and trailing whitespace + query = " ".join(f"language:{language.strip()}" for language in languages) + query = query + f" stars:>{min_stars}" + return query + +def repos_with_most_stars(languages, min_stars=40000, sort="stars", order="desc"): + query = create_query(languages, min_stars) + parameters = {"q": query, "sort": sort, "order": order} + print(parameters) + response = requests.get(GITHUB_API_URL, params=parameters) + + if response.status_code != 200: + raise GitHubApiException(response.status_code) + + response_json = response.json() + items = response_json["items"] + return [GitHubRepo(item["name"], item["language"], item["stargazers_count"]) for item in items] +``` + +{{%/expand%}} + + +### `app.py` + +Finally, let's tie it all together with our `app.py` file. We'll start off with some boilerplate - we'll need to import a few things from `flask`, as well as our `GitHubApiException` and our `repos_with_most_stars()` function: + +```python +from flask import Flask, render_template, request + +from repos.exceptions import GitHubApiException +from repos.api import repos_with_most_stars +``` + +Next, we'll create the flask `app` object. We'll also create a list of all the available languages that the user of our web app can choose from. It will help us keep track of if they're selected or not. + +```python +app = Flask(__name__) + +available_languages = ["Python", "JavaScript", "Ruby", "Java"] +``` + + +Next, we'll need a function that gets called when the root url for our website, or `/` is requested by the user + +. We'll start with the `@app.route()` decorator - we didn't cover decorators in this class, but just know that this signals to Flask that this `index()` function should be called to handle any `GET` or `POST` requests to the URL `/`. + +```python +@app.route('/', methods=['POST', 'GET']) +def index(): + if request.method == 'GET': + # code for a GET + pass + elif request.method == 'POST': + # code for a POST + pass +``` + + +We need to figure out which languages we have selected to determine which repos to display. + +We'll check the `request.method` variable to determine what kind of request it was - if it was a `GET` request, we'll just display whichever repos were selected last (or all of them if this is the first request). + +If it's a `POST`, we'll grab the `languages` variable from the request form and use it to populate our `selected_languages` list: + +```python + if request.method == 'GET': + # Use the list of all languages + selected_languages = available_languages + elif request.method == 'POST': + # Use the languages we selected in the request form + selected_languages = request.form.getlist("languages") +``` + +Now, we just need to get our results and render our website. Call the `repos_with_most_stars()` function in `api.py` and pass it our `selected_languages`. + +Then, we'll return our flask `render_template()` function and pass it our list of selected languages, available languages, and our results. + +```python + results = repos_with_most_stars(selected_languages) + + return render_template( + 'index.html', + selected_languages=selected_languages, + available_languages=available_languages, + results=results) +``` + +Finally, we'll add a custom error handler renders a special website (`error.html`) if we receive a `GitHubApiException`: + +```python +@app.errorhandler(GitHubApiException) +def handle_api_error(error): + return render_template('error.html', message=error) +``` + +Phew. +{{%expand "Here's the final `app.py` file that you should have:" %}} + +```python +from flask import Flask, render_template, request + +from repos.api import repos_with_most_stars +from repos.exceptions import GitHubApiException + +app = Flask(__name__) + +available_languages = ["Python", "JavaScript", "Ruby", "Java"] + + +@app.route('/', methods=['POST', 'GET']) +def index(): + if request.method == 'GET': + # Use the list of all languages + selected_languages = available_languages + elif request.method == 'POST': + # Use the languages we selected in the request form + selected_languages = request.form.getlist("languages") + + results = repos_with_most_stars(selected_languages) + + return render_template( + 'index.html', + selected_languages=selected_languages, + available_languages=available_languages, + results=results) + + +@app.errorhandler(GitHubApiException) +def handle_api_error(error): + return render_template('error.html', message=error) + +``` + +{{%/expand%}} + + +### Run your Webapp + +At last, make sure you're in your root `day_two_final` directory `$ cd day_two_final` and start your webapp with debug mode. + +```bash +(env) $ export FLASK_ENV=development; python3 -m flask run + * Environment: development + * Debug mode: on + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + * Restarting with stat + * Debugger is active! + * Debugger PIN: 165-366-879 +``` + +Point your web browser to the given URL (http://127.0.0.1:5000/ in this case) and you should see your list of repos sorted by number of stars. Play with the check boxes and the submit button on the left and see how the repo list changes. Congrats, you wrote a webapp in Python! + + +### Bonus: Unit Tests + +Let's add some quick unit tests to our code, to make sure we don't introduce any regressions later on. Create a file called `test.py` in your `day_two_final` folder. Your folder should look like this: + +```text +day_two_final +├── app.py +├── test.py +├── repos +│ ├── exceptions.py +│ ├── models.py +│ └── api.py +├── static +│ ├── favicon.png +│ └── style.css +└── templates + ├── error.html + └── index.html +``` + +Create a new `unittest.TestCase` called `TestCreateQuery`. Inside, make a method called `test_create_query()`. In this method, create a list of language names and an `int` representing the a minimum number of stars. By looking at the `create_query()` function, see if you can figure out what the correct query string should be. Call the `create_query()` function with your test variables and use `self.assertEqual()` to make sure they match. Don't forget to `import repos.api` and add your `unittest.main()` invocation. + +{{%expand "You should have something like this:" %}} +```python +import repos.api +import unittest + +class TestCreateQuery(unittest.TestCase): + + def test_create_query(self): + test_languages = ["Python", "Ruby", "Java"] + test_min_stars = 10000 + + expected = "language:Python language:Ruby language:Java stars:>10000" + result = repos.api.create_query(test_languages, test_min_stars) + + self.assertEqual(result, expected, "Unexpected result from create_query") + +if __name__ == "__main__": + unittest.main() +``` +{{%/expand%}} + +Run your test: + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +$ python test.py --verbose +test_create_query (__main__.TestCreateQuery) ... ok + +---------------------------------------------------------------------- +Ran 1 test in 0.000s + +OK +``` + +{{%/expand%}} + +Let's make two more quick tests of our GitHubApiException. Make a new `TestCase` with two test functions. Name the class and functions using the same naming convention we've been using. For the first, we'll use a fake `status_code` of `403`. Create a `GitHubApiException` object and pass it the `status_code`. Check to see if the string "Rate limit" exists in the string representation of your exception (hint: use `str()`). + +For the second test, we'll do the same thing with a `status_code` of `500`. This time, we'll check to see if "500" exists in the exception string. + +{{%expand "You should have something like this:" %}} + +```python +import repos.api +import repos.exceptions +import unittest + +class TestCreateQuery(unittest.TestCase): + + def test_create_query(self): + test_languages = ["Python", "Ruby", "Java"] + test_min_stars = 10000 + + expected = "language:Python language:Ruby language:Java stars:>10000" + result = repos.api.create_query(test_languages, test_min_stars) + + self.assertEqual(result, expected, "Unexpected result from create_query") + +class TestGitHubApiException(unittest.TestCase): + + def test_exception_403(self): + status_code = 403 + exception = repos.exceptions.GitHubApiException(status_code) + self.assertTrue("Rate limit" in str(exception), "'Rate limit' not found") + + def test_exception_500(self): + status_code = 500 + exception = repos.exceptions.GitHubApiException(status_code) + self.assertTrue(str(status_code) in str(exception)) + +if __name__ == "__main__": + unittest.main() +``` + +{{%/expand%}} + +{{%expand "Here's what you should have seen on your command line:" %}} + +```bash +$ python test.py --verbose +test_create_query (__main__.TestCreateQuery) ... ok +test_exception_403 (__main__.TestGitHubApiException) ... ok +test_exception_500 (__main__.TestGitHubApiException) ... ok + +---------------------------------------------------------------------- +Ran 3 tests in 0.001s + +OK +``` + +{{%/expand%}} diff --git a/website/content/03-intermediate-python/80-web-frameworks/images/request-response.jpeg b/website/content/03-intermediate-python/80-web-frameworks/images/request-response.jpeg new file mode 100644 index 0000000..9a1d293 Binary files /dev/null and b/website/content/03-intermediate-python/80-web-frameworks/images/request-response.jpeg differ diff --git a/website/content/03-intermediate-python/90-Conclusion/_index.md b/website/content/03-intermediate-python/90-Conclusion/_index.md new file mode 100644 index 0000000..c3d87f0 --- /dev/null +++ b/website/content/03-intermediate-python/90-Conclusion/_index.md @@ -0,0 +1,28 @@ +--- +title: "Wrapping Up" +date: 2019-02-10T18:39:32-08:00 +draft: false +--- + +## Source Control + +I highly recommend that you use source control while working on your Python projects. GitHub is a popular and free option. + +Source control lets you track changes to your project. You can use that to your advantage to commit early and often and track changes to your project. + +If you're not familiar with how to use git or GitHub, you can watch my [Git In-depth Frontend Masters class](https://frontendmasters.com/courses/git-in-depth/). For Python projects, make sure that you use the correct `.gitignore` file. GitHub provides a [free template that you can use](https://github.com/github/gitignore/blob/master/Python.gitignore). + +## Next Steps + +Now you know the basics, just enough to get started with working with APIs in Python with the `requests` library. You know just enough to see how powerful Python can be. You can chain all of these requests together into a fully functioning program that does so much more. + +The next topics to study are: +* [How to authenticate to APIs](http://docs.python-requests.org/en/master/user/authentication/) +* How to use the other HTTP methods, just as POST to create resources, or PUT to update them +* Read the [requests Quick Start Guide](http://docs.python-requests.org/en/master/user/quickstart/) + +## Additional Resources + +* [Flask Mega Tutorial](https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world) - For learning the A-Z of Python web apps +* [Hitchhikers Guide to Python](https://docs.python-guide.org/) - free Book +* Watch the video for this course - [Intro to Python](https://frontendmasters.com/workshops/intro-to-python/) & [Intermediate Python](https://frontendmasters.com/workshops/intermediate-python/) \ No newline at end of file diff --git a/website/content/03-intermediate-python/_index.md b/website/content/03-intermediate-python/_index.md new file mode 100644 index 0000000..1716579 --- /dev/null +++ b/website/content/03-intermediate-python/_index.md @@ -0,0 +1,28 @@ ++++ +title = "Intermediate Python" +date = 2019-03-03T11:55:16-08:00 +weight = 3 +chapter = true +draft = false +pre = "Day 2. " ++++ + +### Day 2 + +# Intermediate Python + +During day two, we'll start getting more comfortable with some of the more advanced aspects of Python, like classes, list comprehensions, and the standard library. At the end of the day, you'll know enough to build a small interactive API using the Flask web framework. + +{{% notice tip %}} +To fully participate in todays class, you must have VS Code set up locally, along with Python version >= 3.7. You must be familiar with all the concepts from Day 1, including running Python programs, simple and complex types, boolean logic, control statements, and looping. If you're not sure, start with [Day 1](../02-introduction-to-python). +{{% /notice %}} + +# Day 2 Overview + +By coding along with me in this workshop, you'll: + +- Use object-oriented programming to organize your code. +- Diagnose problems in your Python programs by understanding Exceptions. +- Work with new features in Python3 that make Python easier than ever, such as f-strings. +- Learn about generators, a Python feature that allows you to loop over large data sets in a memory efficient way. +- Learn how to build interactive APIs and websites efficiently using the Flask web framework. \ No newline at end of file diff --git a/website/content/_index.md b/website/content/_index.md new file mode 100644 index 0000000..67641ca --- /dev/null +++ b/website/content/_index.md @@ -0,0 +1,24 @@ +--- +title: Learn Python +weight: 1 +chapter: true +--- + +# Learn Python + +Welcome to Introduction and Intermediate Python, recorded for Frontend Masters. + +Created by Nina Zakharenko. @nnja. + +Watch the accompanying screencast via subscription on [FrontendMasters.com](https://frontendmasters.com/teachers/nina-zakharenko/). + +{{< figure src="/images/fem.png" title="Watch the course" link="https://frontendmasters.com/courses/python/">}} + +Stay up to date with me on LinkedIn, or to contact me directly, please send me an email at learnpython@nnja.io. + +You can find the content and exercises on the Github repo. + + +The course content is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.Creative Commons License + +I'd like to encourage learning individuals to learn Python from the course material for free, but if you'd like to use the material for your own workshop or for commercial use, please consider supporting my work and [watch the screencast](https://frontendmasters.com/teachers/nina-zakharenko/) instead. \ No newline at end of file diff --git a/website/content/credits.md b/website/content/credits.md new file mode 100644 index 0000000..2a1bb05 --- /dev/null +++ b/website/content/credits.md @@ -0,0 +1,23 @@ +--- +title: Credits +disableToc: true +--- + + +### Themes +* Special thanks to the hugo [learn theme](https://github.com/matcornic/hugo-theme-learn). + +### Tooling +* [Hugo](https://gohugo.io/) + +### Other +* [Pixel Art](https://www.pixilart.com/art/python-robotics-pixel-art-5c4a112fde67eab) + +### CSS Styles For Final Exercise +* [Buttons](https://codepen.io/wilder_taype/pen/LeoQEb) +* [Checkboxes](https://codepen.io/wilder_taype/pen/pNXwMW) +* [Tables](https://codepen.io/gmb/pen/xVGYZw) +* [Error Page](https://codepen.io/akashrajendra/pen/JKKRvQ) + + +##### Special thanks to my [❤️ Max](http://twitter.com/mhenstell) for moral support while I worked on this class. \ No newline at end of file diff --git a/website/layouts/partials/logo.html b/website/layouts/partials/logo.html new file mode 100644 index 0000000..12abafd --- /dev/null +++ b/website/layouts/partials/logo.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/layouts/partials/menu-footer.html b/website/layouts/partials/menu-footer.html new file mode 100644 index 0000000..31be334 --- /dev/null +++ b/website/layouts/partials/menu-footer.html @@ -0,0 +1,3 @@ +

Built with the Learn theme.

+
+Creative Commons License
The course content is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. \ No newline at end of file diff --git a/website/layouts/partials/meta_twitter.html b/website/layouts/partials/meta_twitter.html new file mode 100644 index 0000000..6ec22f6 --- /dev/null +++ b/website/layouts/partials/meta_twitter.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/website/layouts/shortcodes/todo.html b/website/layouts/shortcodes/todo.html new file mode 100644 index 0000000..3662553 --- /dev/null +++ b/website/layouts/shortcodes/todo.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/public/categories/index.xml b/website/public/categories/index.xml new file mode 100644 index 0000000..6e385a4 --- /dev/null +++ b/website/public/categories/index.xml @@ -0,0 +1,14 @@ + + + + Categories on My New Hugo Site + http://example.org/categories/ + Recent content in Categories on My New Hugo Site + Hugo -- gohugo.io + en-us + + + + + + \ No newline at end of file diff --git a/website/public/index.xml b/website/public/index.xml new file mode 100644 index 0000000..884a3c9 --- /dev/null +++ b/website/public/index.xml @@ -0,0 +1,14 @@ + + + + My New Hugo Site + http://example.org/ + Recent content on My New Hugo Site + Hugo -- gohugo.io + en-us + + + + + + \ No newline at end of file diff --git a/website/public/sitemap.xml b/website/public/sitemap.xml new file mode 100644 index 0000000..60e0486 --- /dev/null +++ b/website/public/sitemap.xml @@ -0,0 +1,20 @@ + + + + + http://example.org/categories/ + 0 + + + + http://example.org/ + 0 + + + + http://example.org/tags/ + 0 + + + \ No newline at end of file diff --git a/website/public/tags/index.xml b/website/public/tags/index.xml new file mode 100644 index 0000000..a8ab33c --- /dev/null +++ b/website/public/tags/index.xml @@ -0,0 +1,14 @@ + + + + Tags on My New Hugo Site + http://example.org/tags/ + Recent content in Tags on My New Hugo Site + Hugo -- gohugo.io + en-us + + + + + + \ No newline at end of file diff --git a/website/static/_redirects b/website/static/_redirects new file mode 100644 index 0000000..aa54467 --- /dev/null +++ b/website/static/_redirects @@ -0,0 +1,2 @@ +# Redirect default subdomain to primary domain +https://learnpython.netlify.com/* https://www.learnpython.dev/:splat 301! \ No newline at end of file diff --git a/website/static/code/cities.json b/website/static/code/cities.json new file mode 100644 index 0000000..32b8933 --- /dev/null +++ b/website/static/code/cities.json @@ -0,0 +1,21 @@ +[{ + "name": "New York", + "pop": 8550405 + }, + { + "name": "Los Angeles", + "pop": 3971883 + }, + { + "name": "Chicago", + "pop": 2720546 + }, + { + "name": "Houston", + "pop": 2296224 + }, + { + "name": "Philadelphia", + "pop": 1567442 + } +] \ No newline at end of file diff --git a/website/static/code/day_one_class.py b/website/static/code/day_one_class.py new file mode 100644 index 0000000..22d9cb8 --- /dev/null +++ b/website/static/code/day_one_class.py @@ -0,0 +1,70 @@ +""" +A small Python program that uses the GitHub search API to list +the top projects by language, based on stars. + +GitHub Search API documentation: https://developer.github.com/v3/search/ + +Additional parameters for searching repos can be found here: +https://help.github.com/en/articles/searching-for-repositories#search-by-number-of-stars + +Note: The API will return results found before a timeout occurs, +so results may not be the same across requests, even with the same query. + +Requests to this endpoint are rate limited to 10 requests per +minute per IP address. +""" + +import requests + +GITHUB_API_URL = "https://api.github.com/search/repositories" + + +def create_query(languages, min_stars=50000): + """ + Create the query string for the GitHub search API, + based on the minimum amount of stars for a project, and + the provided programming languages. + + An example search query looks like: + stars:>50000 language:python language:javascript + """ + query = f"stars:>{min_stars} " + + for language in languages: + query += f"language:{language} " + + return query + + +def repos_with_most_stars(languages, sort="stars", order="desc"): + query = create_query(languages) + + # Define the parameters we want to be part of our URL + parameters = {"q": query, "sort": sort, "order": order} + + # Pass in the query and the parameters as part of the request. + response = requests.get(GITHUB_API_URL, params=parameters) + status_code = response.status_code + + # Check if the rate limit was hit. Applies only for students running this code + # in the in-person course. + if status_code == 403: + raise RuntimeError("Rate limit reached. Please wait a minute and try again.") + if status_code != 200: + raise RuntimeError(f"An error occurred. HTTP Status Code was: {status_code}.") + else: + response_json = response.json() + records = response_json["items"] + return records + + +if __name__ == "__main__": + languages = ["python", "javascript", "ruby"] + results = repos_with_most_stars(languages) + + for result in results: + language = result["language"] + stars = result["stargazers_count"] + name = result["name"] + + print(f"-> {name} is a {language} repo with {stars} stars.") diff --git a/website/static/code/day_one_min.py b/website/static/code/day_one_min.py new file mode 100644 index 0000000..864bec9 --- /dev/null +++ b/website/static/code/day_one_min.py @@ -0,0 +1,46 @@ +""" +A small Python program that uses the GitHub search API to list +the top projects by language, based on stars. +""" + +import requests + +GITHUB_API_URL = "https://api.github.com/search/repositories" + + +def create_query(languages, min_stars=50000): + query = f"stars:>{min_stars} " + + for language in languages: + query += f"language:{language} " + + # a sample query looks like: "stars:>50 language:python language:javascript" + return query + + +def repos_with_most_stars(languages, sort="stars", order="desc"): + query = create_query(languages) + params = {"q": query, "sort": sort, "order": order} + + response = requests.get(GITHUB_API_URL, params=params) + + print(response.url) + status_code = response.status_code + + if status_code != 200: + raise RuntimeError(f"An error occurred. HTTP Code: {status_code}.") + else: + response_json = response.json() + return response_json["items"] + + +if __name__ == "__main__": + languages = ["python", "javascript", "ruby"] + results = repos_with_most_stars(languages) + + for result in results: + language = result["language"] + stars = result["stargazers_count"] + name = result["name"] + + print(f"-> {name} is a {language} repo with {stars} stars.") diff --git a/website/static/code/day_two_final_exercise.zip b/website/static/code/day_two_final_exercise.zip new file mode 100644 index 0000000..fa64db3 Binary files /dev/null and b/website/static/code/day_two_final_exercise.zip differ diff --git a/website/static/code/day_two_final_exercise/app.py b/website/static/code/day_two_final_exercise/app.py new file mode 100644 index 0000000..ad8ad9d --- /dev/null +++ b/website/static/code/day_two_final_exercise/app.py @@ -0,0 +1,40 @@ +""" +A Simple Flask Web Application interface +For viewing popular GitHub Repos sorted by stars using the +GitHub Search API. + +To run: +(env) $ python -m pip install -r requirements.txt +(env) $ export FLASK_ENV=development; python3 -m flask run +""" +from flask import Flask, render_template, request + +from repos.api import repos_with_most_stars +from repos.exceptions import GitHubApiException + +app = Flask(__name__) + +available_languages = ["Python", "JavaScript", "Ruby", "Java"] + + +@app.route('/', methods=['POST', 'GET']) +def index(): + if request.method == 'GET': + # Use the list of all languages + selected_languages = available_languages + elif request.method == 'POST': + # Use the languages we selected in the request form + selected_languages = request.form.getlist("languages") + + results = repos_with_most_stars(selected_languages) + + return render_template( + 'index.html', + selected_languages=selected_languages, + available_languages=available_languages, + results=results) + + +@app.errorhandler(GitHubApiException) +def handle_api_error(error): + return render_template('error.html', message=error) diff --git a/website/static/code/day_two_final_exercise/repos/__init__.py b/website/static/code/day_two_final_exercise/repos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/website/static/code/day_two_final_exercise/repos/api.py b/website/static/code/day_two_final_exercise/repos/api.py new file mode 100644 index 0000000..20837f4 --- /dev/null +++ b/website/static/code/day_two_final_exercise/repos/api.py @@ -0,0 +1,34 @@ +import requests + +from repos.exceptions import GitHubApiException +from repos.models import GitHubRepo + + +GITHUB_API_URL = "https://api.github.com/search/repositories" + + +def create_query(languages, min_stars): + """ + Create the query string for the GitHub search API, + based on the minimum amount of stars for a project, and + the provided programming languages. + """ + # Notice we are calling .strip() on each language, + # to clear it of leading and trailing whitespace + query = " ".join(f"language:{language.strip()}" for language in languages) + query = query + f" stars:>{min_stars}" + return query + + +def repos_with_most_stars(languages, min_stars=40000, sort="stars", order="desc"): + query = create_query(languages, min_stars) + parameters = {"q": query, "sort": sort, "order": order} + print(parameters) + response = requests.get(GITHUB_API_URL, params=parameters) + + if response.status_code != 200: + raise GitHubApiException(response.status_code) + + response_json = response.json() + items = response_json["items"] + return [GitHubRepo(item["name"], item["language"], item["stargazers_count"]) for item in items] diff --git a/website/static/code/day_two_final_exercise/repos/exceptions.py b/website/static/code/day_two_final_exercise/repos/exceptions.py new file mode 100644 index 0000000..0ba8343 --- /dev/null +++ b/website/static/code/day_two_final_exercise/repos/exceptions.py @@ -0,0 +1,13 @@ +""" +GitHub API Application: Custom Exception Classes +""" + +class GitHubApiException(Exception): + + def __init__(self, status_code): + if status_code == 403: + message = "Rate limit reached. Please wait a minute and try again." + else: + message = f"HTTP Status Code was: {status_code}." + + super().__init__("A GitHub API Error Occurred: " + message) diff --git a/website/static/code/day_two_final_exercise/repos/models.py b/website/static/code/day_two_final_exercise/repos/models.py new file mode 100644 index 0000000..cdee487 --- /dev/null +++ b/website/static/code/day_two_final_exercise/repos/models.py @@ -0,0 +1,19 @@ +""" +GitHub API Application: Custom Model Classes +""" + +class GitHubRepo: + """ + A class used to represent a single GitHub Repository. + """ + + def __init__(self, name, language, num_stars): + self.name = name + self.language = language + self.num_stars = num_stars + + def __str__(self): + return f"-> {self.name} is a {self.language} repo with {self.num_stars} stars." + + def __repr__(self): + return f'GitHubRepo({self.name}, {self.language}, {self.num_stars})' diff --git a/website/static/code/day_two_final_exercise/requirements.txt b/website/static/code/day_two_final_exercise/requirements.txt new file mode 100644 index 0000000..5eaf725 --- /dev/null +++ b/website/static/code/day_two_final_exercise/requirements.txt @@ -0,0 +1,2 @@ +flask +requests \ No newline at end of file diff --git a/website/static/code/day_two_final_exercise/static/favicon.png b/website/static/code/day_two_final_exercise/static/favicon.png new file mode 100644 index 0000000..42c4379 Binary files /dev/null and b/website/static/code/day_two_final_exercise/static/favicon.png differ diff --git a/website/static/code/day_two_final_exercise/static/style.css b/website/static/code/day_two_final_exercise/static/style.css new file mode 100644 index 0000000..0a0b219 --- /dev/null +++ b/website/static/code/day_two_final_exercise/static/style.css @@ -0,0 +1,229 @@ +/* +CSS Styles for GitHub Repos by Stars exercise site. +Sorry for the bad css! I'm a "full-stack" developer. +*/ + +/* +Checkbox Style From: https://codepen.io/wilder_taype/pen/pNXwMW +*/ + +@import url(https://fonts.googleapis.com/css?family=Roboto:400,700); +*{font-family: 'Roboto', sans-serif;} + +.option-input { + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + -o-appearance: none; + appearance: none; + position: relative; + top: 13.33333px; + right: 0; + bottom: 0; + left: 0; + height: 40px; + width: 40px; + transition: all 0.15s ease-out 0s; + background: #cbd1d8; + border: none; + color: #fff; + cursor: pointer; + display: inline-block; + margin-right: 0.5rem; + outline: none; + position: relative; + z-index: 1000; +} +.option-input:hover { + background: #9faab7; +} +.option-input:checked { + background: #40e0d0; +} +.option-input:checked::before { + height: 40px; + width: 40px; + position: absolute; + content: '✔'; + display: inline-block; + font-size: 26.66667px; + text-align: center; + line-height: 40px; +} +.option-input:checked::after { + background: #40e0d0; + content: ''; + display: block; + position: relative; + z-index: 100; +} + +body { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: box; + background: #e8ebee; + color: #9faab7; + font-family: "Helvetica Neue", "Helvetica", "Roboto", "Arial", sans-serif; + text-align: center; +} +body div { + padding: 5rem; +} +body label { + display: block; + line-height: 40px; + text-align: left; +} + +/* +Button Style From: https://codepen.io/wilder_taype/pen/LeoQEb +*/ + +button { + display: block; + border-radius: 3px; + margin: 15px 5px; + color: #fff; + cursor: pointer; + box-shadow: 3px 7px 7px 4px rgba(237,80,83,.5), 1px 0 0 rgba(0,0,0,.15); + background: #ed5053; + padding: 20px; + flex-grow: 1; + width: 100px; + border: none; + font-size: 1.6rem; + line-height: 1.6; +} + +/* +Table Style From: https://codepen.io/gmb/pen/xVGYZw +*/ + +html { + font-size: 62.5%; +} + +body { + font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 1.6rem; + line-height: 1.6; + color: #20262e; + background-color: #28b1de; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.container { + max-width: 800px; +} + +h1 { + text-align: center; + font-size: 3rem; + color: rgba(255, 255, 255, .8); + text-transform: uppercase; + line-height: 1.375; + margin: 0 0 2.4rem 0; + letter-spacing: 1px +} + +h2 { + text-align: right; + font-size: 1.2rem; + text-transform: uppercase; + line-height: 1.375; + margin: 0; + letter-spacing: 1px +} + +table { + width: 100%; + min-width: 300px; + margin-bottom: 2.4rem; + background-color: #20262e; + color: #fff; + overflow: hidden; +} + +table tr:nth-child(even) { + background-color: rgb(46, 53, 62); +} + +table th, +table td:before { + color: #28b1de; +} + +table th { + display: none; +} + +table th, +table td { + margin: .5rem 2rem; + text-align: left; +} + +table td { + display: block; + font-size: 90%; +} + +table td:first-child { + padding-top: 1rem; +} + +table td:last-child { + padding-bottom: 1rem; +} + +table td:before { + content: attr(data-th) ':\00a0'; + font-weight: bold; + min-width: 8rem; + display: inline-block; +} + + +@media (min-width: 600px) { + table td:before { + display: none; + } + table th, + table td { + display: table-cell; + } + table th, + table td, + table td:first-child, + table td:last-child { + padding: 1.5rem 2rem; + } +} + +/* +Error Page Style From: https://codepen.io/akashrajendra/pen/JKKRvQ +*/ + +#error{ + font-family: 'Lato', sans-serif; + color: #888; + margin: 0; + display: table; + width: 100%; + height: 100vh; + text-align: center; +} + +.fof{ + display: table-cell; + vertical-align: middle; +} + +.fof h1{ + font-size: 50px; + display: inline-block; + padding-right: 12px; +} \ No newline at end of file diff --git a/website/static/code/day_two_final_exercise/static_files.zip b/website/static/code/day_two_final_exercise/static_files.zip new file mode 100644 index 0000000..707f131 Binary files /dev/null and b/website/static/code/day_two_final_exercise/static_files.zip differ diff --git a/website/static/code/day_two_final_exercise/templates/error.html b/website/static/code/day_two_final_exercise/templates/error.html new file mode 100644 index 0000000..0e0b73c --- /dev/null +++ b/website/static/code/day_two_final_exercise/templates/error.html @@ -0,0 +1,13 @@ + + + + + +
+
+

{{message}}

+
+
+ + + \ No newline at end of file diff --git a/website/static/code/day_two_final_exercise/templates/index.html b/website/static/code/day_two_final_exercise/templates/index.html new file mode 100644 index 0000000..89c1844 --- /dev/null +++ b/website/static/code/day_two_final_exercise/templates/index.html @@ -0,0 +1,57 @@ + + + + + + + Learn Python: Popular GitHub Repos (by ⭐️) With Flask + + + + + + + +
+

Languages

+
+ {% for language in available_languages %} + + {% endfor %} + +
+
+ +
+

Popular GitHub Repos (by ⭐️)

+
+ + + {% if not results %} + No Results. + {% else %} + + + + + + {% endif %} + + {% for result in results %} + + + + + + {% endfor %} + +
NameLanguageNumber Stars
{{result.name}}{{result.language}}{{result.num_stars}}
+
+
+ + + \ No newline at end of file diff --git a/website/static/code/day_two_final_exercise/test.py b/website/static/code/day_two_final_exercise/test.py new file mode 100644 index 0000000..bb9176e --- /dev/null +++ b/website/static/code/day_two_final_exercise/test.py @@ -0,0 +1,32 @@ +import repos.api +import repos.exceptions + +import unittest + +class TestCreateQuery(unittest.TestCase): + + def test_create_query(self): + test_languages = ["Python", "Ruby", "Java"] + test_min_stars = 10000 + + expected = "language:Python language:Ruby language:Java stars:>10000" + result = repos.api.create_query(test_languages, test_min_stars) + + self.assertEqual(result, expected, "Unexpected result from create_query") + + +class TestGitHubApiException(unittest.TestCase): + + def test_exception_403(self): + status_code = 403 + exception = repos.exceptions.GitHubApiException(status_code) + self.assertTrue("Rate limit" in str(exception), "'Rate limit' not found") + + def test_exception_500(self): + status_code = 500 + exception = repos.exceptions.GitHubApiException(status_code) + self.assertTrue(str(status_code) in str(exception)) + + +if __name__ == "__main__": + unittest.main() diff --git a/website/static/code/day_two_final_exercise/tests/__init__.py b/website/static/code/day_two_final_exercise/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/website/static/code/day_two_final_exercise/tests/tests.py b/website/static/code/day_two_final_exercise/tests/tests.py new file mode 100644 index 0000000..e69de29 diff --git a/website/static/code/dayone.py b/website/static/code/dayone.py new file mode 100644 index 0000000..1355bd1 --- /dev/null +++ b/website/static/code/dayone.py @@ -0,0 +1,67 @@ +""" +A small Python program that uses the GitHub search API to list +the top projects by language, based on stars. + +GitHub Search API documentation: https://developer.github.com/v3/search/ + +Additional parameters for searching repos can be found here: +https://help.github.com/en/articles/searching-for-repositories#search-by-number-of-stars + +Note: The API will return results found before a timeout occurs, +so results may not be the same across requests, even with the same query. + +Requests to this endpoint are rate limited to 10 requests per +minute per IP address. +""" + +import requests + +GITHUB_API_URL = "https://api.github.com/search/repositories" + + +def create_query(languages, min_stars=50000): + """ + Create the query string for the GitHub search API, + based on the minimum amount of stars for a project, and + the provided programming languages. + + An example search query looks like: + stars:>50000 language:python language:javascript + """ + query = f"stars:>{min_stars} " + + for language in languages: + query += f"language:{language} " + + return query + + +def repos_with_most_stars(languages, sort="stars", order="desc"): + query = create_query(languages) + + # Define the parameters we want to be part of our URL + parameters = {"q": query, "sort": sort, "order": order} + + # Pass in the query and the parameters as part of the request. + response = requests.get(GITHUB_API_URL, params=parameters) + status_code = response.status_code + + if status_code != 200: + raise RuntimeError( + f"An error occurred. HTTP Status Code was: {status_code}.") + else: + response_json = response.json() + records = response_json["items"] + return records + + +if __name__ == "__main__": + languages = ["python", "javascript", "ruby"] + results = repos_with_most_stars(languages) + + for result in results: + language = result["language"] + stars = result["stargazers_count"] + name = result["name"] + + print(f"-> {name} is a {language} repo with {stars} stars.") diff --git a/website/static/code/daytwo_part1.py b/website/static/code/daytwo_part1.py new file mode 100755 index 0000000..6479c0a --- /dev/null +++ b/website/static/code/daytwo_part1.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +""" +A small command line Python program that uses the GitHub search API to list +the top projects by language, based on stars. + +GitHub Search API documentation: https://developer.github.com/v3/search/ + +Requests to this endpoint are rate limited to 10 requests per +minute per IP address. +""" + +import sys +import requests + + +GITHUB_API_URL = "https://api.github.com/search/repositories" + + +class GitHubApiException(Exception): + + def __init__(self, status_code): + if status_code == 403: + message = "Rate limit reached. Please wait a minute and try again." + else: + message = f"HTTP Status Code was: {status_code}." + + super().__init__("A GitHub API Error Occurred: " + message) + + +class GitHubRepo: + """ + A class used to represent a single GitHub Repository. + """ + + def __init__(self, name, language, num_stars): + self.name = name + self.language = language + self.num_stars = num_stars + + def __str__(self): + return f"-> {self.name} is a {self.language} repo with {self.num_stars} stars." + + def __repr__(self): + return f'GitHubRepo({self.name}, {self.language}, {self.num_stars})' + + +def create_query(languages, min_stars): + """ + Create the query string for the GitHub search API, + based on the minimum amount of stars for a project, and + the provided programming languages. + """ + # Notice we are calling .strip() on each language, to clear it of leading + # and trailing whitespace + query = " ".join(f"language:{language.strip()}" for language in languages) + query = query + f" stars:>{min_stars}" + return query + + +def repos_with_most_stars(languages, min_stars=50000, sort="stars", order="desc"): + query = create_query(languages, min_stars) + parameters = {"q": query, "sort": sort, "order": order} + response = requests.get(GITHUB_API_URL, params=parameters) + + if response.status_code != 200: + raise GitHubApiException(response.status_code) + + response_json = response.json() + items = response_json["items"] + return [GitHubRepo(item["name"], item["language"], item["stargazers_count"]) for item in items] + + +if __name__ == "__main__": + # Accept an optional argument for minimum number of stars from the command line + # $ ./gh_api 100000 # means an input of 100,000 minimum stars. + script_arguments = sys.argv + min_stars = 50000 + + if len(script_arguments) >= 2: + try: + min_stars = int(script_arguments[1]) + except ValueError: + sys.exit("Error: Command line argument must be a valid number.") + + # Accept the list of languages from the user, or provide a default list. + languages = input( + "Enter a comma separated list of programming languages (or press ENTER for defaults): " + ).strip() + if not languages: + languages = ["python", "javascript", "ruby"] + else: + languages = languages.split(",") + + # Get the results + result_repos = repos_with_most_stars(languages=languages, min_stars=min_stars) + if not result_repos: + print("No Results Found.") + else: + for repo in result_repos: + print(repo) diff --git a/website/static/images/favicon.png b/website/static/images/favicon.png new file mode 100644 index 0000000..459fa7a Binary files /dev/null and b/website/static/images/favicon.png differ diff --git a/website/static/images/fem.png b/website/static/images/fem.png new file mode 100644 index 0000000..7a23f47 Binary files /dev/null and b/website/static/images/fem.png differ diff --git a/website/static/images/me.jpg b/website/static/images/me.jpg new file mode 100644 index 0000000..dc3ce4f Binary files /dev/null and b/website/static/images/me.jpg differ diff --git a/website/static/images/snake-cropped.png b/website/static/images/snake-cropped.png new file mode 100644 index 0000000..0a75d65 Binary files /dev/null and b/website/static/images/snake-cropped.png differ diff --git a/website/static/images/snake-scaled.png b/website/static/images/snake-scaled.png new file mode 100644 index 0000000..a9db22f Binary files /dev/null and b/website/static/images/snake-scaled.png differ diff --git a/website/static/images/snake.png b/website/static/images/snake.png new file mode 100644 index 0000000..152854d Binary files /dev/null and b/website/static/images/snake.png differ diff --git a/website/static/images/twittercard.png b/website/static/images/twittercard.png new file mode 100644 index 0000000..94c0401 Binary files /dev/null and b/website/static/images/twittercard.png differ diff --git a/website/themes/nnja-theme-learn/.gitignore b/website/themes/nnja-theme-learn/.gitignore new file mode 100644 index 0000000..6429751 --- /dev/null +++ b/website/themes/nnja-theme-learn/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +public/ +exampleSite/public diff --git a/website/themes/nnja-theme-learn/.grenrc.yml b/website/themes/nnja-theme-learn/.grenrc.yml new file mode 100644 index 0000000..ad31ac8 --- /dev/null +++ b/website/themes/nnja-theme-learn/.grenrc.yml @@ -0,0 +1,25 @@ +--- + dataSource: "prs" + prefix: "v" + onlyMilestones: false + changelogFilename: "CHANGELOG.md" + includeMessages: "all" + ignoreIssuesWith: + - "support" + ignoreLabels: + - "duplicate" + - "invalid" + - "wontfix" + groupBy: + New features: + - "feature" + Bug Fixes: + - "bug" + Enhancements: + - "enhancement" + Internationalisation: + - "i18n" + Theme Meta: + - "meta" + Uncategorised: + - "closed" diff --git a/website/themes/nnja-theme-learn/CHANGELOG.md b/website/themes/nnja-theme-learn/CHANGELOG.md new file mode 100644 index 0000000..47d6510 --- /dev/null +++ b/website/themes/nnja-theme-learn/CHANGELOG.md @@ -0,0 +1,77 @@ +# Changelog + +## v2.2.0 (01/01/1970) + +#### New features + +- [**feature**] Sitewide param to enable or disable next/previous page buttons and breadcrumbs [#184](https://github.com/matcornic/hugo-theme-learn/pull/184) + +#### Bug Fixes + +- [**bug**] Fix baseurl used by search to load json data [#177](https://github.com/matcornic/hugo-theme-learn/pull/177) +- [**bug**] Updated CSS link to fontawesome library [#186](https://github.com/matcornic/hugo-theme-learn/pull/186) +- [**bug**] Close / Cancel search icon not showing in input box [#215](https://github.com/matcornic/hugo-theme-learn/pull/215) +- [**bug**] Prevent left and right keydown events while in input fields [#219](https://github.com/matcornic/hugo-theme-learn/pull/219) +- [**bug**] xss fix [#182](https://github.com/matcornic/hugo-theme-learn/pull/182) +- [**bug**] Fix error in blockquote documentation fixes #165 [#190](https://github.com/matcornic/hugo-theme-learn/pull/190) + +#### Enhancements + +- [**enhancement**] Update mermaid.js to a499296 [#199](https://github.com/matcornic/hugo-theme-learn/pull/199) +- [**enhancement**] Update Font Awesome to 5.0.6 [#129](https://github.com/matcornic/hugo-theme-learn/pull/129) +- [**enhancement**] Update 404.html alttext [#161](https://github.com/matcornic/hugo-theme-learn/pull/161) +- [**enhancement**] Remove CSS source map metadata [#167](https://github.com/matcornic/hugo-theme-learn/pull/167) +- [**enhancement**] Load github images in examplesite via https instead of http [#180](https://github.com/matcornic/hugo-theme-learn/pull/180) +- [**enhancement**] Load main site logo via BaseUrl [#185](https://github.com/matcornic/hugo-theme-learn/pull/185) +- [**enhancement**] HTTPS links in examplesite sidebar [#200](https://github.com/matcornic/hugo-theme-learn/pull/200) +- [**enhancement**] Use correct input type for search [#205](https://github.com/matcornic/hugo-theme-learn/pull/205) +- [**enhancement**] HTTPS link to learn.getgrav.org [#207](https://github.com/matcornic/hugo-theme-learn/pull/207) +- [**enhancement**] Update html5shiv-printshiv.min.js [#208](https://github.com/matcornic/hugo-theme-learn/pull/208) +- [**enhancement**] Remove whitespace from clippy.svg [#211](https://github.com/matcornic/hugo-theme-learn/pull/211) +- [**enhancement**] fix clickable nodes style in mermaid [#169](https://github.com/matcornic/hugo-theme-learn/pull/169) + +#### Internationalisation + +- [**i18n**] French language correction [#157](https://github.com/matcornic/hugo-theme-learn/pull/157) +- [**i18n**] French language correction [#158](https://github.com/matcornic/hugo-theme-learn/pull/158) +- [**i18n**] Add indonesian translation [#159](https://github.com/matcornic/hugo-theme-learn/pull/159) +- [**i18n**] Add Turkish i18n config file [#175](https://github.com/matcornic/hugo-theme-learn/pull/175) + +#### Theme Meta + +- [**meta**] Fix wercker builds [#178](https://github.com/matcornic/hugo-theme-learn/pull/178) +- [**meta**] Declare netlify buildsteps in repo file rather than in webui [#217](https://github.com/matcornic/hugo-theme-learn/pull/217) + +--- + +## 2.1.0 - Font Awesome 5.2 (10/08/2018) +- Use font-awesome 5.2, thanks to @matalo33 #129 +- TranslationBaseName replaced for Name on archetypes template thanks to @nonumeros #145 +- Fix typos in doc, thanks to @tedyoung @afs2015 @mine-cetinkaya-rundel +--- + +## 2.0.0 Theme rewrite (20/08/2017) +New version of theme, rewritten from scratch with help of @vjeantet docdock fork. This theme is now fully compatible with `Hugo 0.25+` + +- Automatic Search +- Multilingual mode +- Unlimited menu levels +- Automatic next/prev buttons to navigate through menu entries +- Image resizing, shadow… +- Attachments files +- List child pages +- Mermaid diagram (flowchart, sequence, gantt) +- Customizable look and feel and themes variants +- Buttons, Tip/Note/Info/Warning boxes, Expand +--- + +## 1.1.0 Automatic arrows (22/07/2017) +Works only for Hugo version from 0.19 to 0.21 (included) +--- + +## 1.0.0 First release (26/03/2017) +First real release of hugo-theme-learn as some features will be deprecated in the next release. + +This version garantees full compatibility with your documentation website when you used this theme from the start. In the next releases, even if the community worked hard, you may have compatibility issues with the documentation if you don't follow new guidelines. + +Next release will change the way chapters need to be created in order to have better automatic behaviour (auto generation of arrows, menu ordering based on weight) \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/LICENSE.md b/website/themes/nnja-theme-learn/LICENSE.md new file mode 100644 index 0000000..0cf9fcb --- /dev/null +++ b/website/themes/nnja-theme-learn/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Grav +Copyright (c) 2016 MATHIEU CORNIC +Copyright (c) 2017 Valere JEANTET + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. diff --git a/website/themes/nnja-theme-learn/README.md b/website/themes/nnja-theme-learn/README.md new file mode 100644 index 0000000..b0f2cca --- /dev/null +++ b/website/themes/nnja-theme-learn/README.md @@ -0,0 +1,60 @@ +# Hugo Learn Theme + +This repository contains a theme for [Hugo](https://gohugo.io/), based on great [Grav Learn Theme](https://learn.getgrav.org/). + +Visit the [theme documentation](https://learn.netlify.com/en/) to see what is going on. It is actually built with this theme. + +[![wercker status](https://app.wercker.com/status/233466a2be73fcea400e7dc02ef6adf9/s/master "wercker status")](https://app.wercker.com/project/byKey/233466a2be73fcea400e7dc02ef6adf9) +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmatcornic%2Fhugo-theme-learn.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fmatcornic%2Fhugo-theme-learn?ref=badge_shield) + +## Main features + +- Automatic Search +- Multilingual mode +- Unlimited menu levels +- Automatic next/prev buttons to navigate through menu entries +- Image resizing, shadow… +- Attachments files +- List child pages +- Mermaid diagram (flowchart, sequence, gantt) +- Customizable look and feel and themes variants +- Buttons, Tip/Note/Info/Warning boxes, Expand + +## Installation + +Navigate to your themes folder in your Hugo site and use the following commands: + +``` +$ cd themes +$ git clone https://github.com/matcornic/hugo-theme-learn.git +``` + +Check that your Hugo version is minimum `0.25` with `hugo version`. + +![Overview](https://github.com/matcornic/hugo-theme-learn/raw/master/images/tn.png) + +## Usage + +- [Visit the documentation](https://learn.netlify.com/en/) + +## Download old versions (prior to 2.0.0) + +If you need old version for compatibility purpose, either download [theme source code from releases](https://github.com/matcornic/hugo-theme-learn/releases) or use the right git tag. For example, with `1.1.0` + +- Direct download way: https://github.com/matcornic/hugo-theme-learn/archive/1.1.0.zip +- Git way: + +```shell +cd themes/hugo-theme-learn +git checkout tags/1.1.0 +``` + +For both solutions, the documentation is available at https://github.com/matcornic/hugo-theme-learn/releases/download/1.1.0/hugo-learn-doc-1.1.0.zip + +## Credits + +Many thanks to [@vjeantet](https://github.com/vjeantet/) for the fork [docdock](https://github.com/vjeantet/hugo-theme-docdock). The v2 of this theme is mainly based on his work ! + + +## License +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmatcornic%2Fhugo-theme-learn.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fmatcornic%2Fhugo-theme-learn?ref=badge_large) diff --git a/website/themes/nnja-theme-learn/archetypes/chapter.md b/website/themes/nnja-theme-learn/archetypes/chapter.md new file mode 100644 index 0000000..a29ba1b --- /dev/null +++ b/website/themes/nnja-theme-learn/archetypes/chapter.md @@ -0,0 +1,13 @@ ++++ +title = "{{ replace .Name "-" " " | title }}" +date = {{ .Date }} +weight = 5 +chapter = true +pre = "X. " ++++ + +### Chapter X + +# Some Chapter title + +Lorem Ipsum. \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/archetypes/default.md b/website/themes/nnja-theme-learn/archetypes/default.md new file mode 100644 index 0000000..5124e2a --- /dev/null +++ b/website/themes/nnja-theme-learn/archetypes/default.md @@ -0,0 +1,7 @@ ++++ +title = "{{ replace .Name "-" " " | title }}" +date = {{ .Date }} +weight = 5 ++++ + +Lorem Ipsum. \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/i18n/en.toml b/website/themes/nnja-theme-learn/i18n/en.toml new file mode 100644 index 0000000..118345f --- /dev/null +++ b/website/themes/nnja-theme-learn/i18n/en.toml @@ -0,0 +1,26 @@ +[Search-placeholder] +other = "Search..." + +[Clear-History] +other = "Clear History" + +[Attachments-label] +other = "Attachments" + +[title-404] +other = "Error" + +[message-404] +other = "Woops. Looks like this page doesn't exist ¯\\_(ツ)_/¯." + +[Go-to-homepage] +other = "Go to homepage" + +[Edit-this-page] +other = "Edit this page" + +[Shortcuts-Title] +other = "More" + +[Expand-title] +other = "Expand me..." \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/i18n/es.toml b/website/themes/nnja-theme-learn/i18n/es.toml new file mode 100644 index 0000000..57b7a33 --- /dev/null +++ b/website/themes/nnja-theme-learn/i18n/es.toml @@ -0,0 +1,26 @@ +[Search-placeholder] +other = "Buscar..." + +[Clear-History] +other = "Borrar Historial" + +[Attachments-label] +other = "Adjuntos" + +[title-404] +other = "Error" + +[message-404] +other = "Ups. Parece que la página no existe ¯\\_(ツ)_/¯." + +[Go-to-homepage] +other = "Ir al inicio" + +[Edit-this-page] +other = "Editar esta página" + +[Shortcuts-Title] +other = "Más" + +[Expand-title] +other = "Expandir..." diff --git a/website/themes/nnja-theme-learn/i18n/fr.toml b/website/themes/nnja-theme-learn/i18n/fr.toml new file mode 100644 index 0000000..106f20d --- /dev/null +++ b/website/themes/nnja-theme-learn/i18n/fr.toml @@ -0,0 +1,26 @@ +[Search-placeholder] +other = "Rechercher..." + +[Clear-History] +other = "Supprimer l'historique" + +[Attachments-label] +other = "Pièces jointes" + +[title-404] +other = "Erreur" + +[message-404] +other = "Oups. On dirait que cette page n'existe pas ¯\\_(ツ)_/¯" + +[Go-to-homepage] +other = "Vers la page d'accueil" + +[Edit-this-page] +other = "Modifier la page" + +[Shortcuts-Title] +other = "Aller plus loin" + +[Expand-title] +other = "Déroulez-moi..." \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/i18n/id.toml b/website/themes/nnja-theme-learn/i18n/id.toml new file mode 100644 index 0000000..e0aa5ae --- /dev/null +++ b/website/themes/nnja-theme-learn/i18n/id.toml @@ -0,0 +1,26 @@ +[Search-placeholder] +other = "Telusuri..." + +[Clear-History] +other = "Bersihkan Riwayat" + +[Attachments-label] +other = "Lampiran" + +[title-404] +other = "Kesalahan" + +[message-404] +other = "Oops. Sepertinya halaman ini tidak ada ¯\\_(ツ)_/¯." + +[Go-to-homepage] +other = "Ke halaman depan" + +[Edit-this-page] +other = "Edit halaman ini" + +[Shortcuts-Title] +other = "Lainnya" + +[Expand-title] +other = "Bentangkan..." diff --git a/website/themes/nnja-theme-learn/i18n/nl.toml b/website/themes/nnja-theme-learn/i18n/nl.toml new file mode 100644 index 0000000..cca84b4 --- /dev/null +++ b/website/themes/nnja-theme-learn/i18n/nl.toml @@ -0,0 +1,26 @@ +[Search-placeholder] +other = "Zoeken..." + +[Clear-History] +other = "Wis geschiedenis" + +[Attachments-label] +other = "Bijlagen" + +[title-404] +other = "Error" + +[message-404] +other = "Blijkbaar bestaat deze pagina niet ¯\\_(ツ)_/¯." + +[Go-to-homepage] +other = "Naar startpagina" + +[Edit-this-page] +other = "Deze pagina bewerken" + +[Shortcuts-Title] +other = "Snelkoppelingen" + +[Expand-title] +other = "Lees meer..." diff --git a/website/themes/nnja-theme-learn/i18n/pt.toml b/website/themes/nnja-theme-learn/i18n/pt.toml new file mode 100644 index 0000000..b110533 --- /dev/null +++ b/website/themes/nnja-theme-learn/i18n/pt.toml @@ -0,0 +1,26 @@ +[Search-placeholder] +other = "Procurar..." + +[Clear-History] +other = "Limpar Histórico" + +[Attachments-label] +other = "Anexos" + +[title-404] +other = "Erro" + +[message-404] +other = "Ops. Parece que a página não existe ¯\\_(ツ)_/¯." + +[Go-to-homepage] +other = "Ir para o início" + +[Edit-this-page] +other = "Editar esta página" + +[Shortcuts-Title] +other = "Mais" + +[Expand-title] +other = "Expandir..." diff --git a/website/themes/nnja-theme-learn/i18n/tr.toml b/website/themes/nnja-theme-learn/i18n/tr.toml new file mode 100644 index 0000000..17249ed --- /dev/null +++ b/website/themes/nnja-theme-learn/i18n/tr.toml @@ -0,0 +1,26 @@ +[Search-placeholder] +other = "Ara..." + +[Clear-History] +other = "Geçmişi Temizle" + +[Attachments-label] +other = "Ekler" + +[title-404] +other = "Hata" + +[message-404] +other = "Uups. Görünüşe göre böyle bir sayfa yok ¯\\_(ツ)_/¯" + +[Go-to-homepage] +other = "Anasayfaya dön" + +[Edit-this-page] +other = "Sayfayı düzenle" + +[Shortcuts-Title] +other = "Dahası Var" + +[Expand-title] +other = "Genişlet..." diff --git a/website/themes/nnja-theme-learn/images/screenshot.png b/website/themes/nnja-theme-learn/images/screenshot.png new file mode 100644 index 0000000..85966a2 Binary files /dev/null and b/website/themes/nnja-theme-learn/images/screenshot.png differ diff --git a/website/themes/nnja-theme-learn/images/tn.png b/website/themes/nnja-theme-learn/images/tn.png new file mode 100644 index 0000000..c969306 Binary files /dev/null and b/website/themes/nnja-theme-learn/images/tn.png differ diff --git a/website/themes/nnja-theme-learn/layouts/404.html b/website/themes/nnja-theme-learn/layouts/404.html new file mode 100644 index 0000000..4904b31 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/404.html @@ -0,0 +1,59 @@ + + + + + {{ partial "meta.html" . }} {{ partial "favicon.html" . }} {{ .Scratch.Add "title" "" }}{{ if eq .Site.Data.titles .Title }}{{ .Scratch.Set "title" (index .Site.Data.titles .Title).title }}{{ else }}{{ .Scratch.Set "title" .Title}}{{end}} + {{ .Scratch.Get "title" }} + + {{ $assetBusting := not .Site.Params.disableAssetsBusting }} + + + + + + + + + {{with .Site.Params.themeVariant}} + + {{end}} + +{{ partial "custom-header.html" . }} + + + + + + +
+
+
+
+

{{T "title-404"}}

+

+

+

{{T "message-404"}}

+

+

{{T "Go-to-homepage"}}

+

Page not found!

+
+
+ +
+ + + diff --git a/website/themes/nnja-theme-learn/layouts/_default/list.html b/website/themes/nnja-theme-learn/layouts/_default/list.html new file mode 100644 index 0000000..d9598ac --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/_default/list.html @@ -0,0 +1,12 @@ +{{ partial "header.html" . }} + +{{ .Content }} + +
+ {{with .Params.LastModifierDisplayName}} + {{ . }} {{with $.Date}} {{ .Format "02/01/2006" }}{{end}} + + {{end}} +
+ +{{ partial "footer.html" . }} \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/layouts/_default/single.html b/website/themes/nnja-theme-learn/layouts/_default/single.html new file mode 100644 index 0000000..ab1424f --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/_default/single.html @@ -0,0 +1,13 @@ +{{ partial "header.html" . }} + +{{ .Content }} + +
+ {{with .Params.LastModifierDisplayName}} + {{ . }} {{with $.Date}} {{ .Format "02/01/2006" }}{{end}} + + {{end}} +
+ + +{{ partial "footer.html" . }} diff --git a/website/themes/nnja-theme-learn/layouts/index.html b/website/themes/nnja-theme-learn/layouts/index.html new file mode 100644 index 0000000..cdf25d9 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/index.html @@ -0,0 +1,31 @@ +{{ partial "header.html" . }} + + navigation + + +{{if .Site.Home.Content }} +{{.Site.Home.Content}} +{{else}} + {{if eq .Site.Language.Lang "fr"}} +

Personaliser la page d'accueil

+

+ Le site fonctionne. Ne pas oublier de personaliser cette page avec votre propre contenu. 3 manières de faire : +

+
    +
  • 1. Créer un fichier _index.md dans le dossier content et le remplir de Markdown
  • +
  • 2. Créer un fichier index.html dans le dossier static et le remplir de code HTML
  • +
  • 3. Configurer le serveur http pour rediriger automatiquement la homepage vers la page de votre choix dans le site
  • +
+ {{else}} +

Customize your own home page

+

+ The site is working. Don't forget to customize this homepage with your own. You typically have 3 choices : +

+
    +
  • 1. Create an _index.md document in content folder and fill it with Markdown content
  • +
  • 2. Create an index.html file in the static folder and fill the file with HTML content
  • +
  • 3. Configure your server to automatically redirect home page to one your documentation page
  • +
+ {{end}} +{{ end }} +{{ partial "footer.html" . }} diff --git a/website/themes/nnja-theme-learn/layouts/index.json b/website/themes/nnja-theme-learn/layouts/index.json new file mode 100644 index 0000000..367f3b3 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/index.json @@ -0,0 +1,12 @@ +[{{ range $index, $page := .Site.Pages }} +{{- if ne $page.Type "json" -}} +{{- if and $index (gt $index 0) -}},{{- end }} +{ + "uri": "{{ $page.Permalink }}", + "title": "{{ htmlEscape $page.Title}}", + "tags": [{{ range $tindex, $tag := $page.Params.tags }}{{ if $tindex }}, {{ end }}"{{ $tag| htmlEscape }}"{{ end }}], + "description": "{{ htmlEscape .Description}}", + "content": {{$page.Plain | jsonify}} +} +{{- end -}} +{{- end -}}] \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/layouts/partials/custom-comments.html b/website/themes/nnja-theme-learn/layouts/partials/custom-comments.html new file mode 100644 index 0000000..6f5a034 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/custom-comments.html @@ -0,0 +1,3 @@ + diff --git a/website/themes/nnja-theme-learn/layouts/partials/custom-footer.html b/website/themes/nnja-theme-learn/layouts/partials/custom-footer.html new file mode 100644 index 0000000..9bb0f48 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/custom-footer.html @@ -0,0 +1,5 @@ + diff --git a/website/themes/nnja-theme-learn/layouts/partials/custom-header.html b/website/themes/nnja-theme-learn/layouts/partials/custom-header.html new file mode 100644 index 0000000..294af29 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/custom-header.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/layouts/partials/favicon.html b/website/themes/nnja-theme-learn/layouts/partials/favicon.html new file mode 100644 index 0000000..1a45be0 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/favicon.html @@ -0,0 +1 @@ + diff --git a/website/themes/nnja-theme-learn/layouts/partials/footer.html b/website/themes/nnja-theme-learn/layouts/partials/footer.html new file mode 100644 index 0000000..920c271 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/footer.html @@ -0,0 +1,78 @@ + {{ if .Params.chapter }} + + {{ end }} + + {{ partial "custom-comments.html" . }} + + + + + + +
+
+
+ + + + + + + + + + + + + + + + {{ partial "custom-footer.html" . }} + + diff --git a/website/themes/nnja-theme-learn/layouts/partials/header.html b/website/themes/nnja-theme-learn/layouts/partials/header.html new file mode 100644 index 0000000..7532997 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/header.html @@ -0,0 +1,106 @@ + + + + + + + {{ .Hugo.Generator }} + + {{ template "_internal/google_analytics_async.html" . }} + {{ partial "meta_twitter.html" . }} + + {{ partial "meta.html" . }} + {{ partial "favicon.html" . }} + + {{ .Title }} :: {{ .Site.Title }} + + {{ $assetBusting := not .Site.Params.disableAssetsBusting }} + + + + + + + + + {{with .Site.Params.themeVariant}} + + {{end}} + + + + + {{ partial "custom-header.html" . }} + + + {{ partial "menu.html" . }} +
+
+
+ {{if not .IsHome}} +
+
+ {{ if and (or .IsPage .IsSection) .Site.Params.editURL }} + {{ $File := .File }} + {{ $Site := .Site }} + {{with $File.Path }} + + {{ end }} + {{ end }} + {{$toc := (and (not .Params.disableToc) (not .Params.chapter))}} + + {{ if $toc }} + {{ partial "toc.html" . }} + {{ end }} +
+
+ {{ end }} + + {{ if .Params.chapter }} +
+ {{ end }} +
+ {{if and (not .IsHome) (not .Params.chapter) }} +

{{.Title}}

+ {{end}} + + {{define "breadcrumb"}} + {{$parent := .page.Parent }} + {{ if $parent }} + {{ $value := (printf "%s > %s" $parent.URL $parent.Title .value) }} + {{ template "breadcrumb" dict "page" $parent "value" $value }} + {{else}} + {{.value|safeHTML}} + {{end}} + {{end}} diff --git a/website/themes/nnja-theme-learn/layouts/partials/logo.html b/website/themes/nnja-theme-learn/layouts/partials/logo.html new file mode 100644 index 0000000..e0fc42d --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/logo.html @@ -0,0 +1,19 @@ + diff --git a/website/themes/nnja-theme-learn/layouts/partials/menu-footer.html b/website/themes/nnja-theme-learn/layouts/partials/menu-footer.html new file mode 100644 index 0000000..9b0b593 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/menu-footer.html @@ -0,0 +1 @@ +

Built with from Grav and Hugo

diff --git a/website/themes/nnja-theme-learn/layouts/partials/menu.html b/website/themes/nnja-theme-learn/layouts/partials/menu.html new file mode 100644 index 0000000..2a90520 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/menu.html @@ -0,0 +1,150 @@ + + + +{{ define "section-tree-nav" }} +{{ $showvisitedlinks := .showvisitedlinks }} +{{ $currentNode := .currentnode }} + {{with .sect}} + {{if .IsSection}} + {{safeHTML .Params.head}} +
  • + + {{safeHTML .Params.Pre}}{{or .Params.menuTitle .LinkTitle .Title}}{{safeHTML .Params.Post}} + {{ if $showvisitedlinks}} + + {{ end }} + + {{ $numberOfPages := (add (len .Pages) (len .Sections)) }} + {{ if ne $numberOfPages 0 }} +
      + {{ $currentNode.Scratch.Set "pages" .Pages }} + {{ if .Sections}} + {{ $currentNode.Scratch.Set "pages" (.Pages | union .Sections) }} + {{end}} + {{ $pages := ($currentNode.Scratch.Get "pages") }} + + {{if eq .Site.Params.ordersectionsby "title"}} + {{ range $pages.ByTitle }} + {{ if and .Params.hidden (not $.showhidden) }} + {{else}} + {{ template "section-tree-nav" dict "sect" . "currentnode" $currentNode "showvisitedlinks" $showvisitedlinks }} + {{end}} + {{ end }} + {{else}} + {{ range $pages.ByWeight }} + {{ if and .Params.hidden (not $.showhidden) }} + {{else}} + {{ template "section-tree-nav" dict "sect" . "currentnode" $currentNode "showvisitedlinks" $showvisitedlinks }} + {{end}} + {{ end }} + {{end}} +
    + {{ end }} +
  • + {{else}} + {{ if not .Params.Hidden }} +
  • + + {{safeHTML .Params.Pre}}{{or .Params.menuTitle .LinkTitle .Title}}{{safeHTML .Params.Post}} + {{ if $showvisitedlinks}}{{end}} + +
  • + {{ end }} + {{end}} + {{ end }} +{{ end }} + diff --git a/website/themes/nnja-theme-learn/layouts/partials/meta.html b/website/themes/nnja-theme-learn/layouts/partials/meta.html new file mode 100644 index 0000000..71d9634 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/meta.html @@ -0,0 +1,2 @@ + +{{ with .Site.Params.author }}{{ end }} diff --git a/website/themes/nnja-theme-learn/layouts/partials/search.html b/website/themes/nnja-theme-learn/layouts/partials/search.html new file mode 100644 index 0000000..41daf84 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/search.html @@ -0,0 +1,16 @@ + +{{ $assetBusting := not .Site.Params.disableAssetsBusting }} + + + + diff --git a/website/themes/nnja-theme-learn/layouts/partials/toc.html b/website/themes/nnja-theme-learn/layouts/partials/toc.html new file mode 100644 index 0000000..6b69051 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/partials/toc.html @@ -0,0 +1,5 @@ +
    +
    +{{ .TableOfContents }} +
    +
    diff --git a/website/themes/nnja-theme-learn/layouts/shortcodes/attachments.html b/website/themes/nnja-theme-learn/layouts/shortcodes/attachments.html new file mode 100644 index 0000000..630342b --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/shortcodes/attachments.html @@ -0,0 +1,35 @@ +
    + + {{if eq .Page.File.BaseFileName "index"}} + {{$.Scratch.Add "filesName" "files"}} + {{else}} + {{$.Scratch.Add "filesName" (printf "%s.files" .Page.File.BaseFileName)}} + {{end}} +
    + {{ range (readDir (printf "./content/%s%s" .Page.File.Dir ($.Scratch.Get "filesName")) ) }} + {{ $fileDir := replace $.Page.File.Dir "\\" "/" }} + {{if ($.Get "pattern")}} + {{if (findRE ($.Get "pattern") .Name)}} +
  • + + {{.Name}} + + ({{div .Size 1024 }} ko) +
  • + {{end}} + {{else}} +
  • + + {{.Name}} + + ({{div .Size 1024 }} ko) +
  • + {{end}} + {{end}} +
    + {{.Inner}} +
    + diff --git a/website/themes/nnja-theme-learn/layouts/shortcodes/button.html b/website/themes/nnja-theme-learn/layouts/shortcodes/button.html new file mode 100644 index 0000000..fdeaa19 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/shortcodes/button.html @@ -0,0 +1,13 @@ + + {{ $icon := .Get "icon" }} + {{ $iconposition := .Get "icon-position" }} + {{ if ($icon) }} + {{ if or (not ($iconposition)) (eq $iconposition "left") }} + + {{ end }} + {{ end }} + {{ .Inner }} + {{ if and ($icon) (eq $iconposition "right")}} + + {{ end }} + diff --git a/website/themes/nnja-theme-learn/layouts/shortcodes/children.html b/website/themes/nnja-theme-learn/layouts/shortcodes/children.html new file mode 100644 index 0000000..a1e31b2 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/shortcodes/children.html @@ -0,0 +1,96 @@ +{{ $showhidden := .Get "showhidden"}} +{{ $style := .Get "style" | default "li" }} +{{ $depth := .Get "depth" | default 1 }} +{{ $withDescription := .Get "description" | default false }} +{{ $sortTerm := .Get "sort" | default "Weight" }} + + +
      + {{ .Scratch.Set "pages" .Page.Pages }} + {{ if .Page.Sections}} + {{ .Scratch.Set "pages" (.Page.Pages | union .Page.Sections) }} + {{end}} + {{ $pages := (.Scratch.Get "pages") }} + + {{if eq $sortTerm "Weight"}} + {{template "childs" dict "menu" $pages.ByWeight "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} + {{else if eq $sortTerm "Name"}} + {{template "childs" dict "menu" $pages.ByTitle "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} + {{else if eq $sortTerm "PublishDate"}} + {{template "childs" dict "menu" $pages.ByPublishDate "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} + {{else if eq $sortTerm "Date"}} + {{template "childs" dict "menu" $pages.ByDate "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} + {{else if eq $sortTerm "Length"}} + {{template "childs" dict "menu" $pages.ByLength "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} + {{else}} + {{template "childs" dict "menu" $pages "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} + {{end}} +
    + +{{.Inner|safeHTML}} + +{{ define "childs" }} + {{ range .menu }} + {{ if and .Params.hidden (not $.showhidden) }} + {{else}} + + +{{if hasPrefix $.style "h"}} + {{$num := sub ( int (trim $.style "h") ) 1 }} + {{$numn := add $num $.count }} + +{{(printf "" $numn)|safeHTML}} +{{ .Title }} +{{(printf "" $numn)|safeHTML}} + +{{else}} +{{(printf "<%s>" $.style)|safeHTML}} +{{ .Title }} +{{(printf "" $.style)|safeHTML}} +{{end}} + + + + + + {{if $.description}} + {{if .Description}} +

    {{.Description}}

    + {{else}} +

    {{.Summary}}

    + {{end}} + {{end}} + + + + {{ if lt $.count $.depth}} +{{if eq $.style "li"}} +
      +{{end}} + {{ $.Page.Scratch.Set "pages" .Pages }} + {{ if .Sections}} + {{ $.Page.Scratch.Set "pages" (.Pages | union .Sections) }} + {{end}} + {{ $pages := ($.Page.Scratch.Get "pages") }} + + {{if eq $.sortTerm "Weight"}} + {{template "childs" dict "menu" $pages.ByWeight "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} + {{else if eq $.sortTerm "Name"}} + {{template "childs" dict "menu" $pages.ByTitle "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} + {{else if eq $.sortTerm "PublishDate"}} + {{template "childs" dict "menu" $pages.ByPublishDate "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} + {{else if eq $.sortTerm "Date"}} + {{template "childs" dict "menu" $pages.ByDate "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} + {{else if eq $.sortTerm "Length"}} + {{template "childs" dict "menu" $pages.ByLength "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} + {{else}} + {{template "childs" dict "menu" $pages "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} + {{end}} +{{if eq $.style "li"}} +
    +{{end}} + {{end}} + + {{end}} + {{end}} +{{end}} \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/layouts/shortcodes/expand.html b/website/themes/nnja-theme-learn/layouts/shortcodes/expand.html new file mode 100644 index 0000000..4825847 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/shortcodes/expand.html @@ -0,0 +1,18 @@ +
    +
    + + + + {{$expandMessage := T "Expand-title"}} + {{ if .IsNamedParams }} + {{.Get "default" | default $expandMessage}} + {{else}} + {{.Get 0 | default $expandMessage}} + {{end}} + + +
    + +
    \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/layouts/shortcodes/mermaid.html b/website/themes/nnja-theme-learn/layouts/shortcodes/mermaid.html new file mode 100644 index 0000000..a6a61a5 --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/shortcodes/mermaid.html @@ -0,0 +1 @@ +
    {{ safeHTML .Inner }}
    diff --git a/website/themes/nnja-theme-learn/layouts/shortcodes/notice.html b/website/themes/nnja-theme-learn/layouts/shortcodes/notice.html new file mode 100644 index 0000000..936f68a --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/shortcodes/notice.html @@ -0,0 +1 @@ +
    {{ .Inner }}
    diff --git a/website/themes/nnja-theme-learn/layouts/shortcodes/ref.html b/website/themes/nnja-theme-learn/layouts/shortcodes/ref.html new file mode 100644 index 0000000..e7f769e --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/shortcodes/ref.html @@ -0,0 +1,14 @@ +{{- if in (.Get 0) "/_index.md" -}} + {{- $paths := (split (.Get 0) "_index.md") -}} + {{- $pagepath := index $paths 0 -}} + {{- $anchor := index $paths 1 -}} + {{- with .Site.GetPage "section" (trim $pagepath "/") -}} + {{- ( printf "%s%s" $pagepath $anchor ) | relLangURL -}} + {{- end -}} +{{- else -}} + {{- with .Site.GetPage "section" (.Get 0) }} + {{- .URL -}} + {{- else -}} + {{- .Get 0 | relref .Page -}} + {{- end -}} +{{- end -}} \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/layouts/shortcodes/relref.html b/website/themes/nnja-theme-learn/layouts/shortcodes/relref.html new file mode 100644 index 0000000..e7f769e --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/shortcodes/relref.html @@ -0,0 +1,14 @@ +{{- if in (.Get 0) "/_index.md" -}} + {{- $paths := (split (.Get 0) "_index.md") -}} + {{- $pagepath := index $paths 0 -}} + {{- $anchor := index $paths 1 -}} + {{- with .Site.GetPage "section" (trim $pagepath "/") -}} + {{- ( printf "%s%s" $pagepath $anchor ) | relLangURL -}} + {{- end -}} +{{- else -}} + {{- with .Site.GetPage "section" (.Get 0) }} + {{- .URL -}} + {{- else -}} + {{- .Get 0 | relref .Page -}} + {{- end -}} +{{- end -}} \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/layouts/shortcodes/siteparam.html b/website/themes/nnja-theme-learn/layouts/shortcodes/siteparam.html new file mode 100644 index 0000000..20cd2ca --- /dev/null +++ b/website/themes/nnja-theme-learn/layouts/shortcodes/siteparam.html @@ -0,0 +1,7 @@ +{{- $paramName := (.Get 0) -}} +{{- $siteParams := .Site.Params -}} +{{- with $paramName -}} + {{- with $siteParams -}} + {{- index . (lower $paramName) -}} + {{- end -}} +{{- end -}} \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/netlify.toml b/website/themes/nnja-theme-learn/netlify.toml new file mode 100644 index 0000000..bf02f0d --- /dev/null +++ b/website/themes/nnja-theme-learn/netlify.toml @@ -0,0 +1,24 @@ +[build] + publish = "exampleSite/public" + command = "hugo -s exampleSite" + +[build.environment] + HUGO_THEME = "repo" + HUGO_THEMESDIR = "/opt/build" + HUGO_VERSION = "0.50" + +[context.production.environment] + HUGO_BASEURL = "https://learn.netlify.com/" + +[context.deploy-preview] + command = "hugo -s exampleSite -b $DEPLOY_PRIME_URL" + +[context.deploy-preview.environment] + HUGO_ENABLEGITINFO = "true" + +[context.branch-deplpy] + command = "hugo -s exampleSite -b $DEPLOY_PRIME_URL" + +[context.branch-deploy.environment] + HUGO_ENABLEGITINFO = "true" + diff --git a/website/themes/nnja-theme-learn/static/css/auto-complete.css b/website/themes/nnja-theme-learn/static/css/auto-complete.css new file mode 100644 index 0000000..ac6979a --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/auto-complete.css @@ -0,0 +1,47 @@ +.autocomplete-suggestions { + text-align: left; + cursor: default; + border: 1px solid #ccc; + border-top: 0; + background: #fff; + box-shadow: -1px 1px 3px rgba(0,0,0,.1); + + /* core styles should not be changed */ + position: absolute; + display: none; + z-index: 9999; + max-height: 254px; + overflow: hidden; + overflow-y: auto; + box-sizing: border-box; + +} +.autocomplete-suggestion { + position: relative; + cursor: pointer; + padding: 7px; + line-height: 23px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: #333; +} + +.autocomplete-suggestion b { + font-weight: normal; + color: #1f8dd6; +} + +.autocomplete-suggestion.selected { + background: #333; + color: #fff; +} + +.autocomplete-suggestion:hover { + background: #444; + color: #fff; +} + +.autocomplete-suggestion > .context { + font-size: 12px; +} diff --git a/website/themes/nnja-theme-learn/static/css/featherlight.min.css b/website/themes/nnja-theme-learn/static/css/featherlight.min.css new file mode 100644 index 0000000..f225bec --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/featherlight.min.css @@ -0,0 +1,8 @@ +/** + * Featherlight - ultra slim jQuery lightbox + * Version 1.2.3 - http://noelboss.github.io/featherlight/ + * + * Copyright 2015, Noël Raoul Bossart (http://www.noelboss.com) + * MIT Licensed. +**/ +@media all{.featherlight{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:2147483647;text-align:center;white-space:nowrap;cursor:pointer;background:#333;background:rgba(0,0,0,0)}.featherlight:last-of-type{background:rgba(0,0,0,.8)}.featherlight:before{content:'';display:inline-block;height:100%;vertical-align:middle;margin-right:-.25em}.featherlight .featherlight-content{position:relative;text-align:left;vertical-align:middle;display:inline-block;overflow:auto;padding:25px 25px 0;border-bottom:25px solid transparent;min-width:30%;margin-left:5%;margin-right:5%;max-height:95%;background:#fff;cursor:auto;white-space:normal}.featherlight .featherlight-inner{display:block}.featherlight .featherlight-close-icon{position:absolute;z-index:9999;top:0;right:0;line-height:25px;width:25px;cursor:pointer;text-align:center;font:Arial,sans-serif;background:#fff;background:rgba(255,255,255,.3);color:#000}.featherlight .featherlight-image{width:100%}.featherlight-iframe .featherlight-content{border-bottom:0;padding:0}.featherlight iframe{border:0}}@media only screen and (max-width:1024px){.featherlight .featherlight-content{margin-left:10px;margin-right:10px;max-height:98%;padding:10px 10px 0;border-bottom:10px solid transparent}} \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/static/css/fontawesome-all.min.css b/website/themes/nnja-theme-learn/static/css/fontawesome-all.min.css new file mode 100644 index 0000000..de56473 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/fontawesome-all.min.css @@ -0,0 +1 @@ +.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hashtag:before{content:"\f292"}.fa-hat-wizard:before{content:"\f6e8"}.fa-haykal:before{content:"\f666"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-icicles:before{content:"\f7ad"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/static/css/hugo-theme.css b/website/themes/nnja-theme-learn/static/css/hugo-theme.css new file mode 100644 index 0000000..867e132 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/hugo-theme.css @@ -0,0 +1,290 @@ +/* Insert here special css for hugo theme, on top of any other imported css */ + +/* Language display style borrowed from scotch.io. Thanks! */ +pre { + border-radius: 10px; + border: none; + position: relative; + padding: 50px 40px 30px; + z-index: 1; +} + +pre:after { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + content: ""; + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 8px; + background: #ddd; +} + +pre:before { + content: attr(data-title); + position: absolute; + top: 7px; + padding: 0 10px 3px; + font-size: 10px; + text-align: right; + color: #444; + font-weight: 700; + letter-spacing: .8px; + text-transform: uppercase; + border-radius: 0 0 5px 5px; + background: #ddd; +} + +/* Table of contents */ + +.progress ul { + list-style: none; + margin: 0; + padding: 0 5px; +} + +#TableOfContents { + font-size: 13px !important; + max-height: 85vh; + overflow: auto; + padding: 15px !important; +} + + +#TableOfContents > ul > li > ul > li > ul li { + margin-right: 8px; +} + +#TableOfContents > ul > li > a { + font-weight: bold; padding: 0 18px; margin: 0 2px; +} + +#TableOfContents > ul > li > ul > li > a { + font-weight: bold; +} + +#TableOfContents > ul > li > ul > li > ul > li > ul > li > ul > li { + display: none; +} + +body { + font-size: 16px !important; + color: #323232 !important; +} + +#body a.highlight, #body a.highlight:hover, #body a.highlight:focus { + text-decoration: none; + outline: none; + outline: 0; +} +#body a.highlight { + line-height: 1.1; + display: inline-block; +} +#body a.highlight:after { + display: block; + content: ""; + height: 1px; + width: 0%; + background-color: #0082a7; /*#CE3B2F*/ + -webkit-transition: width 0.5s ease; + -moz-transition: width 0.5s ease; + -ms-transition: width 0.5s ease; + transition: width 0.5s ease; +} +#body a.highlight:hover:after, #body a.highlight:focus:after { + width: 100%; +} +.progress { + position:absolute; + background-color: rgba(246, 246, 246, 0.97); + width: auto; + border: thin solid #ECECEC; + display:none; + z-index:200; +} + +#toc-menu { + border-right: thin solid #DAD8D8 !important; + padding-right: 1rem !important; + margin-right: 0.5rem !important; +} + +#sidebar-toggle-span { + border-right: thin solid #DAD8D8 !important; + padding-right: 0.5rem !important; + margin-right: 1rem !important; +} + +.btn { + display: inline-block !important; + padding: 6px 12px !important; + margin-bottom: 0 !important; + font-size: 14px !important; + font-weight: normal !important; + line-height: 1.42857143 !important; + text-align: center !important; + white-space: nowrap !important; + vertical-align: middle !important; + -ms-touch-action: manipulation !important; + touch-action: manipulation !important; + cursor: pointer !important; + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important; + background-image: none !important; + border: 1px solid transparent !important; + border-radius: 4px !important; + -webkit-transition: all 0.15s !important; + -moz-transition: all 0.15s !important; + transition: all 0.15s !important; +} +.btn:focus { + /*outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px;*/ + outline: none !important; +} +.btn:hover, +.btn:focus { + color: #2b2b2b !important; + text-decoration: none !important; +} + +.btn-default { + color: #333 !important; + background-color: #fff !important; + border-color: #ccc !important; +} +.btn-default:hover, +.btn-default:focus, +.btn-default:active { + color: #fff !important; + background-color: #9e9e9e !important; + border-color: #9e9e9e !important; +} +.btn-default:active { + background-image: none !important; +} + +/* anchors */ +.anchor { + color: #00bdf3; + font-size: 0.5em; + cursor:pointer; + visibility:hidden; + margin-left: 0.5em; + position: absolute; + margin-top:0.1em; +} + +h2:hover .anchor, h3:hover .anchor, h4:hover .anchor, h5:hover .anchor, h6:hover .anchor { + visibility:visible; +} + +/* Redfines headers style */ + +h2, h3, h4, h5, h6 { + font-weight: 400; + line-height: 1.1; +} + +h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { + font-weight: inherit; +} + +h2 { + font-size: 2.5rem; + line-height: 110% !important; + margin: 2.5rem 0 1.5rem 0; +} + +h3 { + font-size: 2rem; + line-height: 110% !important; + margin: 2rem 0 1rem 0; +} + +h4 { + font-size: 1.5rem; + line-height: 110% !important; + margin: 1.5rem 0 0.75rem 0; +} + +h5 { + font-size: 1rem; + line-height: 110% !important; + margin: 1rem 0 0.2rem 0; +} + +h6 { + font-size: 0.5rem; + line-height: 110% !important; + margin: 0.5rem 0 0.2rem 0; +} + +p { + margin: 1rem 0; +} + +figcaption h4 { + font-weight: 300 !important; + opacity: .85; + font-size: 1em; + text-align: center; + margin-top: -1.5em; +} + +.select-style { + border: 0; + width: 150px; + border-radius: 0px; + overflow: hidden; + display: inline-flex; +} + +.select-style svg { + fill: #ccc; + width: 14px; + height: 14px; + pointer-events: none; + margin: auto; +} + +.select-style svg:hover { + fill: #e6e6e6; +} + +.select-style select { + padding: 0; + width: 130%; + border: none; + box-shadow: none; + background: transparent; + background-image: none; + -webkit-appearance: none; + margin: auto; + margin-left: 0px; + margin-right: -20px; +} + +.select-style select:focus { + outline: none; +} + +.select-style :hover { + cursor: pointer; +} + +@media only all and (max-width: 47.938em) { + #breadcrumbs .links, #top-github-link-text { + display: none; + } +} + +.is-sticky #top-bar { + box-shadow: -1px 2px 5px 1px rgba(0, 0, 0, 0.1); +} \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/static/css/hybrid-theme-purple.css b/website/themes/nnja-theme-learn/static/css/hybrid-theme-purple.css new file mode 100644 index 0000000..5998f5a --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/hybrid-theme-purple.css @@ -0,0 +1,108 @@ +/*! + * Agate by Taufik Nurrohman + * ---------------------------------------------------- + * + * #ade5fc + * #a2fca2 + * #c6b4f0 + * #d36363 + * #fcc28c + * #fc9b9b + * #ffa + * #fff + * #333 + * #62c8f3 + * #888 + * + */ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #333; + color: white; +} + +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-code, +.hljs-emphasis { + font-style: italic; +} + +.hljs-tag { + color: #62c8f3; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-selector-id, +.hljs-selector-class { + color: #ade5fc; +} + +.hljs-string, +.hljs-bullet { + color: #a2fca2; +} + +.hljs-type, +.hljs-title, +.hljs-section, +.hljs-attribute, +.hljs-quote, +.hljs-built_in, +.hljs-builtin-name { + color: #ffa; +} + +.hljs-number, +.hljs-symbol, +.hljs-bullet { + color: #d36363; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal { + color: #fcc28c; +} + +.hljs-comment, +.hljs-deletion, +.hljs-code { + color: #888; +} + +.hljs-regexp, +.hljs-link { + color: #c6b4f0; +} + +.hljs-meta { + color: #fc9b9b; +} + +.hljs-deletion { + background-color: #fc9b9b; + color: #333; +} + +.hljs-addition { + background-color: #a2fca2; + color: #333; +} + +.hljs a { + color: inherit; +} + +.hljs a:focus, +.hljs a:hover { + color: inherit; + text-decoration: underline; +} \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/static/css/hybrid.css b/website/themes/nnja-theme-learn/static/css/hybrid.css new file mode 100644 index 0000000..e3999c1 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/hybrid.css @@ -0,0 +1,109 @@ +/*! + * Agate by Taufik Nurrohman + * ---------------------------------------------------- + * + * #ade5fc + * #a2fca2 + * #c6b4f0 + * #d36363 + * #fcc28c + * #fc9b9b + * #ffa + * #fff + * #333 + * #62c8f3 + * #888 + * + */ + +.hljs { + display: block; + overflow-x: auto; + /* padding: 0.5em; */ + /* background: #333; */ + background: #1d1f21; + color: white; +} + +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-code, +.hljs-emphasis { + font-style: italic; +} + +.hljs-tag { + color: #62c8f3; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-selector-id, +.hljs-selector-class { + color: #ade5fc; +} + +.hljs-string, +.hljs-bullet { + color: #a2fca2; +} + +.hljs-type, +.hljs-title, +.hljs-section, +.hljs-attribute, +.hljs-quote, +.hljs-built_in, +.hljs-builtin-name { + color: #ffa; +} + +.hljs-number, +.hljs-symbol, +.hljs-bullet { + color: #d36363; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal { + color: #fcc28c; +} + +.hljs-comment, +.hljs-deletion, +.hljs-code { + color: rgb(186, 173, 219); +} + +.hljs-regexp, +.hljs-link { + color: #c6b4f0; +} + +.hljs-meta { + color: #fc9b9b; +} + +.hljs-deletion { + background-color: #fc9b9b; + color: #333; +} + +.hljs-addition { + background-color: #a2fca2; + color: #333; +} + +.hljs a { + color: inherit; +} + +.hljs a:focus, +.hljs a:hover { + color: inherit; + text-decoration: underline; +} \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/static/css/nucleus.css b/website/themes/nnja-theme-learn/static/css/nucleus.css new file mode 100644 index 0000000..1897fc5 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/nucleus.css @@ -0,0 +1,615 @@ +*, *::before, *::after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + +@-webkit-viewport { + width: device-width; } +@-moz-viewport { + width: device-width; } +@-ms-viewport { + width: device-width; } +@-o-viewport { + width: device-width; } +@viewport { + width: device-width; } +html { + font-size: 100%; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; } + +body { + margin: 0; } + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; } + +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; } + +audio:not([controls]) { + display: none; + height: 0; } + +[hidden], +template { + display: none; } + +a { + background: transparent; + text-decoration: none; } + +a:active, +a:hover { + outline: 0; } + +abbr[title] { + border-bottom: 1px dotted; } + +b, +strong { + font-weight: bold; } + +dfn { + font-style: italic; } + +mark { + background: #FFFF27; + color: #333; } + +sub, +sup { + font-size: 0.8rem; + line-height: 0; + position: relative; + vertical-align: baseline; } + +sup { + top: -0.5em; } + +sub { + bottom: -0.25em; } + +img { + border: 0; + max-width: 100%; } + +svg:not(:root) { + overflow: hidden; } + +figure { + margin: 1em 40px; } + +hr { + height: 0; } + +pre { + overflow: auto; } + +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; } + +button { + overflow: visible; } + +button, +select { + text-transform: none; } + +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; } + +button[disabled], +html input[disabled] { + cursor: default; } + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; } + +input { + line-height: normal; } + +input[type="checkbox"], +input[type="radio"] { + padding: 0; } + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; } + +input[type="search"] { + -webkit-appearance: textfield; } + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; } + +legend { + border: 0; + padding: 0; } + +textarea { + overflow: auto; } + +optgroup { + font-weight: bold; } + +table { + border-collapse: collapse; + border-spacing: 0; + table-layout: fixed; + width: 100%; } + +tr, td, th { + vertical-align: middle; } + +th, td { + padding: 0.425rem 0; } + +th { + text-align: left; } + +.container { + width: 75em; + margin: 0 auto; + padding: 0; } + @media only all and (min-width: 60em) and (max-width: 74.938em) { + .container { + width: 60em; } } + @media only all and (min-width: 48em) and (max-width: 59.938em) { + .container { + width: 48em; } } + @media only all and (min-width: 30.063em) and (max-width: 47.938em) { + .container { + width: 30em; } } + @media only all and (max-width: 30em) { + .container { + width: 100%; } } + +.grid { + display: -webkit-box; + display: -moz-box; + display: box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-flow: row; + -moz-flex-flow: row; + flex-flow: row; + list-style: none; + margin: 0; + padding: 0; } + @media only all and (max-width: 47.938em) { + .grid { + -webkit-flex-flow: row wrap; + -moz-flex-flow: row wrap; + flex-flow: row wrap; } } + +.block { + -webkit-box-flex: 1; + -moz-box-flex: 1; + box-flex: 1; + -webkit-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; + min-width: 0; + min-height: 0; } + @media only all and (max-width: 47.938em) { + .block { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 100%; + -moz-flex: 0 100%; + -ms-flex: 0 100%; + flex: 0 100%; } } + +.content { + margin: 0.625rem; + padding: 0.938rem; } + +@media only all and (max-width: 47.938em) { + body [class*="size-"] { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 100%; + -moz-flex: 0 100%; + -ms-flex: 0 100%; + flex: 0 100%; } } + +.size-1-2 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 50%; + -moz-flex: 0 50%; + -ms-flex: 0 50%; + flex: 0 50%; } + +.size-1-3 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 33.33333%; + -moz-flex: 0 33.33333%; + -ms-flex: 0 33.33333%; + flex: 0 33.33333%; } + +.size-1-4 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 25%; + -moz-flex: 0 25%; + -ms-flex: 0 25%; + flex: 0 25%; } + +.size-1-5 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 20%; + -moz-flex: 0 20%; + -ms-flex: 0 20%; + flex: 0 20%; } + +.size-1-6 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 16.66667%; + -moz-flex: 0 16.66667%; + -ms-flex: 0 16.66667%; + flex: 0 16.66667%; } + +.size-1-7 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 14.28571%; + -moz-flex: 0 14.28571%; + -ms-flex: 0 14.28571%; + flex: 0 14.28571%; } + +.size-1-8 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 12.5%; + -moz-flex: 0 12.5%; + -ms-flex: 0 12.5%; + flex: 0 12.5%; } + +.size-1-9 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 11.11111%; + -moz-flex: 0 11.11111%; + -ms-flex: 0 11.11111%; + flex: 0 11.11111%; } + +.size-1-10 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 10%; + -moz-flex: 0 10%; + -ms-flex: 0 10%; + flex: 0 10%; } + +.size-1-11 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 9.09091%; + -moz-flex: 0 9.09091%; + -ms-flex: 0 9.09091%; + flex: 0 9.09091%; } + +.size-1-12 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 8.33333%; + -moz-flex: 0 8.33333%; + -ms-flex: 0 8.33333%; + flex: 0 8.33333%; } + +@media only all and (min-width: 48em) and (max-width: 59.938em) { + .size-tablet-1-2 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 50%; + -moz-flex: 0 50%; + -ms-flex: 0 50%; + flex: 0 50%; } + + .size-tablet-1-3 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 33.33333%; + -moz-flex: 0 33.33333%; + -ms-flex: 0 33.33333%; + flex: 0 33.33333%; } + + .size-tablet-1-4 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 25%; + -moz-flex: 0 25%; + -ms-flex: 0 25%; + flex: 0 25%; } + + .size-tablet-1-5 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 20%; + -moz-flex: 0 20%; + -ms-flex: 0 20%; + flex: 0 20%; } + + .size-tablet-1-6 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 16.66667%; + -moz-flex: 0 16.66667%; + -ms-flex: 0 16.66667%; + flex: 0 16.66667%; } + + .size-tablet-1-7 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 14.28571%; + -moz-flex: 0 14.28571%; + -ms-flex: 0 14.28571%; + flex: 0 14.28571%; } + + .size-tablet-1-8 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 12.5%; + -moz-flex: 0 12.5%; + -ms-flex: 0 12.5%; + flex: 0 12.5%; } + + .size-tablet-1-9 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 11.11111%; + -moz-flex: 0 11.11111%; + -ms-flex: 0 11.11111%; + flex: 0 11.11111%; } + + .size-tablet-1-10 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 10%; + -moz-flex: 0 10%; + -ms-flex: 0 10%; + flex: 0 10%; } + + .size-tablet-1-11 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 9.09091%; + -moz-flex: 0 9.09091%; + -ms-flex: 0 9.09091%; + flex: 0 9.09091%; } + + .size-tablet-1-12 { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; + -webkit-flex: 0 8.33333%; + -moz-flex: 0 8.33333%; + -ms-flex: 0 8.33333%; + flex: 0 8.33333%; } } +@media only all and (max-width: 47.938em) { + @supports not (flex-wrap: wrap) { + .grid { + display: block; + -webkit-box-lines: inherit; + -moz-box-lines: inherit; + box-lines: inherit; + -webkit-flex-wrap: inherit; + -moz-flex-wrap: inherit; + -ms-flex-wrap: inherit; + flex-wrap: inherit; } + + .block { + display: block; + -webkit-box-flex: inherit; + -moz-box-flex: inherit; + box-flex: inherit; + -webkit-flex: inherit; + -moz-flex: inherit; + -ms-flex: inherit; + flex: inherit; } } } +.first-block { + -webkit-box-ordinal-group: 0; + -webkit-order: -1; + -ms-flex-order: -1; + order: -1; } + +.last-block { + -webkit-box-ordinal-group: 2; + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; } + +.fixed-blocks { + -webkit-flex-flow: row wrap; + -moz-flex-flow: row wrap; + flex-flow: row wrap; } + .fixed-blocks .block { + -webkit-box-flex: inherit; + -moz-box-flex: inherit; + box-flex: inherit; + -webkit-flex: inherit; + -moz-flex: inherit; + -ms-flex: inherit; + flex: inherit; + width: 25%; } + @media only all and (min-width: 60em) and (max-width: 74.938em) { + .fixed-blocks .block { + width: 33.33333%; } } + @media only all and (min-width: 48em) and (max-width: 59.938em) { + .fixed-blocks .block { + width: 50%; } } + @media only all and (max-width: 47.938em) { + .fixed-blocks .block { + width: 100%; } } + +body { + font-size: 1.05rem; + line-height: 1.7; } + +h1, h2, h3, h4, h5, h6 { + margin: 0.85rem 0 1.7rem 0; + text-rendering: optimizeLegibility; } + +h1 { + font-size: 3.25rem; } + +h2 { + font-size: 2.55rem; } + +h3 { + font-size: 2.15rem; } + +h4 { + font-size: 1.8rem; } + +h5 { + font-size: 1.4rem; } + +h6 { + font-size: 0.9rem; } + +p { + margin: 1.7rem 0; } + +ul, ol { + margin-top: 1.7rem; + margin-bottom: 1.7rem; } + ul ul, ul ol, ol ul, ol ol { + margin-top: 0; + margin-bottom: 0; } + +blockquote { + margin: 1.7rem 0; + padding-left: 0.85rem; } + +cite { + display: block; + font-size: 0.925rem; } + cite:before { + content: "\2014 \0020"; } + +pre { + margin: 1.7rem 0; + padding: 0.938rem; } + +code { + vertical-align: bottom; } + +small { + font-size: 0.925rem; } + +hr { + border-left: none; + border-right: none; + border-top: none; + margin: 1.7rem 0; } + +fieldset { + border: 0; + padding: 0.938rem; + margin: 0 0 1.7rem 0; } + +input, +label, +select { + display: block; } + +label { + margin-bottom: 0.425rem; } + label.required:after { + content: "*"; } + label abbr { + display: none; } + +textarea, input[type="email"], input[type="number"], input[type="password"], input[type="search"], input[type="tel"], input[type="text"], input[type="url"], input[type="color"], input[type="date"], input[type="datetime"], input[type="datetime-local"], input[type="month"], input[type="time"], input[type="week"], select[multiple=multiple] { + -webkit-transition: border-color; + -moz-transition: border-color; + transition: border-color; + border-radius: 0.1875rem; + margin-bottom: 0.85rem; + padding: 0.425rem 0.425rem; + width: 100%; } + textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus { + outline: none; } + +textarea { + resize: vertical; } + +input[type="checkbox"], input[type="radio"] { + display: inline; + margin-right: 0.425rem; } + +input[type="file"] { + width: 100%; } + +select { + width: auto; + max-width: 100%; + margin-bottom: 1.7rem; } + +button, +input[type="submit"] { + cursor: pointer; + user-select: none; + vertical-align: middle; + white-space: nowrap; + border: inherit; } diff --git a/website/themes/nnja-theme-learn/static/css/perfect-scrollbar.min.css b/website/themes/nnja-theme-learn/static/css/perfect-scrollbar.min.css new file mode 100644 index 0000000..ebd2cb4 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/perfect-scrollbar.min.css @@ -0,0 +1,2 @@ +/* perfect-scrollbar v0.6.13 */ +.ps-container{-ms-touch-action:auto;touch-action:auto;overflow:hidden !important;-ms-overflow-style:none}@supports (-ms-overflow-style: none){.ps-container{overflow:auto !important}}@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none){.ps-container{overflow:auto !important}}.ps-container.ps-active-x>.ps-scrollbar-x-rail,.ps-container.ps-active-y>.ps-scrollbar-y-rail{display:block;background-color:transparent}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:.9}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999;height:11px}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:.9}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999;width:11px}.ps-container>.ps-scrollbar-x-rail{display:none;position:absolute;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;bottom:0px;height:15px}.ps-container>.ps-scrollbar-x-rail>.ps-scrollbar-x{position:absolute;background-color:#aaa;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;-o-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;-moz-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;bottom:2px;height:6px}.ps-container>.ps-scrollbar-x-rail:hover>.ps-scrollbar-x,.ps-container>.ps-scrollbar-x-rail:active>.ps-scrollbar-x{height:11px}.ps-container>.ps-scrollbar-y-rail{display:none;position:absolute;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;right:0;width:15px}.ps-container>.ps-scrollbar-y-rail>.ps-scrollbar-y{position:absolute;background-color:#aaa;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;-o-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;-moz-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;right:2px;width:6px}.ps-container>.ps-scrollbar-y-rail:hover>.ps-scrollbar-y,.ps-container>.ps-scrollbar-y-rail:active>.ps-scrollbar-y{width:11px}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:.9}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999;height:11px}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:.9}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999;width:11px}.ps-container:hover>.ps-scrollbar-x-rail,.ps-container:hover>.ps-scrollbar-y-rail{opacity:.6}.ps-container:hover>.ps-scrollbar-x-rail:hover{background-color:#eee;opacity:.9}.ps-container:hover>.ps-scrollbar-x-rail:hover>.ps-scrollbar-x{background-color:#999}.ps-container:hover>.ps-scrollbar-y-rail:hover{background-color:#eee;opacity:.9}.ps-container:hover>.ps-scrollbar-y-rail:hover>.ps-scrollbar-y{background-color:#999} diff --git a/website/themes/nnja-theme-learn/static/css/theme-blue.css b/website/themes/nnja-theme-learn/static/css/theme-blue.css new file mode 100644 index 0000000..dc58214 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/theme-blue.css @@ -0,0 +1,104 @@ + +:root{ + + --MAIN-TEXT-color:#323232; /* Color of text by default */ + --MAIN-TITLES-TEXT-color: #5e5e5e; /* Color of titles h2-h3-h4-h5 */ + --MAIN-LINK-color:#1C90F3; /* Color of links */ + --MAIN-LINK-HOVER-color:#167ad0; /* Color of hovered links */ + --MAIN-ANCHOR-color: #1C90F3; /* color of anchors on titles */ + + --MENU-HEADER-BG-color:#1C90F3; /* Background color of menu header */ + --MENU-HEADER-BORDER-color:#33a1ff; /*Color of menu header border */ + + --MENU-SEARCH-BG-color:#167ad0; /* Search field background color (by default borders + icons) */ + --MENU-SEARCH-BOX-color: #33a1ff; /* Override search field border color */ + --MENU-SEARCH-BOX-ICONS-color: #a1d2fd; /* Override search field icons color */ + + --MENU-SECTIONS-ACTIVE-BG-color:#20272b; /* Background color of the active section and its childs */ + --MENU-SECTIONS-BG-color:#252c31; /* Background color of other sections */ + --MENU-SECTIONS-LINK-color: #ccc; /* Color of links in menu */ + --MENU-SECTIONS-LINK-HOVER-color: #e6e6e6; /* Color of links in menu, when hovered */ + --MENU-SECTION-ACTIVE-CATEGORY-color: #777; /* Color of active category text */ + --MENU-SECTION-ACTIVE-CATEGORY-BG-color: #fff; /* Color of background for the active category (only) */ + + --MENU-VISITED-color: #33a1ff; /* Color of 'page visited' icons in menu */ + --MENU-SECTION-HR-color: #20272b; /* Color of
    separator in menu */ + +} + +body { + color: var(--MAIN-TEXT-color) !important; +} + +textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus { + border-color: none; + box-shadow: none; +} + +h2, h3, h4, h5 { + color: var(--MAIN-TITLES-TEXT-color) !important; +} + +a { + color: var(--MAIN-LINK-color); +} + +.anchor { + color: var(--MAIN-ANCHOR-color); +} + +a:hover { + color: var(--MAIN-LINK-HOVER-color); +} + +#sidebar ul li.visited > a .read-icon { + color: var(--MENU-VISITED-color); +} + +#body a.highlight:after { + display: block; + content: ""; + height: 1px; + width: 0%; + -webkit-transition: width 0.5s ease; + -moz-transition: width 0.5s ease; + -ms-transition: width 0.5s ease; + transition: width 0.5s ease; + background-color: var(--MAIN-LINK-HOVER-color); +} +#sidebar { + background-color: var(--MENU-SECTIONS-BG-color); +} +#sidebar #header-wrapper { + background: var(--MENU-HEADER-BG-color); + color: var(--MENU-SEARCH-BOX-color); + border-color: var(--MENU-HEADER-BORDER-color); +} +#sidebar .searchbox { + border-color: var(--MENU-SEARCH-BOX-color); + background: var(--MENU-SEARCH-BG-color); +} +#sidebar ul.topics > li.parent, #sidebar ul.topics > li.active { + background: var(--MENU-SECTIONS-ACTIVE-BG-color); +} +#sidebar .searchbox * { + color: var(--MENU-SEARCH-BOX-ICONS-color); +} + +#sidebar a { + color: var(--MENU-SECTIONS-LINK-color); +} + +#sidebar a:hover { + color: var(--MENU-SECTIONS-LINK-HOVER-color); +} + +#sidebar ul li.active > a { + background: var(--MENU-SECTION-ACTIVE-CATEGORY-BG-color); + color: var(--MENU-SECTION-ACTIVE-CATEGORY-color) !important; +} + +#sidebar hr { + border-color: var(--MENU-SECTION-HR-color); +} + diff --git a/website/themes/nnja-theme-learn/static/css/theme-green.css b/website/themes/nnja-theme-learn/static/css/theme-green.css new file mode 100644 index 0000000..2bbc868 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/theme-green.css @@ -0,0 +1,104 @@ + +:root{ + + --MAIN-TEXT-color:#323232; /* Color of text by default */ + --MAIN-TITLES-TEXT-color: #5e5e5e; /* Color of titles h2-h3-h4-h5 */ + --MAIN-LINK-color:#599a3e; /* Color of links */ + --MAIN-LINK-HOVER-color:#3f6d2c; /* Color of hovered links */ + --MAIN-ANCHOR-color: #599a3e; /* color of anchors on titles */ + + --MENU-HEADER-BG-color:#74b559; /* Background color of menu header */ + --MENU-HEADER-BORDER-color:#9cd484; /*Color of menu header border */ + + --MENU-SEARCH-BG-color:#599a3e; /* Search field background color (by default borders + icons) */ + --MENU-SEARCH-BOX-color: #84c767; /* Override search field border color */ + --MENU-SEARCH-BOX-ICONS-color: #c7f7c4; /* Override search field icons color */ + + --MENU-SECTIONS-ACTIVE-BG-color:#1b211c; /* Background color of the active section and its childs */ + --MENU-SECTIONS-BG-color:#222723; /* Background color of other sections */ + --MENU-SECTIONS-LINK-color: #ccc; /* Color of links in menu */ + --MENU-SECTIONS-LINK-HOVER-color: #e6e6e6; /* Color of links in menu, when hovered */ + --MENU-SECTION-ACTIVE-CATEGORY-color: #777; /* Color of active category text */ + --MENU-SECTION-ACTIVE-CATEGORY-BG-color: #fff; /* Color of background for the active category (only) */ + + --MENU-VISITED-color: #599a3e; /* Color of 'page visited' icons in menu */ + --MENU-SECTION-HR-color: #18211c; /* Color of
    separator in menu */ + +} + +body { + color: var(--MAIN-TEXT-color) !important; +} + +textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus { + border-color: none; + box-shadow: none; +} + +h2, h3, h4, h5 { + color: var(--MAIN-TITLES-TEXT-color) !important; +} + +a { + color: var(--MAIN-LINK-color); +} + +.anchor { + color: var(--MAIN-ANCHOR-color); +} + +a:hover { + color: var(--MAIN-LINK-HOVER-color); +} + +#sidebar ul li.visited > a .read-icon { + color: var(--MENU-VISITED-color); +} + +#body a.highlight:after { + display: block; + content: ""; + height: 1px; + width: 0%; + -webkit-transition: width 0.5s ease; + -moz-transition: width 0.5s ease; + -ms-transition: width 0.5s ease; + transition: width 0.5s ease; + background-color: var(--MAIN-LINK-HOVER-color); +} +#sidebar { + background-color: var(--MENU-SECTIONS-BG-color); +} +#sidebar #header-wrapper { + background: var(--MENU-HEADER-BG-color); + color: var(--MENU-SEARCH-BOX-color); + border-color: var(--MENU-HEADER-BORDER-color); +} +#sidebar .searchbox { + border-color: var(--MENU-SEARCH-BOX-color); + background: var(--MENU-SEARCH-BG-color); +} +#sidebar ul.topics > li.parent, #sidebar ul.topics > li.active { + background: var(--MENU-SECTIONS-ACTIVE-BG-color); +} +#sidebar .searchbox * { + color: var(--MENU-SEARCH-BOX-ICONS-color); +} + +#sidebar a { + color: var(--MENU-SECTIONS-LINK-color); +} + +#sidebar a:hover { + color: var(--MENU-SECTIONS-LINK-HOVER-color); +} + +#sidebar ul li.active > a { + background: var(--MENU-SECTION-ACTIVE-CATEGORY-BG-color); + color: var(--MENU-SECTION-ACTIVE-CATEGORY-color) !important; +} + +#sidebar hr { + border-color: var(--MENU-SECTION-HR-color); +} + diff --git a/website/themes/nnja-theme-learn/static/css/theme-red.css b/website/themes/nnja-theme-learn/static/css/theme-red.css new file mode 100644 index 0000000..8c2401a --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/theme-red.css @@ -0,0 +1,104 @@ + +:root{ + + --MAIN-TEXT-color:#323232; /* Color of text by default */ + --MAIN-TITLES-TEXT-color: #5e5e5e; /* Color of titles h2-h3-h4-h5 */ + --MAIN-LINK-color:#f31c1c; /* Color of links */ + --MAIN-LINK-HOVER-color:#d01616; /* Color of hovered links */ + --MAIN-ANCHOR-color: #f31c1c; /* color of anchors on titles */ + + --MENU-HEADER-BG-color:#dc1010; /* Background color of menu header */ + --MENU-HEADER-BORDER-color:#e23131; /*Color of menu header border */ + + --MENU-SEARCH-BG-color:#b90000; /* Search field background color (by default borders + icons) */ + --MENU-SEARCH-BOX-color: #ef2020; /* Override search field border color */ + --MENU-SEARCH-BOX-ICONS-color: #fda1a1; /* Override search field icons color */ + + --MENU-SECTIONS-ACTIVE-BG-color:#2b2020; /* Background color of the active section and its childs */ + --MENU-SECTIONS-BG-color:#312525; /* Background color of other sections */ + --MENU-SECTIONS-LINK-color: #ccc; /* Color of links in menu */ + --MENU-SECTIONS-LINK-HOVER-color: #e6e6e6; /* Color of links in menu, when hovered */ + --MENU-SECTION-ACTIVE-CATEGORY-color: #777; /* Color of active category text */ + --MENU-SECTION-ACTIVE-CATEGORY-BG-color: #fff; /* Color of background for the active category (only) */ + + --MENU-VISITED-color: #ff3333; /* Color of 'page visited' icons in menu */ + --MENU-SECTION-HR-color: #2b2020; /* Color of
    separator in menu */ + +} + +body { + color: var(--MAIN-TEXT-color) !important; +} + +textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus { + border-color: none; + box-shadow: none; +} + +h2, h3, h4, h5 { + color: var(--MAIN-TITLES-TEXT-color) !important; +} + +a { + color: var(--MAIN-LINK-color); +} + +.anchor { + color: var(--MAIN-ANCHOR-color); +} + +a:hover { + color: var(--MAIN-LINK-HOVER-color); +} + +#sidebar ul li.visited > a .read-icon { + color: var(--MENU-VISITED-color); +} + +#body a.highlight:after { + display: block; + content: ""; + height: 1px; + width: 0%; + -webkit-transition: width 0.5s ease; + -moz-transition: width 0.5s ease; + -ms-transition: width 0.5s ease; + transition: width 0.5s ease; + background-color: var(--MAIN-LINK-HOVER-color); +} +#sidebar { + background-color: var(--MENU-SECTIONS-BG-color); +} +#sidebar #header-wrapper { + background: var(--MENU-HEADER-BG-color); + color: var(--MENU-SEARCH-BOX-color); + border-color: var(--MENU-HEADER-BORDER-color); +} +#sidebar .searchbox { + border-color: var(--MENU-SEARCH-BOX-color); + background: var(--MENU-SEARCH-BG-color); +} +#sidebar ul.topics > li.parent, #sidebar ul.topics > li.active { + background: var(--MENU-SECTIONS-ACTIVE-BG-color); +} +#sidebar .searchbox * { + color: var(--MENU-SEARCH-BOX-ICONS-color); +} + +#sidebar a { + color: var(--MENU-SECTIONS-LINK-color); +} + +#sidebar a:hover { + color: var(--MENU-SECTIONS-LINK-HOVER-color); +} + +#sidebar ul li.active > a { + background: var(--MENU-SECTION-ACTIVE-CATEGORY-BG-color); + color: var(--MENU-SECTION-ACTIVE-CATEGORY-color) !important; +} + +#sidebar hr { + border-color: var(--MENU-SECTION-HR-color); +} + diff --git a/website/themes/nnja-theme-learn/static/css/theme.css b/website/themes/nnja-theme-learn/static/css/theme.css new file mode 100644 index 0000000..964730e --- /dev/null +++ b/website/themes/nnja-theme-learn/static/css/theme.css @@ -0,0 +1,1131 @@ +@charset "UTF-8"; +#top-github-link, #body #breadcrumbs { + position: relative; + top: 50%; + -webkit-transform: translateY(-50%); + -moz-transform: translateY(-50%); + -o-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} +.button, .button-secondary { + display: inline-block; + padding: 7px 12px; +} +.button:active, .button-secondary:active { + margin: 2px 0 -2px 0; +} +@font-face { + font-family: 'Novacento Sans Wide'; + src: url("../fonts/Novecentosanswide-UltraLight-webfont.eot"); + src: url("../fonts/Novecentosanswide-UltraLight-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/Novecentosanswide-UltraLight-webfont.woff2") format("woff2"), url("../fonts/Novecentosanswide-UltraLight-webfont.woff") format("woff"), url("../fonts/Novecentosanswide-UltraLight-webfont.ttf") format("truetype"), url("../fonts/Novecentosanswide-UltraLight-webfont.svg#novecento_sans_wideultralight") format("svg"); + font-style: normal; + font-weight: 200; +} +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 300; + src: url("../fonts/Work_Sans_300.eot?#iefix") format("embedded-opentype"), url("../fonts/Work_Sans_300.woff") format("woff"), url("../fonts/Work_Sans_300.woff2") format("woff2"), url("../fonts/Work_Sans_300.svg#WorkSans") format("svg"), url("../fonts/Work_Sans_300.ttf") format("truetype"); +} +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 500; + src: url("../fonts/Work_Sans_500.eot?#iefix") format("embedded-opentype"), url("../fonts/Work_Sans_500.woff") format("woff"), url("../fonts/Work_Sans_500.woff2") format("woff2"), url("../fonts/Work_Sans_500.svg#WorkSans") format("svg"), url("../fonts/Work_Sans_500.ttf") format("truetype"); +} +body { + background: #fff; + color: #777; +} +body #chapter h1 { + font-size: 3.5rem; +} +@media only all and (min-width: 48em) and (max-width: 59.938em) { + body #chapter h1 { + font-size: 3rem; + } +} +@media only all and (max-width: 47.938em) { + body #chapter h1 { + font-size: 2rem; + } +} +a { + color: #00bdf3; +} +a:hover { + color: #0082a7; +} +pre { + position: relative; + color: #ffffff; +} +.bg { + background: #fff; + border: 1px solid #eaeaea; +} +b, strong, label, th { + font-weight: 600; +} +.default-animation, #header #logo-svg, #header #logo-svg path, #sidebar, #sidebar ul, #body, #body .padding, #body .nav { + -webkit-transition: all 0.5s ease; + -moz-transition: all 0.5s ease; + transition: all 0.5s ease; +} +#grav-logo { + max-width: 60%; +} +#grav-logo path { + fill: #fff !important; +} +#sidebar { + font-weight: 300 !important; +} +fieldset { + border: 1px solid #ddd; +} +textarea, input[type="email"], input[type="number"], input[type="password"], input[type="search"], input[type="tel"], input[type="text"], input[type="url"], input[type="color"], input[type="date"], input[type="datetime"], input[type="datetime-local"], input[type="month"], input[type="time"], input[type="week"], select[multiple=multiple] { + background-color: white; + border: 1px solid #ddd; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.06); +} +textarea:hover, input[type="email"]:hover, input[type="number"]:hover, input[type="password"]:hover, input[type="search"]:hover, input[type="tel"]:hover, input[type="text"]:hover, input[type="url"]:hover, input[type="color"]:hover, input[type="date"]:hover, input[type="datetime"]:hover, input[type="datetime-local"]:hover, input[type="month"]:hover, input[type="time"]:hover, input[type="week"]:hover, select[multiple=multiple]:hover { + border-color: #c4c4c4; +} +textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus { + border-color: #00bdf3; + box-shadow: inset 0 1px 3px rgba(0,0,0,.06),0 0 5px rgba(0,169,218,.7) +} +#header-wrapper { + background: #8451a1; + color: #fff; + text-align: center; + border-bottom: 4px solid #9c6fb6; + padding: 1rem; +} +#header a { + display: inline-block; +} +#header #logo-svg { + width: 8rem; + height: 2rem; +} +#header #logo-svg path { + fill: #fff; +} +.expand-content { + padding-left: 30px; +} +.searchbox { + margin-top: 1rem; + position: relative; + border: 1px solid #915eae; + background: #764890; + border-radius: 4px; +} +.searchbox label { + color: rgba(255, 255, 255, 0.8); + position: absolute; + left: 10px; + top: 3px; +} +.searchbox span { + color: rgba(255, 255, 255, 0.6); + position: absolute; + right: 10px; + top: 3px; + cursor: pointer; +} +.searchbox span:hover { + color: rgba(255, 255, 255, 0.9); +} +.searchbox input { + display: inline-block; + color: #fff; + width: 100%; + height: 30px; + background: transparent; + border: 0; + padding: 0 25px 0 30px; + margin: 0; + font-weight: 300; +} +.searchbox input::-webkit-input-placeholder { + color: rgba(255, 255, 255, 0.6); +} +.searchbox input::-moz-placeholder { + color: rgba(255, 255, 255, 0.6); +} +.searchbox input:-moz-placeholder { + color: rgba(255, 255, 255, 0.6); +} +.searchbox input:-ms-input-placeholder { + color: rgba(255, 255, 255, 0.6); +} +#sidebar-toggle-span { + display: none; +} +@media only all and (max-width: 47.938em) { + #sidebar-toggle-span { + display: inline; + } +} +#sidebar { + background-color: #322A38; + position: fixed; + top: 0; + width: 300px; + bottom: 0; + left: 0; + font-weight: 400; + font-size: 15px; +} +#sidebar a { + color: #ccc; +} +#sidebar a:hover { + color: #e6e6e6; +} +#sidebar a.subtitle { + color: rgba(204, 204, 204, 0.6); +} +#sidebar hr { + border-bottom: 1px solid #2a232f; +} +#sidebar a.padding { + padding: 0 1rem; +} +#sidebar h5 { + margin: 2rem 0 0; + position: relative; + line-height: 2; +} +#sidebar h5 a { + display: block; + margin-left: 0; + margin-right: 0; + padding-left: 1rem; + padding-right: 1rem; +} +#sidebar h5 i { + color: rgba(204, 204, 204, 0.6); + position: absolute; + right: 0.6rem; + top: 0.7rem; + font-size: 80%; +} +#sidebar h5.parent a { + background: #201b24; + color: #d9d9d9 !important; +} +#sidebar h5.active a { + background: #fff; + color: #777 !important; +} +#sidebar h5.active i { + color: #777 !important; +} +#sidebar h5 + ul.topics { + display: none; + margin-top: 0; +} +#sidebar h5.parent + ul.topics, #sidebar h5.active + ul.topics { + display: block; +} +#sidebar ul { + list-style: none; + padding: 0; + margin: 0; +} +#sidebar ul.searched a { + color: #999999; +} +#sidebar ul.searched .search-match a { + color: #e6e6e6; +} +#sidebar ul.searched .search-match a:hover { + color: white; +} +#sidebar ul.topics { + margin: 0 1rem; +} +#sidebar ul.topics.searched ul { + display: block; +} +#sidebar ul.topics ul { + display: none; + padding-bottom: 1rem; +} +#sidebar ul.topics ul ul { + padding-bottom: 0; +} +#sidebar ul.topics li.parent ul, #sidebar ul.topics > li.active ul { + display: block; +} +#sidebar ul.topics > li > a { + line-height: 2rem; + font-size: 1.1rem; +} +#sidebar ul.topics > li > a b { + opacity: 0.5; + font-weight: normal; +} +#sidebar ul.topics > li > a .fa { + margin-top: 9px; +} +#sidebar ul.topics > li.parent, #sidebar ul.topics > li.active { + background: #251f29; + margin-left: -1rem; + margin-right: -1rem; + padding-left: 1rem; + padding-right: 1rem; +} +#sidebar ul li.active > a { + background: #fff; + color: #777 !important; + margin-left: -1rem; + margin-right: -1rem; + padding-left: 1rem; + padding-right: 1rem; +} +#sidebar ul li { + padding: 0; +} +#sidebar ul li.visited + span { + margin-right: 16px; +} +#sidebar ul li a { + display: block; + padding: 2px 0; +} +#sidebar ul li a span { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + display: block; +} +#sidebar ul li > a { + padding: 4px 0; +} +#sidebar ul li.visited > a .read-icon { + color: #9c6fb6; + display: inline; +} +#sidebar ul li li { + padding-left: 1rem; + text-indent: 0.2rem; +} +#main { + background: #f7f7f7; + margin: 0 0 1.563rem 0; +} +#body { + position: relative; + margin-left: 300px; + min-height: 100%; +} +#body img, #body .video-container { + margin: 3rem auto; + display: block; + text-align: center; +} +#body img.border, #body .video-container.border { + border: 2px solid #e6e6e6 !important; + padding: 2px; +} +#body img.shadow, #body .video-container.shadow { + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); +} +#body img.inline { + display: inline !important; + margin: 0 !important; + vertical-align: bottom; +} +#body .bordered { + border: 1px solid #ccc; +} +#body .padding { + padding: 3rem 6rem; +} +@media only all and (max-width: 59.938em) { + #body .padding { + position: static; + padding: 15px 3rem; + } +} +@media only all and (max-width: 47.938em) { + #body .padding { + padding: 5px 1rem; + } +} +#body h1 + hr { + margin-top: -1.7rem; + margin-bottom: 3rem; +} +@media only all and (max-width: 59.938em) { + #body #navigation { + position: static; + margin-right: 0 !important; + width: 100%; + display: table; + } +} +#body .nav { + position: fixed; + top: 0; + bottom: 0; + width: 4rem; + font-size: 50px; + height: 100%; + cursor: pointer; + display: table; + text-align: center; +} +#body .nav > i { + display: table-cell; + vertical-align: middle; + text-align: center; +} +@media only all and (max-width: 59.938em) { + #body .nav { + display: table-cell; + position: static; + top: auto; + width: 50%; + text-align: center; + height: 100px; + line-height: 100px; + padding-top: 0; + } + #body .nav > i { + display: inline-block; + } +} +#body .nav:hover { + background: #F6F6F6; +} +#body .nav.nav-pref { + left: 0; +} +#body .nav.nav-next { + right: 0; +} +#body-inner { + margin-bottom: 5rem; +} +#chapter { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + padding: 2rem 0; +} +#chapter #body-inner { + padding-bottom: 3rem; + max-width: 80%; +} +#chapter h3 { + font-family: "Work Sans", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + font-weight: 300; + text-align: center; +} +#chapter h1 { + font-size: 5rem; + border-bottom: 4px solid #F0F2F4; +} +#chapter p { + text-align: center; + font-size: 1.2rem; +} +#footer { + padding: 3rem 1rem; + color: #b3b3b3; + font-size: 13px; +} +#footer p { + margin: 0; +} +body { + font-family: "Work Sans", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + font-weight: 300; + line-height: 1.6; + font-size: 18px !important; +} +h2, h3, h4, h5, h6 { + font-family: "Work Sans", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + text-rendering: optimizeLegibility; + color: #5e5e5e; + font-weight: 400; + letter-spacing: -1px; +} +h1 { + font-family: "Novacento Sans Wide", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + text-align: center; + text-transform: uppercase; + color: #222; + font-weight: 200; +} +blockquote { + border-left: 10px solid #F0F2F4; +} +blockquote p { + font-size: 1.1rem; + color: #999; +} +blockquote cite { + display: block; + text-align: right; + color: #666; + font-size: 1.2rem; +} +div.notices { + margin: 2rem 0; + position: relative; +} +div.notices p { + padding: 15px; + display: block; + font-size: 1rem; + margin-top: 0rem; + margin-bottom: 0rem; + color: #666; +} +div.notices p:first-child:before { + position: absolute; + top: 2px; + color: #fff; + font-family: "Font Awesome 5 Free"; + font-weight: 900; + content: "\f06a"; + left: 10px; +} +div.notices p:first-child:after { + position: absolute; + top: 2px; + color: #fff; + left: 2rem; +} +div.notices.info p { + border-top: 30px solid #F0B37E; + background: #FFF2DB; +} +div.notices.info p:first-child:after { + content: 'Info'; +} +div.notices.warning p { + border-top: 30px solid rgba(217, 83, 79, 0.8); + background: #FAE2E2; +} +div.notices.warning p:first-child:after { + content: 'Warning'; +} +div.notices.note p { + border-top: 30px solid #6AB0DE; + background: #E7F2FA; +} +div.notices.note p:first-child:after { + content: 'Note'; +} +div.notices.tip p { + border-top: 30px solid rgba(92, 184, 92, 0.8); + background: #E6F9E6; +} +div.notices.tip p:first-child:after { + content: 'Tip'; +} + +/* attachments shortcode */ + +section.attachments { + margin: 2rem 0; + position: relative; +} + +section.attachments label { + font-weight: 400; + padding-left: 0.5em; + padding-top: 0.2em; + padding-bottom: 0.2em; + margin: 0; +} + +section.attachments .attachments-files { + padding: 15px; + display: block; + font-size: 1rem; + margin-top: 0rem; + margin-bottom: 0rem; + color: #666; +} + +section.attachments.orange label { + color: #fff; + background: #F0B37E; +} + +section.attachments.orange .attachments-files { + background: #FFF2DB; +} + +section.attachments.green label { + color: #fff; + background: rgba(92, 184, 92, 0.8); +} + +section.attachments.green .attachments-files { + background: #E6F9E6; +} + +section.attachments.blue label { + color: #fff; + background: #6AB0DE; +} + +section.attachments.blue .attachments-files { + background: #E7F2FA; +} + +section.attachments.grey label { + color: #fff; + background: #505d65; +} + +section.attachments.grey .attachments-files { + background: #f4f4f4; +} + +/* Children shortcode */ + +/* Children shortcode */ +.children p { + font-size: small; + margin-top: 0px; + padding-top: 0px; + margin-bottom: 0px; + padding-bottom: 0px; +} +.children-li p { + font-size: small; + font-style: italic; + +} +.children-h2 p, .children-h3 p { + font-size: small; + margin-top: 0px; + padding-top: 0px; + margin-bottom: 0px; + padding-bottom: 0px; +} +.children h3,.children h2 { + margin-bottom: 0px; + margin-top: 5px; +} + +code, kbd, pre, samp { + font-family: "Consolas", menlo, monospace; + font-size: 92%; +} +code { + border-radius: 2px; + white-space: nowrap; + color: #5e5e5e; + background: #FFF7DD; + border: 1px solid #fbf0cb; + padding: 0px 2px; +} +code + .copy-to-clipboard { + margin-left: -1px; + border-left: 0 !important; + font-size: inherit !important; + vertical-align: middle; + height: 21px; + top: 0; +} +pre { + padding: 1rem; + margin: 2rem 0; + background: #1d1f21; + border: 0; + border-radius: 2px; + line-height: 1.15; +} +pre code { + color: whitesmoke; + background: inherit; + white-space: inherit; + border: 0; + padding: 0; + margin: 0; + font-size: 15px; +} +hr { + border-bottom: 4px solid #F0F2F4; +} +.page-title { + margin-top: -25px; + padding: 25px; + float: left; + clear: both; + background: #9c6fb6; + color: #fff; +} +#body a.anchor-link { + color: #ccc; +} +#body a.anchor-link:hover { + color: #9c6fb6; +} +#body-inner .tabs-wrapper.ui-theme-badges { + background: #1d1f21; +} +#body-inner .tabs-wrapper.ui-theme-badges .tabs-nav li { + font-size: 0.9rem; + text-transform: uppercase; +} +#body-inner .tabs-wrapper.ui-theme-badges .tabs-nav li a { + background: #35393c; +} +#body-inner .tabs-wrapper.ui-theme-badges .tabs-nav li.current a { + background: #4d5257; +} +#body-inner pre { + white-space: pre-wrap; +} +.tabs-wrapper pre { + margin: 1rem 0; + border: 0; + padding: 0; + background: inherit; +} +table { + border: 1px solid #eaeaea; + table-layout: auto; +} +th { + background: #f7f7f7; + padding: 0.5rem; +} +td { + padding: 0.5rem; + border: 1px solid #eaeaea; +} +.button { + background: #9c6fb6; + color: #fff; + box-shadow: 0 3px 0 #00a5d4; +} +.button:hover { + background: #00a5d4; + box-shadow: 0 3px 0 #008db6; + color: #fff; +} +.button:active { + box-shadow: 0 1px 0 #008db6; +} +.button-secondary { + background: #F8B450; + color: #fff; + box-shadow: 0 3px 0 #f7a733; +} +.button-secondary:hover { + background: #f7a733; + box-shadow: 0 3px 0 #f69b15; + color: #fff; +} +.button-secondary:active { + box-shadow: 0 1px 0 #f69b15; +} +.bullets { + margin: 1.7rem 0; + margin-left: -0.85rem; + margin-right: -0.85rem; + overflow: auto; +} +.bullet { + float: left; + padding: 0 0.85rem; +} +.two-column-bullet { + width: 50%; +} +@media only all and (max-width: 47.938em) { + .two-column-bullet { + width: 100%; + } +} +.three-column-bullet { + width: 33.33333%; +} +@media only all and (max-width: 47.938em) { + .three-column-bullet { + width: 100%; + } +} +.four-column-bullet { + width: 25%; +} +@media only all and (max-width: 47.938em) { + .four-column-bullet { + width: 100%; + } +} +.bullet-icon { + float: left; + background: #9c6fb6; + padding: 0.875rem; + width: 3.5rem; + height: 3.5rem; + border-radius: 50%; + color: #fff; + font-size: 1.75rem; + text-align: center; +} +.bullet-icon-1 { + background: #9c6fb6; +} +.bullet-icon-2 { + background: #00f3d8; +} +.bullet-icon-3 { + background: #e6f300; +} +.bullet-content { + margin-left: 4.55rem; +} +.tooltipped { + position: relative; +} +.tooltipped:after { + position: absolute; + z-index: 1000000; + display: none; + padding: 5px 8px; + font: normal normal 11px/1.5 "Work Sans", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + color: #fff; + text-align: center; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-wrap: break-word; + white-space: pre; + pointer-events: none; + content: attr(aria-label); + background: rgba(0, 0, 0, 0.8); + border-radius: 3px; + -webkit-font-smoothing: subpixel-antialiased; +} +.tooltipped:before { + position: absolute; + z-index: 1000001; + display: none; + width: 0; + height: 0; + color: rgba(0, 0, 0, 0.8); + pointer-events: none; + content: ""; + border: 5px solid transparent; +} +.tooltipped:hover:before, .tooltipped:hover:after, .tooltipped:active:before, .tooltipped:active:after, .tooltipped:focus:before, .tooltipped:focus:after { + display: inline-block; + text-decoration: none; +} +.tooltipped-s:after, .tooltipped-se:after, .tooltipped-sw:after { + top: 100%; + right: 50%; + margin-top: 5px; +} +.tooltipped-s:before, .tooltipped-se:before, .tooltipped-sw:before { + top: auto; + right: 50%; + bottom: -5px; + margin-right: -5px; + border-bottom-color: rgba(0, 0, 0, 0.8); +} +.tooltipped-se:after { + right: auto; + left: 50%; + margin-left: -15px; +} +.tooltipped-sw:after { + margin-right: -15px; +} +.tooltipped-n:after, .tooltipped-ne:after, .tooltipped-nw:after { + right: 50%; + bottom: 100%; + margin-bottom: 5px; +} +.tooltipped-n:before, .tooltipped-ne:before, .tooltipped-nw:before { + top: -5px; + right: 50%; + bottom: auto; + margin-right: -5px; + border-top-color: rgba(0, 0, 0, 0.8); +} +.tooltipped-ne:after { + right: auto; + left: 50%; + margin-left: -15px; +} +.tooltipped-nw:after { + margin-right: -15px; +} +.tooltipped-s:after, .tooltipped-n:after { + transform: translateX(50%); +} +.tooltipped-w:after { + right: 100%; + bottom: 50%; + margin-right: 5px; + transform: translateY(50%); +} +.tooltipped-w:before { + top: 50%; + bottom: 50%; + left: -5px; + margin-top: -5px; + border-left-color: rgba(0, 0, 0, 0.8); +} +.tooltipped-e:after { + bottom: 50%; + left: 100%; + margin-left: 5px; + transform: translateY(50%); +} +.tooltipped-e:before { + top: 50%; + right: -5px; + bottom: 50%; + margin-top: -5px; + border-right-color: rgba(0, 0, 0, 0.8); +} +.highlightable { + padding: 1rem 0 1rem; + overflow: auto; + position: relative; +} +.hljs::selection, .hljs span::selection { + background: #b7b7b7; +} +.lightbox-active #body { + overflow: visible; +} +.lightbox-active #body .padding { + overflow: visible; +} +#github-contrib i { + vertical-align: middle; +} +.featherlight img { + margin: 0 !important; +} +.lifecycle #body-inner ul { + list-style: none; + margin: 0; + padding: 2rem 0 0; + position: relative; +} +.lifecycle #body-inner ol { + margin: 1rem 0 1rem 0; + padding: 2rem; + position: relative; +} +.lifecycle #body-inner ol li { + margin-left: 1rem; +} +.lifecycle #body-inner ol strong, .lifecycle #body-inner ol label, .lifecycle #body-inner ol th { + text-decoration: underline; +} +.lifecycle #body-inner ol ol { + margin-left: -1rem; +} +.lifecycle #body-inner h3[class*='level'] { + font-size: 20px; + position: absolute; + margin: 0; + padding: 4px 10px; + right: 0; + z-index: 1000; + color: #fff; + background: #1ABC9C; +} +.lifecycle #body-inner ol h3 { + margin-top: 1rem !important; + right: 2rem !important; +} +.lifecycle #body-inner .level-1 + ol { + background: #f6fefc; + border: 4px solid #1ABC9C; + color: #16A085; +} +.lifecycle #body-inner .level-1 + ol h3 { + background: #2ECC71; +} +.lifecycle #body-inner .level-2 + ol { + background: #f7fdf9; + border: 4px solid #2ECC71; + color: #27AE60; +} +.lifecycle #body-inner .level-2 + ol h3 { + background: #3498DB; +} +.lifecycle #body-inner .level-3 + ol { + background: #f3f9fd; + border: 4px solid #3498DB; + color: #2980B9; +} +.lifecycle #body-inner .level-3 + ol h3 { + background: #34495E; +} +.lifecycle #body-inner .level-4 + ol { + background: #e4eaf0; + border: 4px solid #34495E; + color: #2C3E50; +} +.lifecycle #body-inner .level-4 + ol h3 { + background: #34495E; +} +#top-bar { + background: #F6F6F6; + border-radius: 2px; + padding: 0 1rem; + height: 0; + min-height: 3rem; +} +#top-github-link { + position: relative; + z-index: 1; + float: right; + display: block; +} +#body #breadcrumbs { + height: auto; + margin-bottom: 0; + padding-left: 0; + line-height: 1.4; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + width: 70%; + display: inline-block; + float: left; +} +#body #breadcrumbs span { + padding: 0 0.1rem; +} +@media only all and (max-width: 59.938em) { + #sidebar { + width: 230px; + } + #body { + margin-left: 230px; + } +} +@media only all and (max-width: 47.938em) { + #sidebar { + width: 230px; + left: -230px; + } + #body { + margin-left: 0; + width: 100%; + } + .sidebar-hidden { + overflow: hidden; + } + .sidebar-hidden #sidebar { + left: 0; + } + .sidebar-hidden #body { + margin-left: 230px; + overflow: hidden; + } + .sidebar-hidden #overlay { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 10; + background: rgba(255, 255, 255, 0.5); + cursor: pointer; + } +} +.copy-to-clipboard { + background-image: url(../images/clippy.svg); + background-position: 50% 50%; + background-size: 16px 16px; + background-repeat: no-repeat; + width: 27px; + height: 1.45rem; + top: -1px; + display: inline-block; + vertical-align: middle; + position: relative; + color: #5e5e5e; + background-color: #FFF7DD; + margin-left: -.2rem; + cursor: pointer; + border-radius: 0 2px 2px 0; + margin-bottom: 1px; +} +.copy-to-clipboard:hover { + background-color: #E8E2CD; +} +pre .copy-to-clipboard { + position: absolute; + right: 4px; + top: 12px; + background-color:#C1C4C6; + color: #ccc; + border-radius: 2px; +} +pre .copy-to-clipboard:hover { + background-color: rgb(28, 144, 243); + color: #fff; +} +.parent-element { + -webkit-transform-style: preserve-3d; + -moz-transform-style: preserve-3d; + transform-style: preserve-3d; +} + +#sidebar ul.topics > li > a .read-icon { + margin-top: 9px; +} + +#sidebar ul { + list-style: none; + padding: 0; + margin: 0; +} + +#sidebar #shortcuts li { + padding: 2px 0; + list-style: none; +} + +#sidebar ul li .read-icon { + display: none; + float: right; + font-size: 13px; + min-width: 16px; + margin: 4px 0 0 0; + text-align: right; +} +#sidebar ul li.visited > a .read-icon { + color: #00bdf3; + display: inline; +} + +#sidebar #shortcuts h3 { + font-family: "Novacento Sans Wide", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + color: white ; + margin-top:1rem; + padding-left: 1rem; +} + +#searchResults { + text-align: left; +} \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/static/fonts/Inconsolata.eot b/website/themes/nnja-theme-learn/static/fonts/Inconsolata.eot new file mode 100644 index 0000000..0a705d6 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Inconsolata.eot differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Inconsolata.svg b/website/themes/nnja-theme-learn/static/fonts/Inconsolata.svg new file mode 100644 index 0000000..b7f97c8 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/fonts/Inconsolata.svg @@ -0,0 +1,359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/themes/nnja-theme-learn/static/fonts/Inconsolata.ttf b/website/themes/nnja-theme-learn/static/fonts/Inconsolata.ttf new file mode 100644 index 0000000..4b8a36d Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Inconsolata.ttf differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Inconsolata.woff b/website/themes/nnja-theme-learn/static/fonts/Inconsolata.woff new file mode 100644 index 0000000..6f39625 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Inconsolata.woff differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.eot b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.eot new file mode 100644 index 0000000..9984682 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.eot differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.svg b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.svg new file mode 100644 index 0000000..c412ea8 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.svg @@ -0,0 +1,1019 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.ttf b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.ttf new file mode 100644 index 0000000..8cfb62d Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.ttf differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.woff b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.woff new file mode 100644 index 0000000..d5c4290 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.woff differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.woff2 b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.woff2 new file mode 100644 index 0000000..eefb4a3 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.woff2 differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.eot b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.eot new file mode 100644 index 0000000..2a26561 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.eot differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.svg b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.svg new file mode 100644 index 0000000..e642ab0 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.svg @@ -0,0 +1,918 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.ttf b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.ttf new file mode 100644 index 0000000..9ce9c7f Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.ttf differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.woff b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.woff new file mode 100644 index 0000000..381650c Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.woff differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.woff2 b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.woff2 new file mode 100644 index 0000000..7e65954 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.woff2 differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.eot b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.eot new file mode 100644 index 0000000..4052e4f Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.eot differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.svg b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.svg new file mode 100644 index 0000000..58ab4ba --- /dev/null +++ b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.svg @@ -0,0 +1,332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.ttf b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.ttf new file mode 100644 index 0000000..68019e1 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.ttf differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.woff b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.woff new file mode 100644 index 0000000..a1bd9e4 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.woff differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.woff2 b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.woff2 new file mode 100644 index 0000000..20c68a7 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_200.woff2 differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.eot b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.eot new file mode 100644 index 0000000..ace7993 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.eot differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.svg b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.svg new file mode 100644 index 0000000..f29d0c8 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.svg @@ -0,0 +1,331 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.ttf b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.ttf new file mode 100644 index 0000000..35387c2 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.ttf differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.woff b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.woff new file mode 100644 index 0000000..8d789ea Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.woff differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.woff2 b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.woff2 new file mode 100644 index 0000000..f6e216d Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_300.woff2 differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.eot b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.eot new file mode 100644 index 0000000..9df6929 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.eot differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.svg b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.svg new file mode 100644 index 0000000..4b030b7 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.svg @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.ttf b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.ttf new file mode 100644 index 0000000..5b8cc53 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.ttf differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.woff b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.woff new file mode 100644 index 0000000..df05851 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.woff differ diff --git a/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.woff2 b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.woff2 new file mode 100644 index 0000000..b06c54d Binary files /dev/null and b/website/themes/nnja-theme-learn/static/fonts/Work_Sans_500.woff2 differ diff --git a/website/themes/nnja-theme-learn/static/images/clippy.svg b/website/themes/nnja-theme-learn/static/images/clippy.svg new file mode 100644 index 0000000..1c8abc2 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/images/clippy.svg @@ -0,0 +1 @@ + diff --git a/website/themes/nnja-theme-learn/static/images/favicon.png b/website/themes/nnja-theme-learn/static/images/favicon.png new file mode 100644 index 0000000..0f3bda8 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/images/favicon.png differ diff --git a/website/themes/nnja-theme-learn/static/images/gopher-404.jpg b/website/themes/nnja-theme-learn/static/images/gopher-404.jpg new file mode 100644 index 0000000..df10648 Binary files /dev/null and b/website/themes/nnja-theme-learn/static/images/gopher-404.jpg differ diff --git a/website/themes/nnja-theme-learn/static/js/auto-complete.js b/website/themes/nnja-theme-learn/static/js/auto-complete.js new file mode 100644 index 0000000..7fbde99 --- /dev/null +++ b/website/themes/nnja-theme-learn/static/js/auto-complete.js @@ -0,0 +1,223 @@ +/* + JavaScript autoComplete v1.0.4 + Copyright (c) 2014 Simon Steinberger / Pixabay + GitHub: https://github.com/Pixabay/JavaScript-autoComplete + License: http://www.opensource.org/licenses/mit-license.php +*/ + +var autoComplete = (function(){ + // "use strict"; + function autoComplete(options){ + if (!document.querySelector) return; + + // helpers + function hasClass(el, className){ return el.classList ? el.classList.contains(className) : new RegExp('\\b'+ className+'\\b').test(el.className); } + + function addEvent(el, type, handler){ + if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler); + } + function removeEvent(el, type, handler){ + // if (el.removeEventListener) not working in IE11 + if (el.detachEvent) el.detachEvent('on'+type, handler); else el.removeEventListener(type, handler); + } + function live(elClass, event, cb, context){ + addEvent(context || document, event, function(e){ + var found, el = e.target || e.srcElement; + while (el && !(found = hasClass(el, elClass))) el = el.parentElement; + if (found) cb.call(el, e); + }); + } + + var o = { + selector: 0, + source: 0, + minChars: 3, + delay: 150, + offsetLeft: 0, + offsetTop: 1, + cache: 1, + menuClass: '', + renderItem: function (item, search){ + // escape special characters + search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + var re = new RegExp("(" + search.split(' ').join('|') + ")", "gi"); + return '
    ' + item.replace(re, "$1") + '
    '; + }, + onSelect: function(e, term, item){} + }; + for (var k in options) { if (options.hasOwnProperty(k)) o[k] = options[k]; } + + // init + var elems = typeof o.selector == 'object' ? [o.selector] : document.querySelectorAll(o.selector); + for (var i=0; i 0) + that.sc.scrollTop = selTop + that.sc.suggestionHeight + scrTop - that.sc.maxHeight; + else if (selTop < 0) + that.sc.scrollTop = selTop + scrTop; + } + } + } + addEvent(window, 'resize', that.updateSC); + document.body.appendChild(that.sc); + + live('autocomplete-suggestion', 'mouseleave', function(e){ + var sel = that.sc.querySelector('.autocomplete-suggestion.selected'); + if (sel) setTimeout(function(){ sel.className = sel.className.replace('selected', ''); }, 20); + }, that.sc); + + live('autocomplete-suggestion', 'mouseover', function(e){ + var sel = that.sc.querySelector('.autocomplete-suggestion.selected'); + if (sel) sel.className = sel.className.replace('selected', ''); + this.className += ' selected'; + }, that.sc); + + live('autocomplete-suggestion', 'mousedown', function(e){ + if (hasClass(this, 'autocomplete-suggestion')) { // else outside click + var v = this.getAttribute('data-val'); + that.value = v; + o.onSelect(e, v, this); + that.sc.style.display = 'none'; + } + }, that.sc); + + that.blurHandler = function(){ + try { var over_sb = document.querySelector('.autocomplete-suggestions:hover'); } catch(e){ var over_sb = 0; } + if (!over_sb) { + that.last_val = that.value; + that.sc.style.display = 'none'; + setTimeout(function(){ that.sc.style.display = 'none'; }, 350); // hide suggestions on fast input + } else if (that !== document.activeElement) setTimeout(function(){ that.focus(); }, 20); + }; + addEvent(that, 'blur', that.blurHandler); + + var suggest = function(data){ + var val = that.value; + that.cache[val] = data; + if (data.length && val.length >= o.minChars) { + var s = ''; + for (var i=0;i 40) && key != 13 && key != 27) { + var val = that.value; + if (val.length >= o.minChars) { + if (val != that.last_val) { + that.last_val = val; + clearTimeout(that.timer); + if (o.cache) { + if (val in that.cache) { suggest(that.cache[val]); return; } + // no requests if previous suggestions were empty + for (var i=1; ir;r++)n[r].fn.apply(n[r].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),r=n[t],o=[];if(r&&e)for(var i=0,a=r.length;a>i;i++)r[i].fn!==e&&r[i].fn._!==e&&o.push(r[i]);return o.length?n[t]=o:delete n[t],this}},e.exports=r},{}],8:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}n.__esModule=!0;var i=function(){function t(t,e){for(var n=0;n0})},e=function(a,b){var c={},d=new RegExp("^"+b+"([A-Z])(.*)");for(var e in a){var f=e.match(d);if(f){var g=(f[1]+f[2].replace(/([A-Z])/g,"-$1")).toLowerCase();c[g]=a[e]}}return c},f={keyup:"onKeyUp",resize:"onResize"},g=function(c){a.each(b.opened().reverse(),function(){return c.isDefaultPrevented()||!1!==this[f[c.type]](c)?void 0:(c.preventDefault(),c.stopPropagation(),!1)})},h=function(c){if(c!==b._globalHandlerInstalled){b._globalHandlerInstalled=c;var d=a.map(f,function(a,c){return c+"."+b.prototype.namespace}).join(" ");a(window)[c?"on":"off"](d,g)}};b.prototype={constructor:b,namespace:"featherlight",targetAttr:"data-featherlight",variant:null,resetCss:!1,background:null,openTrigger:"click",closeTrigger:"click",filter:null,root:"body",openSpeed:250,closeSpeed:250,closeOnClick:"background",closeOnEsc:!0,closeIcon:"✕",loading:"",otherClose:null,beforeOpen:a.noop,beforeContent:a.noop,beforeClose:a.noop,afterOpen:a.noop,afterContent:a.noop,afterClose:a.noop,onKeyUp:a.noop,onResize:a.noop,type:null,contentFilters:["jquery","image","html","ajax","iframe","text"],setup:function(b,c){"object"!=typeof b||b instanceof a!=!1||c||(c=b,b=void 0);var d=a.extend(this,c,{target:b}),e=d.resetCss?d.namespace+"-reset":d.namespace,f=a(d.background||['
    ','
    ','',d.closeIcon,"",'
    '+d.loading+"
    ","
    ","
    "].join("")),g="."+d.namespace+"-close"+(d.otherClose?","+d.otherClose:"");return d.$instance=f.clone().addClass(d.variant),d.$instance.on(d.closeTrigger+"."+d.namespace,function(b){var c=a(b.target);("background"===d.closeOnClick&&c.is("."+d.namespace)||"anywhere"===d.closeOnClick||c.closest(g).length)&&(b.preventDefault(),d.close())}),this},getContent:function(){var b=this,c=this.constructor.contentFilters,d=function(a){return b.$currentTarget&&b.$currentTarget.attr(a)},e=d(b.targetAttr),f=b.target||e||"",g=c[b.type];if(!g&&f in c&&(g=c[f],f=b.target&&e),f=f||d("href")||"",!g)for(var h in c)b[h]&&(g=c[h],f=b[h]);if(!g){var i=f;if(f=null,a.each(b.contentFilters,function(){return g=c[this],g.test&&(f=g.test(i)),!f&&g.regex&&i.match&&i.match(g.regex)&&(f=i),!f}),!f)return"console"in window&&window.console.error("Featherlight: no content filter found "+(i?' for "'+i+'"':" (no target specified)")),!1}return g.process.call(b,f)},setContent:function(b){var c=this;return(b.is("iframe")||a("iframe",b).length>0)&&c.$instance.addClass(c.namespace+"-iframe"),c.$instance.removeClass(c.namespace+"-loading"),c.$instance.find("."+c.namespace+"-inner").slice(1).remove().end().replaceWith(a.contains(c.$instance[0],b[0])?"":b),c.$content=b.addClass(c.namespace+"-inner"),c},open:function(b){var d=this;if(d.$instance.hide().appendTo(d.root),!(b&&b.isDefaultPrevented()||d.beforeOpen(b)===!1)){b&&b.preventDefault();var e=d.getContent();if(e)return c.push(d),h(!0),d.$instance.fadeIn(d.openSpeed),d.beforeContent(b),a.when(e).always(function(a){d.setContent(a),d.afterContent(b)}).then(d.$instance.promise()).done(function(){d.afterOpen(b)})}return d.$instance.detach(),a.Deferred().reject().promise()},close:function(b){var c=this,e=a.Deferred();return c.beforeClose(b)===!1?e.reject():(0===d(c).length&&h(!1),c.$instance.fadeOut(c.closeSpeed,function(){c.$instance.detach(),c.afterClose(b),e.resolve()})),e.promise()},chainCallbacks:function(b){for(var c in b)this[c]=a.proxy(b[c],this,a.proxy(this[c],this))}},a.extend(b,{id:0,autoBind:"[data-featherlight]",defaults:b.prototype,contentFilters:{jquery:{regex:/^[#.]\w/,test:function(b){return b instanceof a&&b},process:function(b){return a(b).clone(!0)}},image:{regex:/\.(png|jpg|jpeg|gif|tiff|bmp)(\?\S*)?$/i,process:function(b){var c=this,d=a.Deferred(),e=new Image,f=a('');return e.onload=function(){f.naturalWidth=e.width,f.naturalHeight=e.height,d.resolve(f)},e.onerror=function(){d.reject(f)},e.src=b,d.promise()}},html:{regex:/^\s*<[\w!][^<]*>/,process:function(b){return a(b)}},ajax:{regex:/./,process:function(b){var c=a.Deferred(),d=a("
    ").load(b,function(a,b){"error"!==b&&c.resolve(d.contents()),c.fail()});return c.promise()}},iframe:{process:function(b){var c=new a.Deferred,d=a("