Skip to main content

Higher-Level Programming

Home/projects/Higher-Level Programming

[High-Level Programming]

Higher-Level Programming

A full progression through Python, SQL, and JavaScript at ALX/Holberton — covering data structures, OOP, exceptions, I/O, inheritance, network requests, web scraping, and the 'Almost a Circle' project with its comprehensive unittest suite.

PythonJavaScriptMySQLjQueryunittestJSONcurlrequestsNode.js
View repository

A structured progression through Python from first principles to object-oriented design, followed by SQL, JavaScript, HTTP scripting, and DOM manipulation. The capstone module — "Almost a Circle" — synthesizes OOP, serialization, and unittest into a complete project with a geometry class hierarchy and two interchangeable persistence formats.


Python Basics

f-strings, Slicing, and the Compilation Pipeline

The first module covers output formatting and string manipulation before any control flow. Running Python inline, compiling to bytecode, and writing to stderr are all distinct operations:

# Run a Python file via environment variable
python3 $PYFILE

# Run Python code inline
python3 -c "$PYCODE"

# Compile to .pyc bytecode (keeps .pyc alongside .py)
python3 -m compileall -b $PYFILE
# 0x00-python-hello_world/3-print_number.py
number = 98
print(f"{number} Battery street")

# 0x00-python-hello_world/4-print_float.py
number = 3.14159
print(f"Float: {number:.2f}")   # :.2f rounds to 2 decimal places

String repetition and slicing in one step:

# 0x00-python-hello_world/5-print_string.py
str = "Holberton School"
print(3 * str + '\n' + str[:9])  # repeat × 3, then first 9 chars

Writing to stderr and exiting with a non-zero code:

# 0x00-python-hello_world/100-write.py
import sys
sys.stderr.write("and that piece of art is useful - Dora Korpar, 2015-10-19\n")
sys.exit(1)

if/elif/else, Loops, and ASCII Arithmetic

Characters are manipulated through their ordinal values rather than hardcoded character arrays:

# 0x01-python-if_else_loops_functions/2-print_alphabet.py
for char in range(ord("a"), ord("z") + 1):
    print("{}".format(chr(char)), end="")

Uppercase conversion without str.upper() — subtract 32 from the ordinal:

# 0x01-python-if_else_loops_functions/8-uppercase.py
def uppercase(str):
    result = ""
    for i in str:
        if ord("a") <= ord(i) <= ord("z"):
            result += chr(ord(i) - 32)  # lowercase → uppercase offset
        else:
            result += i
    print("{}".format(result))

Alternating case in a single loop — the i flag toggles between 0 and 32:

# 0x01-python-if_else_loops_functions/100-print_tebahpla.py
i = 0
for c in range(ord("z"), ord("a") - 1, -1):
    print("{}".format(chr(c - i)), end="")
    i = 32 if i == 0 else 0   # subtract 0 → uppercase, 32 → lowercase

Imports and __name__ == "__main__"

The __name__ guard is introduced early. It enforces that a module's "main" block only runs when executed directly — not when imported:

# 0x02-python-import_modules/0-add.py
if __name__ == "__main__":
    from add_0 import add
    a = 1
    b = 2
    print("{} + {} = {}".format(a, b, add(a, b)))

dir() with a name filter to list a module's public symbols:

# 0x02-python-import_modules/4-hidden_discovery.py
if __name__ == "__main__":
    import hidden_4
    for name in dir(hidden_4):
        if name[0] != "_":
            print(name)

A command-line calculator using sys.argv with operator dispatch:

# 0x02-python-import_modules/100-my_calculator.py
if __name__ == "__main__":
    from calculator_1 import add, sub, mul, div
    import sys

    if len(sys.argv) != 4:
        print("Usage: ./100-my_calculator.py <a> <operator> <b>")
        sys.exit(1)
    elif sys.argv[2] not in ["+", "-", "/", "*"]:
        print("Unknown operator. Available operators: +, -, * and /")
        sys.exit(1)
    # ... dispatch to the correct function

Data Structures

Lists

The matrix print function shows how to handle the last element differently without a special sentinel:

# 0x03-python-data_structures/6-print_matrix_integer.py
def print_matrix_integer(matrix=[[]]):
    for row in matrix:
        for j, val in enumerate(row):
            print("{:d}".format(val), end="" if j == len(row) - 1 else " ")
        print()

Immutable-style replacement — return a new list rather than mutating in place:

# 0x03-python-data_structures/4-new_in_list.py
def new_in_list(my_list, idx, element):
    if idx in range(0, len(my_list)):
        new_list = list(my_list)   # shallow copy — original untouched
        new_list[idx] = element
        return new_list
    return my_list

Sets and Dictionaries

Set intersection and symmetric difference in one line each:

# 0x04-python-more_data_structures/3-common_elements.py
def common_elements(set_1, set_2):
    return set_1 & set_2

# 0x04-python-more_data_structures/4-only_diff_elements.py
def only_diff_elements(set_1, set_2):
    return set_1 ^ set_2

Matrix squaring with a nested list comprehension:

# 0x04-python-more_data_structures/0-square_matrix_simple.py
def square_matrix_simple(matrix=[]):
    return [[y**2 for y in x] for x in matrix]

Sum only unique values using a manual deduplication list:

# 0x04-python-more_data_structures/2-uniq_add.py
def uniq_add(my_list=[]):
    unique_list = []
    total = 0
    for i in my_list:
        if i not in unique_list:
            unique_list.append(i)
            total += i
    return total

Exceptions

The exception module covers three distinct patterns: bare except for catch-all (format validation), specific exception types for semantics-aware handling, and finally for guaranteed cleanup:

# 0x05-python-exceptions/0-safe_print_list.py
def safe_print_list(my_list=[], x=0):
    printed = 0
    for i in range(0, x):
        try:
            print("{}".format(my_list[i]), end="")
            printed += 1
        except:          # catches IndexError when x > len(my_list)
            continue
    print()
    return printed

Skip non-integers without aborting the loop — specific exceptions only:

# 0x05-python-exceptions/2-safe_print_list_integers.py
def safe_print_list_integers(my_list=[], x=0):
    printed = 0
    for i in range(0, x):
        try:
            print("{:d}".format(my_list[i]), end="")
            printed += 1
        except (ValueError, TypeError):   # skip strings, floats — re-raise nothing
            continue
    print()
    return printed

finally runs regardless of whether the try succeeded:

# 0x05-python-exceptions/3-safe_print_division.py
def safe_print_division(a, b):
    try:
        res = a / b
    except:
        res = None
    finally:
        print("Inside result: {}".format(res))  # always prints
    return res

Python Objects and Identity

The "Everything is Object" module is a set of short answer questions about id(), is, ==, and how Python interns small integers and strings. Two interesting practical outputs from this module:

Mutable default argument trap — the default list persists across calls:

# 0x09-python-everything_is_object/100-magic_string.py
def magic_string(Holby=[]):
    Holby += ["Holberton"]   # mutates the default list — grows on every call
    return (", ".join(Holby))

__slots__ to restrict attribute creation:

# 0x09-python-everything_is_object/101-locked_class.py
class LockedClass:
    __slots__ = ("first_name",)
    # any attempt to set an attribute not in __slots__ raises AttributeError

Shallow copy via slice:

# 0x09-python-everything_is_object/19-copy_list.py
def copy_list(l):
    return l[:]   # creates a new list — outer container copied, elements shared

Inheritance

The inheritance module builds a geometry hierarchy from scratch, using isinstance, type, and issubclass explicitly before any classes are created:

# 0x0A-python-inheritance/2-is_same_class.py
def is_same_class(obj, a_class):
    return type(obj) == a_class    # exact type — subclasses return False

# 0x0A-python-inheritance/3-is_kind_of_class.py
def is_kind_of_class(obj, a_class):
    return isinstance(obj, a_class)   # True for subclasses too

# 0x0A-python-inheritance/4-inherits_from.py
def inherits_from(obj, a_class):
    return isinstance(obj, a_class) and type(obj) != a_class  # subclass only

BaseGeometry raises Exception on area() — Python's version of an abstract method without abc:

# 0x0A-python-inheritance/7-base_geometry.py
class BaseGeometry:
    def area(self):
        raise Exception("area() is not implemented")

    def integer_validator(self, name, value):
        if type(value) != int:
            raise TypeError("{} must be an integer".format(name))
        if value <= 0:
            raise ValueError("{} must be greater than 0".format(name))

Rectangle calls the validator before storing private attributes, then delegates id management to super().__init__:

# 0x0A-python-inheritance/9-rectangle.py
class Rectangle(BaseGeometry):
    def __init__(self, width, height):
        self.integer_validator("width", width)
        self.__width = width
        self.integer_validator("height", height)
        self.__height = height

    def area(self):
        return self.__width * self.__height

    def __str__(self):
        return "[Rectangle] {}/{}".format(self.__width, self.__height)

Square reuses Rectangle entirely — passing size for both dimensions:

# 0x0A-python-inheritance/10-square.py
class Square(Rectangle):
    def __init__(self, size):
        self.integer_validator("size", size)
        self.__size = size
        super().__init__(size, size)   # Rectangle handles all validation from here

    def area(self):
        return self.__size ** 2

MyInt — inverting == and != to demonstrate operator overriding:

# 0x0A-python-inheritance/100-my_int.py
class MyInt(int):
    def __eq__(self, other):
        return int(self) != int(other)  # equality is inequality

    def __ne__(self, other):
        return int(self) == int(other)  # inequality is equality

File I/O and JSON Serialization

The I/O module builds from raw file reads up through full JSON round-trips:

# 0x0B-python-input_output/0-read_file.py
def read_file(filename=""):
    with open(filename, encoding="utf-8") as f:
        print(f.read(), end="")

# 0x0B-python-input_output/2-append_write.py
def append_write(filename="", text=""):
    with open(filename, "a", encoding="utf-8") as f:
        return f.write(text)

JSON serialization and deserialization wrappers:

# 0x0B-python-input_output/3-to_json_string.py
def to_json_string(my_obj):
    return json.dumps(my_obj)

# 0x0B-python-input_output/4-from_json_string.py
def from_json_string(my_str):
    return json.loads(my_str)

The add_item.py script loads an existing JSON file if it exists, appends CLI arguments to the list, and saves back — a real persistent CLI:

# 0x0B-python-input_output/7-add_item.py
item = []
if os.path.exists("./add_item.json"):
    item = load_from_json_file("add_item.json")

save_to_json_file(item + args, "add_item.json")

Student.to_json with an optional attribute filter:

# 0x0B-python-input_output/10-student.py
def to_json(self, attrs=None):
    if isinstance(attrs, list) and all(isinstance(a, str) for a in attrs):
        return {k: self.__dict__[k] for k in attrs if k in self.__dict__}
    return self.__dict__

Pascal's triangle — each row is built from the previous row's adjacent pairs:

# 0x0B-python-input_output/12-pascal_triangle.py
def pascal_triangle(n):
    triangle = []
    if n == 0:
        return triangle

    triangle.append([1])
    for i in range(1, n):
        before = triangle[-1]
        after  = [1]
        for i in range(len(before) - 1):
            after.append(before[i] + before[i + 1])   # sum adjacent pairs
        after += [1]
        triangle.append(after)

    return triangle

Log metrics reader — reads stdin line by line, prints running totals every 10 lines and on KeyboardInterrupt:

# 0x0B-python-input_output/101-stats.py
try:
    for line in stdin:
        if c == 10:
            print("File size: {}".format(size))
            for i in sorted(st):
                print("{}: {}".format(i, st[i]))
            c = 1
        else:
            c += 1
        # ... parse size and status code from line
except KeyboardInterrupt:
    print("File size: {}".format(size))
    for i in sorted(st):
        print("{}: {}".format(i, st[i]))
    raise

Almost a Circle — OOP Capstone

The "Almost a Circle" project puts everything together: class hierarchy, property validation, serialization to both JSON and CSV, a create factory method, and over 300 unittests.

Base Class — Auto-Incrementing IDs

Base manages a class-level counter for auto-assigned IDs. Explicitly passing an id bypasses the counter:

# 0x0C-python-almost_a_circle/models/base.py

class Base:
    __nb_objects = 0

    def __init__(self, id=None):
        if id is not None:
            self.id = id
        else:
            Base.__nb_objects += 1
            self.id = Base.__nb_objects

Rectangle — Validated Properties

Every attribute goes through a property setter that validates type and range. The setters raise specific TypeError and ValueError with exact messages that the test suite asserts on:

# 0x0C-python-almost_a_circle/models/rectangle.py

@width.setter
def width(self, value):
    if type(value) != int:
        raise TypeError("width must be an integer")
    if value <= 0:
        raise ValueError("width must be > 0")
    self.__width = value

@x.setter
def x(self, value):
    if type(value) != int:
        raise TypeError("x must be an integer")
    if value < 0:              # x can be 0, width cannot
        raise ValueError("x must be >= 0")
    self.__x = value

display() renders the rectangle using # characters, offset by x and y:

def display(self):
    if self.width == 0 or self.height == 0:
        print("")
        return
    [print("") for y in range(self.y)]         # leading blank lines for y offset
    for h in range(self.height):
        [print(" ", end="") for x in range(self.x)]   # leading spaces for x offset
        [print("#", end="") for w in range(self.width)]
        print("")

update() accepts both positional *args (positional priority) and keyword **kwargs. If args are provided, kwargs are ignored:

def update(self, *args, **kwargs):
    if args and len(args) != 0:
        a = 0
        for arg in args:
            if a == 0:
                self.id = arg if arg is not None else self.__init__(self.width, self.height, self.x, self.y)
            elif a == 1: self.width  = arg
            elif a == 2: self.height = arg
            elif a == 3: self.x      = arg
            elif a == 4: self.y      = arg
            a += 1
    elif kwargs and len(kwargs) != 0:
        for k, v in kwargs.items():
            if k == "id":       self.id     = v
            elif k == "width":  self.width  = v
            elif k == "height": self.height = v
            elif k == "x":      self.x      = v
            elif k == "y":      self.y      = v

Square — Reusing Rectangle via Inheritance

Square passes size for both width and height to Rectangle.__init__. The size property is a thin alias for width, so all of Rectangle's validation is reused:

# 0x0C-python-almost_a_circle/models/square.py

class Square(Rectangle):
    def __init__(self, size, x=0, y=0, id=None):
        super().__init__(size, size, x, y, id)

    @property
    def size(self):
        return self.width

    @size.setter
    def size(self, value):
        self.width  = value   # triggers Rectangle's width validator
        self.height = value   # triggers Rectangle's height validator

JSON and CSV Serialization

Base provides class methods for both JSON and CSV. save_to_file uses the class name as the filename — Rectangle.save_to_file(...) writes to Rectangle.json:

# 0x0C-python-almost_a_circle/models/base.py

@staticmethod
def to_json_string(list_dictionaries):
    if list_dictionaries is None or list_dictionaries == []:
        return "[]"
    return json.dumps(list_dictionaries)

@classmethod
def save_to_file(cls, list_objs):
    filename = cls.__name__ + ".json"
    with open(filename, "w") as jsonfile:
        if list_objs is None:
            jsonfile.write("[]")
        else:
            list_dicts = [o.to_dictionary() for o in list_objs]
            jsonfile.write(Base.to_json_string(list_dicts))

@classmethod
def load_from_file(cls):
    filename = str(cls.__name__) + ".json"
    try:
        with open(filename, "r") as jsonfile:
            list_dicts = Base.from_json_string(jsonfile.read())
            return [cls.create(**d) for d in list_dicts]
    except IOError:
        return []

CSV uses DictWriter/DictReader with fieldnames specific to each shape:

@classmethod
def save_to_file_csv(cls, list_objs):
    filename = cls.__name__ + ".csv"
    with open(filename, "w", newline="") as csvfile:
        if not list_objs:
            csvfile.write("[]")
        else:
            fieldnames = ["id", "width", "height", "x", "y"] \
                         if cls.__name__ == "Rectangle" \
                         else ["id", "size", "x", "y"]
            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            for obj in list_objs:
                writer.writerow(obj.to_dictionary())

The create Factory

create reconstructs an object from a dictionary — it creates a "dummy" instance first, then applies update(**dictionary) to set all attributes. This avoids duplicating __init__ logic in a separate factory:

@classmethod
def create(cls, **dictionary):
    if dictionary and dictionary != {}:
        new = cls(1, 1) if cls.__name__ == "Rectangle" else cls(1)
        new.update(**dictionary)
        return new

SQL

Introduction

The SQL module covers DDL and DML from scratch. Key patterns:

-- Safe database and table creation
CREATE DATABASE IF NOT EXISTS hbtn_0c_0;
CREATE TABLE IF NOT EXISTS first_table (id INT, name VARCHAR(256));

-- Create table with constraints
CREATE TABLE IF NOT EXISTS cities (
    id       INT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,
    state_id INT NOT NULL,
    name     VARCHAR(256) NOT NULL,
    FOREIGN KEY (state_id) REFERENCES states(id)
);

-- GROUP BY + ORDER BY with aggregate
SELECT score, COUNT(*) AS number
FROM second_table
GROUP BY score
ORDER BY number DESC;

Advanced Queries

The more queries module covers JOINs, subqueries, and user/privilege management:

-- INNER JOIN — only shows and their genres, excludes unlinked shows
SELECT tv_shows.title, tv_show_genres.genre_id
FROM tv_shows
INNER JOIN tv_show_genres ON tv_shows.id = tv_show_genres.show_id
ORDER BY tv_shows.title, tv_show_genres.genre_id;

-- LEFT JOIN — all shows, NULL genre_id for shows with no genre
SELECT tv_shows.title, tv_show_genres.genre_id
FROM tv_shows
LEFT OUTER JOIN tv_show_genres ON tv_shows.id = tv_show_genres.show_id
ORDER BY tv_shows.title;

-- Subquery: shows NOT in the Comedy genre
SELECT tv_shows.title
FROM tv_shows
WHERE tv_shows.title NOT IN (
    SELECT tv_shows.title
    FROM tv_shows
    INNER JOIN tv_show_genres ON tv_shows.id = tv_show_genres.show_id
    INNER JOIN tv_genres ON tv_show_genres.genre_id = tv_genres.id
    WHERE tv_genres.name = 'Comedy'
)
ORDER BY tv_shows.title;

-- Genres ranked by total rating
SELECT tv_genres.name, SUM(tv_show_ratings.rate) AS rating
FROM tv_genres
JOIN tv_show_genres ON tv_genres.id = tv_show_genres.genre_id
JOIN tv_shows       ON tv_shows.id = tv_show_genres.show_id
JOIN tv_show_ratings ON tv_shows.id = tv_show_ratings.show_id
GROUP BY tv_genres.name
ORDER BY rating DESC;

User and privilege management:

-- Create user, set password, grant all privileges
CREATE USER IF NOT EXISTS 'user_0d_1'@'localhost';
ALTER USER 'user_0d_1'@'localhost' IDENTIFIED BY 'user_0d_1_pwd';
GRANT ALL PRIVILEGES ON *.* TO 'user_0d_1'@localhost;
FLUSH PRIVILEGES;

-- Read-only user on a single database
CREATE USER IF NOT EXISTS 'user_0d_2'@localhost IDENTIFIED BY 'user_0d_2_pwd';
GRANT SELECT ON `hbtn_0d_2`.* TO 'user_0d_2'@'localhost';

HTTP Scripting — curl and Python

curl

The network_0 module uses curl flags to cover the full HTTP method set:

# GET response body size from Content-Length header
curl -sI "$1" | grep -i Content-Length | cut -d " " -f 2

# Follow redirects (-L), print response body
curl -Ls "$1"

# Send DELETE request
curl -s "$1" -X DELETE

# List allowed methods via OPTIONS, extract the Allow header
curl -s -I -X OPTIONS "$1" | grep "Allow:" | cut -f2- -d" "

# Send a custom header
curl -s "$1" -H "X-School-User-Id: 98"

# POST with form-encoded body
curl -s "$1" -X POST -d "email=test@gmail.com&subject=I will always be here for PLD"

# POST JSON from a file
curl -s "$1" -d "@$2" -X POST -H "Content-Type: application/json"

# Print only the HTTP status code
curl -s -L -X HEAD -w "%{http_code}" "$1"

urllib vs requests

The same operations are shown in both urllib (stdlib) and requests (third-party):

# urllib version — lower-level
with urllib.request.urlopen(sys.argv[1]) as response:
    html = response.info()
    print(html.get('X-Request-Id'))

# requests version — cleaner API
response = requests.get(sys.argv[1])
print(response.headers.get("X-Request-Id"))

POST with urllib requires manual encoding; requests handles it directly:

# urllib POST
param = {"email": email}
data  = urllib.parse.urlencode(param).encode("ascii")
req   = urllib.request.Request(url, data)
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

# requests POST
response = requests.post(url, data={"email": email})
print(response.text)

Error handling also diverges — urllib raises on HTTP errors, requests returns them in the response:

# urllib: exception on 4xx/5xx
try:
    with urllib.request.urlopen(url) as response:
        print(response.read().decode("utf-8"))
except urllib.error.HTTPError as e:
    print("Error code: {}".format(e.code))

# requests: check status code on the response object
response = requests.get(url)
if response.status_code >= 400:
    print("Error code: {}".format(response.status_code))
else:
    print(response.text)

GitHub API with HTTP Basic Auth:

# 0x11-python-network_1/10-my_github.py
response = requests.get(
    "https://api.github.com/user",
    auth=HTTPBasicAuth(sys.argv[1], sys.argv[2])
)
print(response.json().get("id"))

JavaScript — Warm-Up and Node.js

Basics via process.argv

// Factorial — handles NaN (no argument) by returning 1
function factorial(n) {
  return n === 0 || isNaN(n) ? 1 : n * factorial(n - 1);
}
console.log(factorial(Number(process.argv[2])));

Second-largest in a list — sort descending, take index length - 2:

// 0x12-javascript-warm_up/11-second_biggest.js
if (process.argv.length <= 3) {
  console.log(0);
} else {
  const args = process.argv
    .map(Number)
    .slice(2)
    .sort((a, b) => a - b);
  console.log(args[args.length - 2]);
}

A base converter using closure — returns a function that converts any number to the given base:

// 0x13-javascript_objects_scopes_closures/10-converter.js
exports.converter = function (base) {
  return (num) => num.toString(base);
};

Object-Oriented JavaScript

The Rectangle class evolves across five files — a good illustration of how progressive feature addition works:

// v4: width, height, print, rotate, double
module.exports = class Rectangle {
  constructor(w, h) {
    if (w > 0 && h > 0) {
      [this.width, this.height] = [w, h];
    }
  }

  print() {
    for (let i = 0; i < this.height; i++) console.log('X'.repeat(this.width));
  }

  rotate() {
    [this.width, this.height] = [this.height, this.width]; // destructuring swap
  }

  double() {
    [this.width, this.height] = [this.width * 2, this.height * 2];
  }
};

// Square extends Rectangle — single constructor arg
module.exports = class Square extends require('./4-rectangle.js') {
  constructor(size) {
    super(size, size);
  }
};

// Square with charPrint — falls back to print() if no char given
module.exports = class Square extends require('./5-square.js') {
  charPrint(c) {
    if (c === undefined) {
      this.print();
    } else {
      for (let i = 0; i < this.height; i++) console.log(c.repeat(this.width));
    }
  }
};

Reverse a list using reduceRight without mutation:

// 0x13-javascript_objects_scopes_closures/8-esrever.js
exports.esrever = function (list) {
  return list.reduceRight(function (array, current) {
    array.push(current);
    return array;
  }, []);
};

Web Scraping with Node.js

The web scraping module uses the request package for HTTP and fs for file I/O.

Pipe a URL response directly to a file — no buffering:

// 0x14-javascript-web_scraping/5-request_store.js
const fs = require('fs');
const request = require('request');
request(process.argv[2]).pipe(fs.createWriteStream(process.argv[3]));

Count appearances of a specific character across all SWAPI films:

// 0x14-javascript-web_scraping/4-starwars_count.js
request(reqURL, function (error, response, body) {
  const results = JSON.parse(body).results;
  let count = 0;
  for (let i = 0; i < results.length; i++) {
    const chars = results[i].characters;
    for (let j = 0; j < chars.length; j++) {
      if (chars[j].endsWith('18/')) count++;
    }
  }
  console.log(count);
});

Star Wars characters in order — recursive calls maintain sequence since the request module is async by default:

// 0x14-javascript-web_scraping/101-starwars_characters.js
function printCharacters(characters, idx) {
  request(characters[idx], (err, res, body) => {
    if (err) {
      console.log(err);
    } else {
      console.log(JSON.parse(body).name);
      if (idx + 1 < characters.length) {
        printCharacters(characters, idx + 1); // recurse to preserve order
      }
    }
  });
}

jQuery DOM Manipulation

The jQuery module progresses from direct DOM manipulation to AJAX:

// Vanilla JS
document.querySelector('HEADER').style.color = '#FF0000';

// jQuery equivalent
$('HEADER').css('color', '#FF0000');

// Class toggling
$('DIV#toggle_header').click(function () {
  $('HEADER').toggleClass('green red');
});

// Append list items dynamically
$('DIV#add_item').click(function () {
  $('UL.my_list').append('<li>Item</li>');
});

Fetch and display SWAPI data:

// GET character name
$.get('https://swapi.co/api/people/5/?format=json', function (data) {
  $('DIV#character').text(data.name);
});

// GET and append all film titles
$.get('https://swapi.co/api/films/?format=json', function (data) {
  $('UL#list_movies').append(...data.results.map((movie) => `<li>${movie.title}</li>`));
});

Translation lookup with Enter key support — attaches a keydown listener inside the focus handler:

// 0x15-javascript-web_jquery/103-script.js
$('INPUT#btn_translate').click(translate);
$('INPUT#language_code').focus(function () {
  $(this).keydown(function (e) {
    if (e.keyCode === 13) translate(); // Enter key triggers same action as button
  });
});

function translate() {
  const url = 'https://www.fourtonfish.com/hellosalut/?';
  $.get(url + $.param({ lang: $('INPUT#language_code').val() }), function (data) {
    $('DIV#hello').html(data.hello);
  });
}

Almost a Circle — unittest Suite

The test suite covers the full API surface with assertRaisesRegex to verify both the exception type and the exact message:

# tests/test_models/test_rectangle.py

def test_str_width(self):
    with self.assertRaisesRegex(TypeError, "width must be an integer"):
        Rectangle("invalid", 2)

def test_negative_width(self):
    with self.assertRaisesRegex(ValueError, "width must be > 0"):
        Rectangle(-1, 2)

def test_width_before_height(self):
    # Validation order: width setter is called before height setter
    with self.assertRaisesRegex(TypeError, "width must be an integer"):
        Rectangle("invalid width", "invalid height")

display() output is captured by redirecting sys.stdout to a StringIO buffer:

@staticmethod
def capture_stdout(rect, method):
    capture = io.StringIO()
    sys.stdout = capture
    if method == "print":
        print(rect)
    else:
        rect.display()
    sys.stdout = sys.__stdout__
    return capture

def test_display_width_height_x_y(self):
    r = Rectangle(2, 4, 3, 2, 0)
    capture = TestRectangle_stdout.capture_stdout(r, "display")
    display = "\n\n   ##\n   ##\n   ##\n   ##\n"
    self.assertEqual(display, capture.getvalue())

JSON/CSV round-trip tests use tearDown to delete test files after each test class — no leftover files between runs:

@classmethod
def tearDown(self):
    try:
        os.remove("Rectangle.json")
    except IOError:
        pass
    try:
        os.remove("Square.json")
    except IOError:
        pass

def test_save_to_file_two_rectangles(self):
    r1 = Rectangle(10, 7, 2, 8, 5)
    r2 = Rectangle(2, 4, 1, 2, 3)
    Rectangle.save_to_file([r1, r2])
    with open("Rectangle.json", "r") as f:
        self.assertTrue(len(f.read()) == 105)

The create factory is tested to confirm it returns a different object (not the same reference) with the same string representation:

def test_create_rectangle_is(self):
    r1 = Rectangle(3, 5, 1, 2, 7)
    r2 = Rectangle.create(**r1.to_dictionary())
    self.assertIsNot(r1, r2)    # different objects

def test_create_rectangle_equals(self):
    r1 = Rectangle(3, 5, 1, 2, 7)
    r2 = Rectangle.create(**r1.to_dictionary())
    self.assertNotEqual(r1, r2)  # == is identity by default (no __eq__)
    # but str(r1) == str(r2) — same data, different object