AI EngineeringZero to ProductionHome·About·Contact
Developer Foundations · Chapter DF5

Environments, packaging & dependencies

Reproducible setups the pro way: venvs, pinned deps, semantic versioning, dependency resolution & conflicts, packaging with pyproject, and enforcing team-wide reproducibility in CI.

⏱️ ~3 hours🧪 8 labs🎯 Beginner→Tech-lead

Learning objectives

  • Use virtual environments and pinned dependencies fluently.
  • Reason about semantic versioning and dependency resolution.
  • Structure, build, and publish a package.
  • Guarantee reproducibility across a team.
▶ Runnable companionCode saved under code/df5-environments/. Python runs offline; configs are ready to use.

1 · Virtual environments — isolation essential

A venv is a private package folder per project so versions don't collide. Every project starts with one.

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
shell · create, activate, freeze
venv.shpython3 -m venv .venv
source .venv/bin/activate           # prompt shows (.venv)
pip install requests
pip freeze > requirements.txt       # snapshot exact versions
deactivate
▶ How this works

A virtual environment (venv) is a private, throwaway folder that holds one project's Python packages. Without it, every pip install dumps packages into your system Python, so two projects that need different versions of the same package fight each other. A venv gives each project its own clean box. This lab creates one, turns it on, installs a package into it, and records what got installed.

  1. python3 -m venv .venv creates the environment in a hidden folder named .venv inside your project. It's just a directory of files — nothing is installed system-wide yet.
  2. source .venv/bin/activate switches on the venv for this terminal. You'll see (.venv) appear at the start of your prompt — that's how you know packages will now go into the project's box, not the system.
  3. pip install requests downloads the requests package into the active venv only. Other projects never see it.
  4. pip freeze > requirements.txt writes the exact versions of everything installed into a text file (the > saves the output to that file). That file is the recipe someone else uses to rebuild the same setup.
  5. deactivate turns the venv back off, returning your terminal to the system Python.

What the output means: Nothing dramatic prints; the payoff is on disk — a .venv/ folder holding requests, and a requirements.txt listing its exact version.

Try this: Run these lines, then type which python before and after deactivate. Inside the venv it points into .venv/; after deactivating it points back at the system Python. That path change is the isolation.

The #1 'works on my machine' causeInstalling globally mixes every project's dependencies. Always create a venv per project. Containers (CD) take this isolation further; a venv is the local foundation.

2 · Dependencies & requirements essential

shell · pin & reproduce
requirements.shpip install fastapi uvicorn
pip freeze > requirements.txt
pip install -r requirements.txt     # reproduce exactly, elsewhere/CI/container
cat requirements.txt                # fastapi==0.115.0  uvicorn==0.30.0 ...
▶ How this works

requirements.txt is your project's shopping list of exact package versions. This lab shows the full loop: install what you need, snapshot it, then rebuild it identically on another machine. This is how you avoid "but it worked on my laptop".

  1. pip install fastapi uvicorn installs two packages (a web framework and the server that runs it) into the active venv.
  2. pip freeze > requirements.txt snapshots every installed package and its exact version into the file — not just the two you named, but everything they pulled in too.
  3. pip install -r requirements.txt is the reverse: the -r flag means "read this requirements file and install exactly what it lists." Run on a teammate's machine, a CI runner, or inside a container, it recreates the same environment.
  4. cat requirements.txt just prints the file so you can see the pinned lines, e.g. fastapi==0.115.0. The == means "exactly this version".

What the output means: The cat shows lines like fastapi==0.115.0 — one per package, each locked to a specific version.

Try this: Open requirements.txt after running freeze. You'll see more lines than the two packages you installed — those extras are dependencies-of-dependencies, captured automatically so the rebuild is complete.

3 · Semantic versioning intermediate

Versions are MAJOR.MINOR.PATCH. MAJOR = breaking, MINOR = new-but-compatible, PATCH = bugfix. This tells you whether an upgrade is safe, and which spec to write.

SpecMeansUse for
x==2.3.1exactly thisapps (reproducible)
x>=2.3,<3compatible rangelibraries
x~=2.3.02.3.* onlyconservative pins
Python · compare versions safely (runs)
semver.pydef parse(v): return tuple(int(x) for x in v.split(".")[:3])
def is_compatible(have, spec_min, spec_max_major):
    h = parse(have)
    return parse(spec_min) <= h and h[0] < spec_max_major

print("2.3.1 satisfies >=2.3,<3 :", is_compatible("2.3.1", "2.3.0", 3))
print("3.0.0 satisfies >=2.3,<3 :", is_compatible("3.0.0", "2.3.0", 3))  # breaking major
print("major bump = review before upgrading")
2.3.1 satisfies >=2.3,<3 : True
3.0.0 satisfies >=2.3,<3 : False
major bump = review before upgrading
▶ How this works

Package versions look like 2.3.1 = MAJOR.MINOR.PATCH. A MAJOR bump (2 → 3) can break your code; MINOR and PATCH bumps are meant to be safe. This tiny program checks whether a version falls inside an allowed range, which is exactly the question pip asks before upgrading.

  1. parse(v) turns the string "2.3.1" into the number triple (2, 3, 1) by splitting on the dots — so versions can be compared like numbers instead of text.
  2. is_compatible(...) returns True only if the version is at or above the minimum and its major number (h[0]) is below the cutoff major. That mirrors a spec like >=2.3,<3.
  3. The first print checks 2.3.1 against >=2.3,<3 — it fits, so it prints True.
  4. The second checks 3.0.0 against the same range. Its major is 3, which is not below 3, so it prints False — a breaking major that you should review before upgrading.

What the output means: True then False: 2.3.1 is inside the allowed range, 3.0.0 is a breaking major that falls outside it.

Try this: Change the check to is_compatible("2.9.9", "2.3.0", 3) — still True, because any 2.x is allowed. Then try "2.2.0": False, because it's below the 2.3 minimum.

4 · Advanced — dependency resolution & conflicts advanced

When two of your dependencies need different versions of a third, you get a conflict. Understanding this saves hours. A resolver finds a version satisfying all constraints — or fails. Here's the core idea, runnable.

Python · a tiny version resolver (runs)
resolver.pydef resolve(constraints, available):
    """constraints: list of (min_major, ...). available: sorted versions.
    Return the highest version satisfying ALL constraints, or None."""
    def ok(v, c): return c["min"] <= v < c["lt_major"]
    for v in sorted(available, reverse=True):
        if all(ok(v, c) for c in constraints):
            return v
    return None

# pkgA needs >=1.2,<2 ; pkgB needs >=1.5,<2 -> highest 1.x that fits
avail = [1.1, 1.3, 1.6, 1.9, 2.0, 2.1]
cons = [{"min": 1.2, "lt_major": 2.0}, {"min": 1.5, "lt_major": 2.0}]
print("resolved to:", resolve(cons, avail))            # 1.9
# now a conflict: pkgC demands >=2.0
cons2 = cons + [{"min": 2.0, "lt_major": 3.0}]
print("with pkgC>=2.0:", resolve(cons2, avail))        # None -> CONFLICT
resolved to: 1.9
with pkgC>=2.0: None
▶ How this works

When two of your packages each demand a different version of a third package, something has to give — that's a dependency conflict. Real tools like pip run a resolver to find one version that satisfies everyone, or to report failure. This lab is a miniature resolver so you can see the logic.

  1. resolve(constraints, available) takes a list of rules and the list of versions that actually exist, and returns the highest version that obeys all the rules.
  2. ok(v, c) is true when a version v sits inside one constraint's window (at or above min, below lt_major).
  3. The loop walks versions highest-first (reverse=True) and returns the first that passes all(...) the constraints — so you always get the newest version that fits.
  4. First run: pkgA needs >=1.2,<2 and pkgB needs >=1.5,<2. The newest version satisfying both is 1.9.
  5. Then a conflict is added: pkgC demands >=2.0, but the others cap at below 2.0. No single version can satisfy all three, so resolve returns None.

What the output means: resolved to: 1.9 (a version everyone accepts), then with pkgC>=2.0: NoneNone is the resolver's way of saying "impossible, these requirements conflict."

Try this: Add 2.5 to the avail list and change pkgA/pkgB's lt_major to 3.0. Now the conflict disappears and the resolver finds a version that satisfies pkgC too.

Lockfiles freeze the resolutionA lockfile (requirements.txt from pip freeze, or poetry/uv locks) records the exact resolved versions so every install is identical. Apps commit a lockfile; libraries specify ranges and let the app resolve.

5 · Advanced — a clean project layout advanced

layout · the standard shape
layout.txtmy_project/
├── README.md
├── .gitignore
├── pyproject.toml          # metadata + deps + entry points (modern standard)
├── src/my_project/
│   ├── __init__.py
│   └── core.py
└── tests/                  # your Testing section lives here
    └── test_core.py
▶ How this works

This isn't code that runs — it's the standard folder shape of a professional Python project. Following it means tools, teammates, and packaging all know where to look. The ├── and └── lines are just a drawing of the folder tree (indentation = "inside this folder").

  1. README.md explains the project; .gitignore lists files Git should ignore (like .venv/).
  2. pyproject.toml is the modern control file: it holds the project's name, version, dependencies, and how to build it (covered in the next lab).
  3. src/my_project/ holds your actual code. __init__.py marks the folder as an importable package; core.py is your module. Putting code under src/ prevents accidentally importing it before it's properly installed.
  4. tests/ holds your tests, kept separate from the code they check.

Try this: Compare this to your own scripts folder. The single biggest upgrade is moving loose .py files into src/my_project/ and adding a pyproject.toml — that's what turns "some scripts" into "a package".

6 · Professional — package with pyproject.toml professional

config · pyproject.toml
pyproject.toml[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = ["requests>=2.30,<3"]

[project.scripts]
mytool = "my_project.core:main"      # pip install . -> `mytool` command

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
# build a wheel:  python -m build   ;  editable install:  pip install -e .
▶ How this works

pyproject.toml is the one file that describes your whole package: its name, version, what Python it needs, its dependencies, and how to build it. It's written in TOML — a simple key = value config format grouped into [sections]. This replaces the older setup.py.

  1. [project] holds the basics: name, version, the minimum Python (requires-python), and dependencies — here a range requests>=2.30,<3 meaning "any 2.x from 2.30 up."
  2. [project.scripts] creates a command-line command. mytool = "my_project.core:main" means "after install, typing mytool in a terminal runs the main function in core.py."
  3. [build-system] tells pip which tool builds the package (hatchling here). You rarely change this — it's boilerplate.
  4. The comments show the two commands you'll actually run: python -m build makes a shippable file (a wheel), and pip install -e . does an editable install — installs the project while pointing at your source so edits take effect without reinstalling.

Try this: Run pip install -e . in a folder with this file, then type mytool. The command exists because of the [project.scripts] line — that's how CLI tools like black or pytest become terminal commands.

7 · Professional — inspect the environment professional

Python · read installed metadata (runs)
metadata.pyfrom importlib.metadata import version, distributions
print("pip:", version("pip"))
some = sorted(d.metadata["Name"] for d in distributions())[:5]
print("installed (sample):", some)
pip: 24.0
installed (sample): ['pip', 'requests', 'setuptools', ...]
▶ How this works

Sometimes you need your program to ask "what's actually installed here, and which version?" — for logging, debugging, or compatibility checks. Python's built-in importlib.metadata reads that information straight from the environment, no extra packages needed.

  1. from importlib.metadata import version, distributions pulls in two helpers: version (get one package's version) and distributions (list everything installed).
  2. version("pip") returns pip's own version as a string and prints it.
  3. distributions() yields every installed package; the code reads each one's "Name", sorts them, and keeps the first five ([:5]) as a sample.
  4. The final print shows that short sample list.

What the output means: Two lines: pip's version (e.g. pip: 24.0) and the first few installed package names alphabetically — a quick snapshot of what lives in this venv.

Try this: Change [:5] to [:20] to see more, or call version("requests"). If you ask for a package that isn't installed, you'll get an error — which is exactly what the next lab turns into a useful CI check.

8 · Tech-lead — reproducibility across the team tech-lead

A lead guarantees everyone runs the same environment: pinned lockfiles, a documented setup, and a CI check that catches drift before it causes "works on my machine" bugs.

Python · fail CI on dependency drift (runs)
env_audit.pyfrom importlib.metadata import version, PackageNotFoundError
REQUIRED = {"pip": None}   # in real use: {"requests":"2","pytest":"8", ...} pinned majors

def audit(required):
    problems = []
    for pkg, want_major in required.items():
        try: have = version(pkg)
        except PackageNotFoundError: problems.append(f"MISSING: {pkg}"); continue
        if want_major and have.split(".")[0] != want_major:
            problems.append(f"{pkg}: want major {want_major}, have {have}")
    return problems

issues = audit(REQUIRED)
print("environment OK" if not issues else "DRIFT:\n" + "\n".join(issues))
environment OK
▶ How this works

A team lead's job is to make sure everyone runs the same versions. This script is a drift check: it compares what's installed against what's required and reports any mismatch. Wired into CI, it fails the build before a version mismatch causes a "works on my machine" bug.

  1. REQUIRED is a dictionary of package: expected-major-version. Here it only checks that pip exists (None = don't check the version); the comment shows a real one pinning majors like {"requests":"2", "pytest":"8"}.
  2. audit(required) loops over each required package and collects problems into a list.
  3. try: have = version(pkg) attempts to read the installed version. If the package is missing it raises PackageNotFoundError, which the except catches and records as MISSING instead of crashing.
  4. have.split(".")[0] grabs the installed major number. If it doesn't match the wanted major, that's flagged as drift.
  5. Finally it prints environment OK when the problems list is empty, or lists every problem it found.

What the output means: environment OK — the list of problems was empty, so the environment matches requirements. In CI you'd have the script exit non-zero on problems to fail the build.

Try this: Add a fake package: REQUIRED = {"pip": None, "nonexistent-pkg": "1"} and re-run. You'll see DRIFT: MISSING: nonexistent-pkg — the exact signal that stops a broken environment from shipping.

Reproducibility is the foundation for TQ + CDPinned deps + a lockfile + a CI env-audit means every laptop, CI runner, and container runs identical versions. That reproducibility is exactly what the Testing and Containers sections build on — and a lead's job to enforce.

Exercise DF5.1 — Package & guard a project

Context: This capstone folds the whole lesson into one shippable artifact: a src/-layout package that installs cleanly, exposes a command, and guards its own reproducibility in CI.

Your task: Restructure a script into a src/ layout, write a pyproject.toml with a [project.scripts] entry, pip install -e . it, and add the env_audit pinned-dependency check to CI.

Requirements:

  • Move importable code under src/ and package it correctly
  • pyproject.toml declares metadata, dependencies, and a console entry point
  • pip install -e . succeeds and the command runs
  • CI runs the env_audit check and fails on any floating dependency
  • Bonus: deliberately introduce a dependency conflict, then resolve it by dropping a pin or upgrading

💡 Hint: Reuse the reproducibility check from the Industry rung as the CI step; the src/ layout keeps import paths honest once installed editable.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · Create and use a venvBeginner

Context: Every serious Python project runs in its own isolated environment so one project's dependencies never leak into another's. A virtual environment is the first thing you create before installing anything.

Your task: Create an isolated virtual environment, activate it, install requests into it, and prove the install is local to that env and not your global Python.

Requirements:

  • Create the env with the stdlib venv module into a .venv/ folder
  • Show the macOS/Linux activation command (and note the Windows variant)
  • Install requests only after activating
  • Verify with pip list / which python that the package and interpreter live inside .venv, not the system Python
  • Deactivate and confirm the package is no longer importable globally

💡 Hint: python3 -m venv .venv then source .venv/bin/activate; a quick which python before and after activation makes the isolation obvious.

Show solution
python3 -m venv .venv          # create the env in ./.venv
source .venv/bin/activate      # activate (Windows: .venv\Scripts\activate)
python -m pip install requests
python -c "import requests; print(requests.__version__)"
which python                   # points inside .venv, not /usr/bin
deactivate                     # leave the env

A venv isolates one project's dependencies so upgrading a package here can't break another project.

Exercise 2 · Pin dependenciesIntermediate

Context: A colleague cloning your repo must be able to recreate the exact set of packages you tested against. That is what a pinned requirements.txt buys you, and the pin operator you choose decides how reproducible it really is.

Your task: Freeze your current environment into a requirements.txt, then recreate the identical package set in a brand-new env from that file, and explain the difference between >= and == pins.

Requirements:

  • Generate the file with pip freeze (exact package==version lines)
  • Recreate into a fresh env with pip install -r requirements.txt
  • State that == locks one version while >= allows any newer one — trading reproducibility for freshness
  • Confirm the two environments hold the same versions

💡 Hint: Freeze in the working env, delete/recreate .venv, install from the file, and diff the two pip freeze outputs — they should match line-for-line.

Show solution
pip freeze > requirements.txt      # exact versions of everything installed
# recreate elsewhere:
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

Example lines and what they mean:

requests==2.32.3      # exact -- reproducible, what freeze writes
flask>=3.0            # floor -- may pull a newer minor, less reproducible

Use loose ranges in your abstract deps (a library) and exact pins in a lock for apps, so deploys are byte-for-byte repeatable.

Exercise 3 · Compare versions by semverAdvanced

Context: Semantic versioning encodes a promise: patch and minor bumps are backward compatible, a major bump is allowed to break you. Reading that promise programmatically is how tooling decides whether an upgrade is safe to take.

Your task: Write a function that parses a MAJOR.MINOR.PATCH string and, given an old and new version, classifies the upgrade as a safe minor/patch bump or a breaking major bump. Test 1.4.0→1.5.2 and 1.4.0→2.0.0.

Requirements:

  • Parse the three numeric components into comparable integers
  • A change in MAJOR is flagged breaking; changes only below it are safe
  • Return a clear verdict (e.g. "safe" vs "breaking") per comparison
  • 1.4.0→1.5.2 is safe; 1.4.0→2.0.0 is breaking — assert both

💡 Hint: Split on . and compare the tuples; the major component alone decides the breaking-vs-safe verdict.

Show solution

Pure Python — the rule that >= pins rely on:

def parse(v):
    return tuple(int(x) for x in v.split("."))

def upgrade_kind(old, new):
    (a1, b1, c1), (a2, b2, c2) = parse(old), parse(new)
    if a2 > a1:            return "major -- may break, read changelog"
    if a2 == a1 and b2 > b1: return "minor -- new features, backward-compatible"
    if (a2, b2) == (a1, b1) and c2 > c1: return "patch -- bug fixes only"
    return "same or downgrade"

print(upgrade_kind("1.4.0", "1.5.2"))   # minor -- backward-compatible
print(upgrade_kind("1.4.0", "2.0.0"))   # major -- may break

Semver's contract: within a major version, upgrades should not break your code. A major bump is a signal to test.

Exercise 4 · Reason about a dependency conflictExpert

Context: Diamond dependency conflicts are a fact of life: two libraries you both need pin the same transitive dependency to ranges that don't overlap, and pip cannot satisfy both at once.

Your task: Explain why pip can't install an app that needs library A (urllib3>=2) and library B (urllib3<2) together, then write a resolver check that detects the incompatibility directly from the two version ranges.

Requirements:

  • State plainly that the two ranges have no version in common, so no single urllib3 satisfies both requirers
  • Represent each requirement as a comparable bound (operator + version)
  • The check returns that the ranges are incompatible (empty intersection)
  • It would return compatible if the ranges did overlap — test both cases

💡 Hint: Reduce each constraint to a numeric lower/upper bound and ask whether the intervals intersect; >=2 and <2 share no point.

Show solution

Only one version of urllib3 can be installed, and no single version satisfies both >=2 and <2 — that is a hard conflict pip reports as "ResolutionImpossible". Detecting it from ranges:

def satisfies(version, lower=None, upper=None):
    v = tuple(int(x) for x in version.split("."))
    if lower and v < tuple(int(x) for x in lower.split(".")): return False
    if upper and v >= tuple(int(x) for x in upper.split(".")): return False
    return True

# is there ANY version both accept? check candidate versions
candidates = ["1.26.0", "2.0.0", "2.2.0"]
a_ok = [v for v in candidates if satisfies(v, lower="2.0.0")]     # A: >=2
b_ok = [v for v in candidates if satisfies(v, upper="2.0.0")]     # B: <2
common = set(a_ok) & set(b_ok)
print("compatible versions:", common or "NONE -- conflict")

Real fix: upgrade B to a release that supports urllib3 2.x, or pin the app to the older urllib3 and hold A back. The empty intersection is the mathematical statement of "no solution".

Exercise 5 · Package with pyproject.tomlProfessional

Context: A folder of loose scripts is not shippable. Turning it into a proper package with pyproject.toml is what lets teammates pip install it, run it as a command, and depend on it reproducibly.

Your task: Convert a loose script folder into an installable package: write a pyproject.toml declaring the name, version, dependencies, and a console entry point, then install it editable and run the command.

Requirements:

  • pyproject.toml declares name, version, and a dependencies list
  • A [project.scripts] entry maps a command name to a module function
  • Install with pip install -e . so edits take effect without reinstalling
  • Running the declared command actually invokes your entry-point function

💡 Hint: The [project.scripts] table's cmd = "pkg.module:func" form is what creates the runnable command after an editable install.

Show solution
# pyproject.toml
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

[project]
name = "reportkit"
version = "0.1.0"
dependencies = ["requests>=2.31"]

[project.scripts]
report = "reportkit.cli:main"      # `report` on the CLI calls cli.main()
pip install -e .        # editable install: code changes take effect immediately
report --help           # the entry point now works as a command

pyproject.toml is the modern standard (PEP 621): one declarative file for build config, metadata, and dependencies — no setup.py boilerplate.

Exercise 6 · Reproducibility across the teamIndustry scenario

Context: "Works on my machine" is almost always an un-pinned dependency drifting to a new version between two installs. As tech lead you make reproducibility a mergeable, checkable property instead of a hope.

Your task: Define what makes an environment reproducible, then write a Python check that scans a requirements file and flags any unpinned (floating) dependency before it is allowed to merge.

Requirements:

  • Articulate reproducibility: same file + same Python must yield the same versions, i.e. every line is exactly pinned
  • Parse each requirement line and detect whether it is exactly pinned (==) or floating (>=, ~=, bare name)
  • Return the list of offending (floating) lines, empty when all are pinned
  • Wire it as a gate: non-empty result should fail the check / block the merge
  • Handle comments and blank lines without false positives

💡 Hint: A line is safe only if it contains an == version pin; treat everything else — bare names, >=, ranges — as a floating dependency to report.

Show solution

Reproducible = same inputs, same bytes: a pinned lock (exact versions and hashes), a pinned Python version, and a clean env built in CI — not the developer's laptop. A gate that fails on floating pins:

import re, sys

def audit(requirements_text):
    floating = []
    for line in requirements_text.splitlines():
        line = line.split("#")[0].strip()
        if not line:
            continue
        # a reproducible line has an exact == pin
        if "==" not in line:
            floating.append(line)
    return floating

reqs = """
requests==2.32.3
flask>=3.0
numpy
pydantic==2.7.1
"""
bad = audit(reqs)
print("unpinned:", bad)          # ['flask>=3.0', 'numpy']
sys.exit(1 if bad else 0)        # non-zero fails the CI gate

Layered fix: developers edit loose top-level deps, a tool (pip-tools/uv/poetry) compiles a fully pinned lock with hashes, CI builds from the lock only, and this check refuses to merge a lock that still floats.

✓ Checkpoint — you can move on when you can…

  • Use venvs and pinned dependencies.
  • Choose specs by semver; understand resolution + conflicts + lockfiles.
  • Structure and package a project with pyproject.
  • Enforce team-wide reproducibility in CI.

Knowledge check check yourself

✓ Knowledge check

Why does every project get its own virtual environment (venv) instead of installing packages globally?

Show answer
A venv is a private, per-project package folder, so one project's pinned versions can't collide with another's. Activating it makes python/pip point into .venv/ — that path change is the isolation.
✓ Knowledge check

Under semantic versioning (MAJOR.MINOR.PATCH), which part changing signals a breaking change, and what does a spec like >=2.3,<3 allow?

Show answer
A MAJOR bump signals breaking changes; MINOR is new-but-compatible and PATCH is a bugfix. >=2.3,<3 allows any 2.x at or above 2.3.0 but forbids 3.0.0, since crossing the major is unsafe.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in