[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.
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