Skip to content

Commit

Permalink
feat(ebook): start ebook structure for this course
Browse files Browse the repository at this point in the history
  • Loading branch information
jslvtr committed Oct 21, 2022
1 parent 07c4f72 commit 423f706
Show file tree
Hide file tree
Showing 33 changed files with 345 additions and 148 deletions.
21 changes: 21 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,24 @@ videos/
**/old
.venv
.python-version

# Dependencies
/node_modules

# Production
/build

# Generated files
.docusaurus
.cache-loader

# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
38 changes: 38 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Contributing

## Building the docs

Write your documentation inside `course_contents`.

To build and serve the documentation, use this command:

```bash
watchmedo shell-command --patterns="*.md" --recursive course_contents --command="cog -r mkdocs.yml"
```

This runs the `cog -r mkdocs.yml` command every time there is a markdown file change.

**note**: at the moment there is [a bug](https://github.com/gorakhargosh/watchdog/issues/346) in `watchmedo` that triggers the shell command twice on file modification, or potentially a few times on file creation. It doesn't affect us majorly.

### Why do we need to use `cog` to build the docs before serving?

The documentation system we use for this e-book, `mkdocs`, has limitation regarding slugs: all slugs are calculated from the filesystem paths.

This means that if we simply serve the docs from `course_contents`, all our slugs would be numbered (e.g. `1_intro/lectures/3_variables_printing/`).

To avoid this, we first run a build step (in `content.py`, `build_and_get_yaml_contents()`) that copies all the non-hidden sections and lectures to a `build` folder, and then we can serve the documentation from there.

This is a bit "hacky", and we must remember to run `cog` on the `mkdocs.yml` file if we want to see our updated documentation!

## Writing documentation README files

There are a few attributes for each `README.md` file that we can use.

In section files:

- `group: str` will try to place sections with the same group name inside a tabbed navigation.
- `hidden: true | false` will hide the section and its lectures, and not include any of them in the build.

In lecture files:

- `hidden: true | false` will hide the lecture and not include it in the build. Other lectures in the same section are unaffected.
117 changes: 117 additions & 0 deletions content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import glob
import json
import pathlib
import shutil
import string
import markdown
from bs4 import BeautifulSoup

def get_grouped_sections(root: str = "course_contents") -> dict[str, list]:
sections = get_all_sections_with_content(root)
grouped_sections = {}
for section in sections:
group = section["index"]["group"]
if not group:
raise RuntimeError("Not all sections part of a group.")
if group:
grouped_sections.setdefault(group, []).append(section)
else:
grouped_sections.setdefault("default", []).append(section)
return grouped_sections

def get_all_sections_with_content(root: str = "course_contents"):
root_path = pathlib.Path(root)
section_folders = [folder.name for folder in root_path.iterdir() if folder.is_dir() and folder.name[0] in string.digits]
sections = sorted(section_folders, key=lambda e: int(str(e).split("_")[0]))
for section in sections:
section = root_path / section
section_index = get_lecture_content(section / "README.md")
if not section_index.get("title") or section_index.get("hidden"):
continue
lectures = get_section_lectures(section)
lecture_contents = [get_lecture_content(lecture) for lecture in lectures]
non_hidden_lectures = [lecture for lecture in lecture_contents if not lecture.get("hidden")]
yield {"index": section_index, "lectures": non_hidden_lectures}


def get_section_lectures(folder: pathlib.Path | str) -> list[str]:
"""Return a list of pathlib.Path objects for all lectsures in a section folder"""
lecture_path = pathlib.Path(folder) / "lectures"
return sorted([folder for folder in lecture_path.glob("*/README.md")])


def get_lecture_content(lecture_path: pathlib.Path, root_path: str | pathlib.Path = "course_contents") -> dict[str, str]:
"""Return a dictionary of lecture content.
Return a dictionary with the following keys:
- title, the h1 from the markdown file
- summary from the markdown metadata
- group, to group sections together, optional
- hidden, to hide entire sections, optional
- filename, the name of the markdown file
- full_path, the full path to the markdown file
- order, the order of the lecture in the section (defaults to file name)
- path, the path to this file relative to the root"""
with open(lecture_path) as f:
content = f.read()
md = markdown.Markdown(extensions=["meta"])
html = md.convert(content)
soup = BeautifulSoup(html, "html.parser")
return {
"title": soup.find("h1").text,
"summary": md.Meta.get("summary", ""),
"group": md.Meta.get("group", [""])[0],
"hidden": md.Meta.get("hidden", [""])[0] == "true",
"filename": lecture_path.name,
"full_path": lecture_path.absolute(),
"order": md.Meta.get("order", [lecture_path.name])[0],
"path": lecture_path.relative_to(root_path),
}


def get_grouped_build_sections(root: str = "build") -> dict[str, list]:
sections = build_and_get_yaml_contents(root)
grouped_sections = {}
for section in sections:
group = section["index"]["group"]
if not group:
raise RuntimeError("Not all sections part of a group.")
if group:
grouped_sections.setdefault(group, []).append(section)
else:
grouped_sections.setdefault("default", []).append(section)
return grouped_sections


def build_and_get_yaml_contents(build_path: str = "build"):
# Delete contents of the build directory
shutil.rmtree(build_path, ignore_errors=True)
sections = get_all_sections_with_content()
for section in sections:
# Strip the leading numbers of the section folder
old_section_name = section["index"]["full_path"].parent.name
section_name = "_".join(old_section_name.split("_")[1:])
# Create a directory in the build folder matching the section_name
pathlib.Path(build_path, section_name).mkdir(parents=True, exist_ok=True)
# Copy the README.md file from the original section to the new directory
shutil.copyfile(section["index"]["full_path"], pathlib.Path(build_path, section_name, "README.md"))
# Copy the lecture folders to the new directory
section["lectures"] = list(copy_lectures_to_build_path(section, section_name, build_path=build_path))
# Update the section index to point to the new path
section["index"]["full_path"] = pathlib.Path(build_path, section_name, "README.md").absolute()
section["index"]["path"] = pathlib.Path(build_path, section_name, "README.md").relative_to(build_path)
yield section


def copy_lectures_to_build_path(section: dict, new_section_name: str, build_path: str = "build"):
for lecture in section["lectures"]:
lecture_name = "_".join(lecture["full_path"].parent.name.split("_")[1:])
pathlib.Path(build_path, new_section_name, lecture_name).mkdir(parents=True, exist_ok=True)
shutil.copyfile(lecture["full_path"], pathlib.Path(build_path, new_section_name, lecture_name, "README.md"))
lecture["full_path"] = pathlib.Path(build_path, new_section_name, lecture_name, "README.md").absolute()
lecture["path"] = pathlib.Path(build_path, new_section_name, lecture_name, "README.md").relative_to(build_path)
yield lecture


if __name__ == "__main__":
section_yaml = get_grouped_build_sections()
print(list(section_yaml))
4 changes: 4 additions & 0 deletions course_contents/10_advanced_python/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
group: Intermediate
hidden: true
---
# Advanced Python Development

In this section of the course we look at some advanced Python features, such as:
Expand Down
4 changes: 4 additions & 0 deletions course_contents/11_web_scraping/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
group: Practical Python
hidden: true
---
# Web Scraping

In this section we look at web scraping using Python and the `requests` library.
Expand Down
4 changes: 4 additions & 0 deletions course_contents/12_browser_automation_selenium/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
group: Practical Python
hidden: true
---
# Browser automation with Selenium

The code is very similar to last section, but now we're launching a browser instead of requesting the page with Python. We will be controlling the browser, instead of just getting HTML.
Expand Down
4 changes: 4 additions & 0 deletions course_contents/13_async_development/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
group: Practical Python
hidden: false
---
# Async Development with Python

Python is a single-threaded language, which means that asynchronous development can sometimes be tricky.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
hidden: false
---

# Code samples for this section

Hey, welcome back!

This section won't have code available on our online editor, but instead all the code we'll write here is available on the GitHub page.

Again, don't read through the code before going through some of the videos. The code is here for you to check as you progress through the section. Otherwise, it could get a bit confusing!

Here's a link to the code in this section: https://github.com/tecladocode/complete-python-course/tree/master/course_contents/13_async_development/sample_code

As always, if you have any questions please ask away in the Course Q&A.

Happy coding!

Jose—your instructor
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
hidden: true
---

# Glossary of terms used in concurrency

Hey there! Welcome to one of the most advanced topics in Python. A lot of developers are scared of what we're going to learn in this section.

You shouldn't be scared!

We'll start at the fundamentals, and build out our knowledge of asynchronous development from the ground up.

Throughout the section we'll learn more about what these terms mean, but it can be helpful to have a short glossary of terms just in case you want to come back and remind yourself.

- **Synchronous**: actions that happen one after another. Programming as we've seen it until now is synchronous, because each line executes after the previous one.
- **Asynchronous**: actions that don't necessary happen after one another, or that can happen in arbitrary order ("without synchrony").
- **Concurrency**: The ability of our programs to run things in different order every time the program runs, without affecting the final outcome.
- **Parallelism**: Running two or more things at the same time.
- **Thread**: A line of code execution that can run in one of your computer's cores.
- **Process**: One of more threads and the resources they need (e.g. network connection, mouse pointer, hard drive access, or even the core(s) in which the thread(s) run).
- **GIL**: A key, critical, important resource in any Python program. Only one is created per Python process, so it's unique in each.

Take it slowly through this section, and ask questions in the Course Q&A as required. We're here to help!

Kind regards,

Jose
4 changes: 4 additions & 0 deletions course_contents/14_managing_projects_pipenv/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
group: Practical Python
hidden: true
---
# Managing projects with Pipenv

In this section we briefly look at managing your project dependencies using Pipenv.
Expand Down
4 changes: 4 additions & 0 deletions course_contents/15_flask/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
group: Practical Python
hidden: true
---
# Web Development with Flask

In this section we learn about making websites using the Flask framework, HTML, and Jinja.
Expand Down
4 changes: 4 additions & 0 deletions course_contents/16_interacting_with_apis/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
group: Practical Python
hidden: true
---
# Interacting with APIs

## Requirements
Expand Down
4 changes: 4 additions & 0 deletions course_contents/17_decorators/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
group: Advanced
hidden: true
---
# Decorators in Python

Decorators allow us to extend another function without modifying it directly.
Expand Down
4 changes: 4 additions & 0 deletions course_contents/18_advanced_oop/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
group: Advanced
hidden: true
---
# Advanced Object-Oriented Programming with Python

In this section we look at some more advanced OOP concepts, such as multiple inheritance, abstract classes, interfaces, and properties.
Expand Down
5 changes: 5 additions & 0 deletions course_contents/19_gui_development_tkinter/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
group: Advanced
hidden: true
---

# GUI Development with Tkinter

In this section we learn about making desktop applications using Python's `tkinter` library.
Expand Down
5 changes: 5 additions & 0 deletions course_contents/1_intro/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
group: Introduction
hidden: true
---

# Course Introduction

In this section we assume you are at the very starting point of coding.
Expand Down
5 changes: 5 additions & 0 deletions course_contents/20_unit_testing/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
group: Advanced
hidden: true
---

# Unit testing with Python

Unit testing means writing some code that checks other code you've written, to make sure it works as intended.
Expand Down
5 changes: 5 additions & 0 deletions course_contents/21_algorithms_data_structures/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
group: Advanced
hidden: true
---

# Algorithms and Data Structures with Python

In this section we learn about algorithms and data structures, two topics that are essential when finding a software development job.
Expand Down
5 changes: 5 additions & 0 deletions course_contents/22_popular_libraries/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
group: Advanced
hidden: true
---

# Python Libraries and Tools

In this section I'll show you some popular Python libraries and tools that you can use to make your project development easier, or to extend your projects.
Expand Down
4 changes: 4 additions & 0 deletions course_contents/2_intro_to_python/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
group: Introduction
hidden: true
---
# Python Fundamentals

This section goes deeper into Python, covering many essential features.
Expand Down
Loading

0 comments on commit 423f706

Please sign in to comment.