PCPP2 study materials
PCPP2 study resources

PCPP2 cheat sheet

PCPP2 · Keep the key ideas within reach.

Quick reference

Testing principles and techniques (unittest and pytest)

  • unittest: tests subclass unittest.TestCase; assertions are methods (assertEqual, assertTrue, assertRaises); test discovery matches files named test*.py and methods prefixed test_.

  • pytest: plain assert statements; assertion rewriting reports the values that failed; test functions or classes named Test* are collected; fixtures inject dependencies via function parameters.

  • Fixture scoping in pytest: scope="function" (default, fresh per test), "class", "module", "package", "session"; use yield in a fixture for setup/teardown around the yield point.

  • parametrize: @pytest.mark.parametrize("a,b,expected", [(1,1,2),(2,3,5)]) generates one test per tuple; stack decorators to get the Cartesian product of parameter sets.

  • Mocking: unittest.mock.patch replaces an attribute for the duration of the with-block or decorated function; Mock records call_args, call_count, and return_value; autospec=True checks call signatures.

  • Exception testing: self.assertRaises(ValueError) as context in unittest; pytest.raises(ValueError, match="pattern") with a regex match on str(excinfo.value).

  • unittest lifecycle order: setUpClass (once per class) -> setUp -> test method -> tearDown -> tearDownClass; pytest equivalents are module/session fixtures plus function fixtures.

  • Test doubles vocabulary: stub returns canned data, mock verifies interactions, fake is a working lightweight implementation, spy records calls while delegating to the real object.

Quick reference

Design patterns (OOP principles and GoF patterns)

  • SOLID: Single responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion (depend on abstractions, not concretions).

  • Singleton: one instance per process; in Python implement with a class attribute plus new, or a module-level instance, since modules are cached in sys.modules.

  • Factory Method defers instantiation to subclasses; Abstract Factory groups related factories; a plain factory function returning one of several classes is the common Python form.

  • Facade: a single simplified interface over a subsystem of several classes; it does not add behavior, only reduces coupling for callers.

  • Proxy: same interface as the real subject, controls access (lazy load, permission check, remote call, caching); differs from Decorator, which adds behavior.

  • Observer: subject keeps a list of observers and calls update() on notify; Python alternative is callback registration or weakref.WeakSet to avoid leaks.

  • Command: encapsulate a request as an object with execute() (and often undo()); enables queues, logging, and macro recording of operations.

  • Template Method: base class defines the algorithm skeleton in a concrete method and calls abstract hook methods that subclasses override; State pattern delegates behavior to a state object that can replace itself.

Quick reference

Interprocess communication (multiprocessing, threading, subprocess, synchronization)

  • GIL: threads suit I/O-bound work and share memory; multiprocessing bypasses the GIL for CPU-bound work but each process has separate memory, so data must be pickled across the boundary.

  • multiprocessing primitives: Process(target=, args=), Pool.map/imap for fan-out, Queue and Pipe for messaging; objects passed must be picklable.

  • Synchronization: Lock (mutual exclusion), RLock (reentrant, same thread may re-acquire), Semaphore (counted permits), Event (flag with wait/set), Condition (wait/notify), Barrier (N parties meet).

  • threading.Lock supports the context manager (with lock:) and is non-reentrant; threading.RLock is required when a locked section re-enters the same lock.

  • subprocess.run(args, capture_output=True, text=True, check=True) is the modern API; check=True raises CalledProcessError on nonzero exit; shell=True invokes a shell and risks injection.

  • Pass argv as a list (never a concatenated string) when shell=False; use timeout= to bound runtime, which raises TimeoutExpired and kills the child.

  • multiprocessing start methods: fork (default on Linux, fast but unsafe with threads), spawn (default on macOS/Windows, fresh interpreter, requires main guard), forkserver.

  • Deadlock avoidance: acquire multiple locks in a fixed global order; use timeouts on acquire(); prefer queues over shared mutable state to avoid lost updates.

Quick reference

Network programming (sockets, automation, SDN, security)

  • socket.socket(AF_INET, SOCK_STREAM) is TCP; SOCK_DGRAM is UDP; server sequence is bind -> listen -> accept, client sequence is connect -> send/recv.

  • recv(n) returns at most n bytes and b'' means the peer closed; TCP is a byte stream with no message boundaries, so length-prefix or delimiter framing is required.

  • socket.create_connection((host, port), timeout=) handles address resolution and timeout; getaddrinfo(host, port, type=SOCK_STREAM) returns address tuples for IPv4/IPv6 portability.

  • SO_REUSEADDR lets a restarted server rebind a port in TIME_WAIT; SO_REUSEPORT allows multiple sockets to bind the same port for load distribution.

  • Automation libraries: netmiko/paramiko for SSH to devices, napalm for multi-vendor config, ncclient for NETCONF, requests for RESTCONF/HTTP APIs; config is idempotent when applied declaratively.

  • SDN separates control plane (controller computes forwarding) from data plane (switches forward); OpenFlow is the classic southbound protocol; northbound APIs are typically REST/JSON.

  • TLS: ssl.create_default_context() gives verified client/server contexts with hostname checking; never disable CERT_REQUIRED in production; wrap sockets with context.wrap_socket().

  • Security rules: validate and bound all input lengths, prefer TLS over plaintext, use hmac.compare_digest for secret comparison, and never interpolate untrusted data into shell commands.

Quick reference

Python-SQL and Python-NoSQL database access

  • DB-API flow: connect() -> cursor() -> execute(sql, params) -> fetchone/fetchall/fetchmany -> commit() -> close(); parameter placeholders are driver-specific (qmark ? for sqlite3, %s for psycopg).

  • Always pass parameters as a tuple to execute(); string concatenation enables SQL injection; executemany() batches inserts efficiently.

  • Transactions: connections default to implicit transactions in many drivers; call commit() to persist and rollback() on exception; use the connection as a context manager where supported.

  • ORM concepts: declarative model classes map to tables, Session/Unit of Work tracks changes and flushes them, lazy vs eager loading (selectinload/joinedload) controls query count.

  • SQLAlchemy Core uses select(), insert(), update(), delete() with expression objects; the ORM layer adds identity map, relationship(), and cascade rules.

  • NoSQL families: document (MongoDB), key-value (Redis), wide-column (Cassandra), graph (Neo4j); choose by access pattern, not by hype; schema-on-read shifts validation to the app.

  • pymongo: collection.insert_one/insert_many, find_one/find with query dicts, update_one with $set, delete_one; indexes via create_index support unique and compound keys.

  • N+1 query problem: one query for parents plus one per child; fix with a join, a batched IN query, or ORM eager loading; measure with query logging.

Quick reference

Testing principles and techniques (unittest and pytest)

  • unittest: tests subclass unittest.TestCase; assertions are methods (assertEqual, assertTrue, assertRaises); test discovery matches files named test*.py and methods prefixed test_.

  • pytest: plain assert statements; assertion rewriting reports the values that failed; test functions or classes named Test* are collected; fixtures inject dependencies via function parameters.

  • Fixture scoping in pytest: scope="function" (default, fresh per test), "class", "module", "package", "session"; use yield in a fixture for setup/teardown around the yield point.

  • parametrize: @pytest.mark.parametrize("a,b,expected", [(1,1,2),(2,3,5)]) generates one test per tuple; stack decorators to get the Cartesian product of parameter sets.

  • Mocking: unittest.mock.patch replaces an attribute for the duration of the with-block or decorated function; Mock records call_args, call_count, and return_value; autospec=True checks call signatures.

  • Exception testing: self.assertRaises(ValueError) as context in unittest; pytest.raises(ValueError, match="pattern") with a regex match on str(excinfo.value).

  • unittest lifecycle order: setUpClass (once per class) -> setUp -> test method -> tearDown -> tearDownClass; pytest equivalents are module/session fixtures plus function fixtures.

  • Test doubles vocabulary: stub returns canned data, mock verifies interactions, fake is a working lightweight implementation, spy records calls while delegating to the real object.

Quick reference

Design patterns (OOP principles and GoF patterns)

  • SOLID: Single responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion (depend on abstractions, not concretions).

  • Singleton: one instance per process; in Python implement with a class attribute plus new, or a module-level instance, since modules are cached in sys.modules.

  • Factory Method defers instantiation to subclasses; Abstract Factory groups related factories; a plain factory function returning one of several classes is the common Python form.

  • Facade: a single simplified interface over a subsystem of several classes; it does not add behavior, only reduces coupling for callers.

  • Proxy: same interface as the real subject, controls access (lazy load, permission check, remote call, caching); differs from Decorator, which adds behavior.

  • Observer: subject keeps a list of observers and calls update() on notify; Python alternative is callback registration or weakref.WeakSet to avoid leaks.

  • Command: encapsulate a request as an object with execute() (and often undo()); enables queues, logging, and macro recording of operations.

  • Template Method: base class defines the algorithm skeleton in a concrete method and calls abstract hook methods that subclasses override; State pattern delegates behavior to a state object that can replace itself.

Quick reference

Interprocess communication (multiprocessing, threading, subprocess, synchronization)

  • GIL: threads suit I/O-bound work and share memory; multiprocessing bypasses the GIL for CPU-bound work but each process has separate memory, so data must be pickled across the boundary.

  • multiprocessing primitives: Process(target=, args=), Pool.map/imap for fan-out, Queue and Pipe for messaging; objects passed must be picklable.

  • Synchronization: Lock (mutual exclusion), RLock (reentrant, same thread may re-acquire), Semaphore (counted permits), Event (flag with wait/set), Condition (wait/notify), Barrier (N parties meet).

  • threading.Lock supports the context manager (with lock:) and is non-reentrant; threading.RLock is required when a locked section re-enters the same lock.

  • subprocess.run(args, capture_output=True, text=True, check=True) is the modern API; check=True raises CalledProcessError on nonzero exit; shell=True invokes a shell and risks injection.

  • Pass argv as a list (never a concatenated string) when shell=False; use timeout= to bound runtime, which raises TimeoutExpired and kills the child.

  • multiprocessing start methods: fork (default on Linux, fast but unsafe with threads), spawn (default on macOS/Windows, fresh interpreter, requires main guard), forkserver.

  • Deadlock avoidance: acquire multiple locks in a fixed global order; use timeouts on acquire(); prefer queues over shared mutable state to avoid lost updates.

Quick reference

Network programming (sockets, automation, SDN, security)

  • socket.socket(AF_INET, SOCK_STREAM) is TCP; SOCK_DGRAM is UDP; server sequence is bind -> listen -> accept, client sequence is connect -> send/recv.

  • recv(n) returns at most n bytes and b'' means the peer closed; TCP is a byte stream with no message boundaries, so length-prefix or delimiter framing is required.

  • socket.create_connection((host, port), timeout=) handles address resolution and timeout; getaddrinfo(host, port, type=SOCK_STREAM) returns address tuples for IPv4/IPv6 portability.

  • SO_REUSEADDR lets a restarted server rebind a port in TIME_WAIT; SO_REUSEPORT allows multiple sockets to bind the same port for load distribution.

  • Automation libraries: netmiko/paramiko for SSH to devices, napalm for multi-vendor config, ncclient for NETCONF, requests for RESTCONF/HTTP APIs; config is idempotent when applied declaratively.

  • SDN separates control plane (controller computes forwarding) from data plane (switches forward); OpenFlow is the classic southbound protocol; northbound APIs are typically REST/JSON.

  • TLS: ssl.create_default_context() gives verified client/server contexts with hostname checking; never disable CERT_REQUIRED in production; wrap sockets with context.wrap_socket().

  • Security rules: validate and bound all input lengths, prefer TLS over plaintext, use hmac.compare_digest for secret comparison, and never interpolate untrusted data into shell commands.

Quick reference

Python-SQL and Python-NoSQL database access

  • DB-API flow: connect() -> cursor() -> execute(sql, params) -> fetchone/fetchall/fetchmany -> commit() -> close(); parameter placeholders are driver-specific (qmark ? for sqlite3, %s for psycopg).

  • Always pass parameters as a tuple to execute(); string concatenation enables SQL injection; executemany() batches inserts efficiently.

  • Transactions: connections default to implicit transactions in many drivers; call commit() to persist and rollback() on exception; use the connection as a context manager where supported.

  • ORM concepts: declarative model classes map to tables, Session/Unit of Work tracks changes and flushes them, lazy vs eager loading (selectinload/joinedload) controls query count.

  • SQLAlchemy Core uses select(), insert(), update(), delete() with expression objects; the ORM layer adds identity map, relationship(), and cascade rules.

  • NoSQL families: document (MongoDB), key-value (Redis), wide-column (Cassandra), graph (Neo4j); choose by access pattern, not by hype; schema-on-read shifts validation to the app.

  • pymongo: collection.insert_one/insert_many, find_one/find with query dicts, update_one with $set, delete_one; indexes via create_index support unique and compound keys.

  • N+1 query problem: one query for parents plus one per child; fix with a join, a batched IN query, or ORM eager loading; measure with query logging.