diff --git a/Makefile b/Makefile index 412bf7c..f6ebf29 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,8 @@ run: @echo "===============================================================================" @echo "Running solution ..." @echo "===============================================================================" - make docker-run + make python-fetch-data + make python-build-dataset # destroy all Docker build and local artifacts # takes around 1 minute to complete @@ -61,7 +62,6 @@ tear-down: @echo "Tearing down solution ..." @echo "===============================================================================" make python-clean - make docker-prune pre-commit-init: @echo "===============================================================================" @@ -140,7 +140,6 @@ python-requirements: pip install pip==25.3 setuptools wheel pip-tools pip-compile requirements/in/base.in -o requirements/base.txt pip-compile requirements/in/local.in -o requirements/local.txt - pip-compile requirements/in/docker.in -o requirements/docker.txt python-fetch-data: @echo "===============================================================================" @@ -150,7 +149,7 @@ python-fetch-data: python-build-dataset: @echo "===============================================================================" - @echo "Building dataset from fetched data ..." + @echo "Building enriched Netflix dataset from fetched data ..." @echo "===============================================================================" $(ACTIVATE_VENV) && python -m netflix.fetch.dataset @@ -177,4 +176,6 @@ help: @echo 'python-lint - Run Python linting using pre-commit and pylint' @echo 'python-clean - Destroy the Python virtual environment and remove __pycache__ directories' @echo 'python-requirements - Compile and update Python dependency files' + @echo 'python-fetch-data - Fetch data from external APIs and save to local files' + @echo 'python-build-dataset - Build enriched Netflix dataset from fetched data' @echo '====================================================================' diff --git a/README.md b/README.md index 442ff1d..04f38ce 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,41 @@ Netflix AI Greenlight Challenge: Can Data Science Predict the Next Hit Drama? ## Quickstart +Install required system packages for your operating system: + +- [Windows](./setup/windows/setup.ps1) +- [macOS](./setup/macos/setup.sh) +- [Linux](./setup/linux/setup.sh) + +Initialize your environment. This includes creating and activating a Python virtual +environment, and then downloading data files for Netflix, IMDb and The Movie +Database (TMDB). The final dataset will be located at `./netflix/db/netflix_enriched_dataset.csv`. + +**The setup process will take between 5 and 15 minutes depending on your compute +device and your Internet connection.** + +```console +make python-init +make run +``` + +Other helpful commands: + +```console +source venv/bin/activate +which python3 +which pip3 +python --version # you should see Python 3.13.x +pip --version # you should see pip 25.3.x +``` + +## Completely Remove This Project + +```console +make tear-down +deactivate +``` + Setup your [Kaggle API Key](./docs/KAGGLE.md) ## Support diff --git a/changelogs/CHANGELOG.md b/changelogs/CHANGELOG.md index 6e75520..1911a9c 100644 --- a/changelogs/CHANGELOG.md +++ b/changelogs/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p +## [0.1.1-alpha.1](https://github.com/FullStackWithLawrence/netflix-writers/compare/v0.1.0...v0.1.1-alpha.1) (2026-06-16) + +### Bug Fixes + +* add os-specific setup scripts ([8d39cb0](https://github.com/FullStackWithLawrence/netflix-writers/commit/8d39cb04c762e5f3c096e5ac7b8be2a9f1d4b603)) + ## [0.1.0](https://github.com/FullStackWithLawrence/netflix-writers/compare/v0.0.1...v0.1.0) (2026-06-16) ### Features diff --git a/netflix/__version__.py b/netflix/__version__.py index a61c780..5dc4417 100644 --- a/netflix/__version__.py +++ b/netflix/__version__.py @@ -1,5 +1,5 @@ # DO NOT EDIT. # Managed via automated CI/CD in .github/workflows/semanticVersionBump.yml. -__version__ = "0.1.0" +__version__ = "0.1.1-alpha.1" __all__ = ["__version__"] diff --git a/netflix/fetch/fetch_imdb.py b/netflix/fetch/fetch_imdb.py index ee2a5f9..2edf053 100644 --- a/netflix/fetch/fetch_imdb.py +++ b/netflix/fetch/fetch_imdb.py @@ -10,6 +10,7 @@ - title.ratings.csv """ +import logging import os from pathlib import Path @@ -22,6 +23,13 @@ IMDB_DIR = os.path.join(DB_DIR, "imdb") IMDB_TITLE_TYPES = ["movie", "tvSeries"] +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s | %(levelname)s | %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) +logger = logging.getLogger(__name__) + def export_titles(con: duckdb.DuckDBPyConnection) -> None: """ @@ -39,7 +47,7 @@ def export_titles(con: duckdb.DuckDBPyConnection) -> None: TO '{output_path}' (FORMAT csv, HEADER true) """) - print(f"Exported title_basics to {output_path}.") + logger.info("Exported title_basics to %s.", output_path) def export_ratings(con: duckdb.DuckDBPyConnection) -> None: @@ -58,7 +66,7 @@ def export_ratings(con: duckdb.DuckDBPyConnection) -> None: TO '{output_path}' (FORMAT csv, HEADER true) """) - print(f"Exported title_ratings to {output_path}.") + logger.info("Exported title_ratings to %s.", output_path) def build_titles_table(con: duckdb.DuckDBPyConnection, filename: Path) -> None: @@ -72,7 +80,7 @@ def build_titles_table(con: duckdb.DuckDBPyConnection, filename: Path) -> None: .. returns: None """ - print(f"Building title_basics table from {filename}...") + logger.info("Building title_basics table from %s...", filename) title_types = ", ".join(f"'{t}'" for t in IMDB_TITLE_TYPES) con.execute( f""" @@ -85,7 +93,7 @@ def build_titles_table(con: duckdb.DuckDBPyConnection, filename: Path) -> None: """, [str(filename)], ) - print("built title_basics table.") + logger.info("Built title_basics table.") def build_ratings_table(con: duckdb.DuckDBPyConnection, filename: Path) -> None: @@ -99,7 +107,7 @@ def build_ratings_table(con: duckdb.DuckDBPyConnection, filename: Path) -> None: .. returns: None """ - print(f"Building title_ratings table from {filename}...") + logger.info("Building title_ratings table from %s...", filename) con.execute( """ CREATE OR REPLACE TABLE title_ratings AS @@ -110,7 +118,7 @@ def build_ratings_table(con: duckdb.DuckDBPyConnection, filename: Path) -> None: """, [str(filename)], ) - print("built title_ratings table.") + logger.info("Built title_ratings table.") def fetch_title_basics(with_cleanup: bool = False) -> None: @@ -140,8 +148,8 @@ def fetch_title_basics(with_cleanup: bool = False) -> None: if with_cleanup: cleanup(titles_file) - print("Generated titles.basics.csv") - print("-" * 40) + logger.info("Generated titles.basics.csv") + logger.info("-" * 40) def fetch_title_ratings(with_cleanup: bool = False) -> None: @@ -171,8 +179,8 @@ def fetch_title_ratings(with_cleanup: bool = False) -> None: if with_cleanup: cleanup(ratings_file) - print("Generated title.ratings.csv") - print("-" * 40) + logger.info("Generated title.ratings.csv") + logger.info("-" * 40) def main() -> None: diff --git a/netflix/fetch/fetch_kaggle_netflix.py b/netflix/fetch/fetch_kaggle_netflix.py index b5178e4..2833baf 100644 --- a/netflix/fetch/fetch_kaggle_netflix.py +++ b/netflix/fetch/fetch_kaggle_netflix.py @@ -8,6 +8,7 @@ https://www.kaggle.com/datasets/dhruvildave/netflix-top-10-tv-shows-and-films """ +import logging import os from kaggle.api.kaggle_api_extended import KaggleApi # type: ignore[import-untyped] @@ -18,6 +19,12 @@ DATASET = "dhruvildave/netflix-top-10-tv-shows-and-films" api = KaggleApi() +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s | %(levelname)s | %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) +logger = logging.getLogger(__name__) def main(): @@ -32,8 +39,8 @@ def main(): """ api.authenticate() api.dataset_download_files(DATASET, path=KAGGLE_DIR, unzip=True) - print("Dataset downloaded successfully.") - print("-" * 40) + logger.info("Dataset downloaded successfully.") + logger.info("-" * 40) if __name__ == "__main__": diff --git a/netflix/fetch/fetch_kaggle_tmdb.py b/netflix/fetch/fetch_kaggle_tmdb.py index 2305f39..f5a5036 100644 --- a/netflix/fetch/fetch_kaggle_tmdb.py +++ b/netflix/fetch/fetch_kaggle_tmdb.py @@ -7,6 +7,7 @@ https://www.kaggle.com/datasets/asaniczka/full-tmdb-tv-shows-dataset-2023-150k-shows """ +import logging import os from kaggle.api.kaggle_api_extended import KaggleApi # type: ignore[import-untyped] @@ -17,6 +18,12 @@ DATASET = "asaniczka/full-tmdb-tv-shows-dataset-2023-150k-shows" api = KaggleApi() +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s | %(levelname)s | %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) +logger = logging.getLogger(__name__) def main(): @@ -31,8 +38,8 @@ def main(): """ api.authenticate() api.dataset_download_files(DATASET, path=KAGGLE_DIR, unzip=True) - print("Dataset downloaded successfully.") - print("-" * 40) + logger.info("Dataset downloaded successfully.") + logger.info("-" * 40) if __name__ == "__main__": diff --git a/netflix/fetch/fetch_polti.py b/netflix/fetch/fetch_polti.py index bd37307..15d335c 100644 --- a/netflix/fetch/fetch_polti.py +++ b/netflix/fetch/fetch_polti.py @@ -9,12 +9,19 @@ """ import csv +import logging import os from pathlib import Path from .const import DB_DIR POLTI_DIR = os.path.join(DB_DIR, "polti") +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s | %(levelname)s | %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) +logger = logging.getLogger(__name__) def write_situations_csv(output_path: Path) -> None: @@ -98,13 +105,13 @@ def write_situations_csv(output_path: Path) -> None: writer.writerow(["Number", "Situation", "Description"]) writer.writerows(rows) - print(f"CSV written to: {output_path}") + logger.info("CSV written to: %s", output_path) def main(): path = Path(os.path.join(POLTI_DIR, "situations.csv")) write_situations_csv(path) - print("-" * 40) + logger.info("-" * 40) if __name__ == "__main__": diff --git a/netflix/fetch/lib.py b/netflix/fetch/lib.py index f19f6ce..bbf6d25 100644 --- a/netflix/fetch/lib.py +++ b/netflix/fetch/lib.py @@ -1,14 +1,23 @@ """Utility functions for downloading and managing files.""" import json +import logging from pathlib import Path +from typing import Any, Optional import pandas as pd import requests from tqdm import tqdm +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s | %(levelname)s | %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) +logger = logging.getLogger(__name__) -def fetch_url(url: str, output_dir: str | Path, timeout: int = 60) -> Path | None: + +def fetch_url(url: str, output_dir: str | Path, timeout: int = 60) -> Optional[Path]: """ Download a file and return its local path. @@ -26,12 +35,12 @@ def fetch_url(url: str, output_dir: str | Path, timeout: int = 60) -> Path | Non filename = url.rsplit("/", maxsplit=1)[-1] output_path = output_dir / filename if output_path.exists(): - print(f"File {output_path} already exists, skipping download.") + logger.info("File %s already exists, skipping download.", output_path) return output_path # Reuse an existing download. if output_path.exists(): - print(f"File {output_path} already exists, skipping download.") + logger.info("File %s already exists, skipping download.", output_path) return output_path try: @@ -53,7 +62,7 @@ def fetch_url(url: str, output_dir: str | Path, timeout: int = 60) -> Path | Non pbar.update(len(chunk)) except (requests.RequestException, OSError) as exc: - print(f"Failed to download {url}: {exc}") + logger.error("Failed to download %s: %s", url, exc) # Avoid leaving behind a partial download. output_path.unlink(missing_ok=True) @@ -68,10 +77,10 @@ def cleanup(filename: Path) -> None: try: filename.unlink(missing_ok=True) except OSError as exc: - print(f"Failed to remove {filename}: {exc}") + logger.error("Failed to remove %s: %s", filename, exc) -def safe_cast(x) -> list: +def safe_cast(x: Any) -> list[Any]: """ Always returns a list safely, even if input is: @@ -96,8 +105,8 @@ def safe_cast(x) -> list: if pd.isna(x): return [] # pylint: disable=broad-except - except Exception: - pass + except Exception as exc: + logger.error("Failed to check NaN: %s", exc) if isinstance(x, str): try: @@ -106,7 +115,8 @@ def safe_cast(x) -> list: return [c.get("name") for c in parsed if isinstance(c, dict) and "name" in c] return [] # pylint: disable=broad-except - except Exception: + except Exception as exc: + logger.error("Failed to parse JSON: %s", exc) return [] return [] diff --git a/pyproject.toml b/pyproject.toml index c9d260c..3890bd0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "netflix-writers" -version = "0.1.0" +version = "0.1.1-alpha.1" requires-python = ">=3.12" description = "Netflix Writers: An AI-powered storytelling assistant for content creators." authors = [{ name = "Lawrence McDaniel", email = "lpm0073@gmail.com" }] diff --git a/release.config.js b/release.config.js index 1ca7f9a..1cf24e5 100644 --- a/release.config.js +++ b/release.config.js @@ -1,5 +1,15 @@ module.exports = { - branches: ["main", "beta", "alpha"], + branches: [ + "main", + { + name: "beta", + prerelease: "beta", + }, + { + name: "alpha", + prerelease: "alpha", + }, + ], dryRun: false, plugins: [ [ diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 0000000..9fd073a --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,423 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --output-file=requirements/base.txt requirements/in/base.in +# +annotated-doc==0.0.4 + # via typer +anyio==4.14.0 + # via + # httpx + # jupyter-server +appnope==0.1.4 + # via ipykernel +argon2-cffi==25.1.0 + # via jupyter-server +argon2-cffi-bindings==25.1.0 + # via argon2-cffi +arrow==1.4.0 + # via isoduration +ast-serialize==0.5.0 + # via mypy +asttokens==3.0.1 + # via stack-data +async-lru==2.3.0 + # via jupyterlab +attrs==26.1.0 + # via + # jsonschema + # referencing +babel==2.18.0 + # via jupyterlab-server +beautifulsoup4==4.15.0 + # via nbconvert +bleach[css]==6.4.0 + # via + # kaggle + # nbconvert +certifi==2026.5.20 + # via + # httpcore + # httpx + # requests +cffi==2.0.0 + # via argon2-cffi-bindings +charset-normalizer==3.4.7 + # via requests +cloudpickle==3.1.2 + # via shap +comm==0.2.3 + # via ipykernel +contourpy==1.3.3 + # via matplotlib +cycler==0.12.1 + # via matplotlib +debugpy==1.8.21 + # via ipykernel +decorator==5.3.1 + # via ipython +defusedxml==0.7.1 + # via nbconvert +duckdb==1.5.3 + # via -r requirements/in/base.in +executing==2.2.1 + # via stack-data +fastjsonschema==2.21.2 + # via nbformat +fonttools==4.63.0 + # via matplotlib +fqdn==1.5.1 + # via jsonschema +h11==0.16.0 + # via httpcore +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via jupyterlab +idna==3.18 + # via + # anyio + # httpx + # jsonschema + # requests +ipykernel==7.3.0 + # via + # -r requirements/in/base.in + # jupyterlab +ipython==9.14.1 + # via ipykernel +ipython-pygments-lexers==1.1.1 + # via ipython +isoduration==20.11.0 + # via jsonschema +jedi==0.20.0 + # via ipython +jinja2==3.1.6 + # via + # jupyter-server + # jupyterlab + # jupyterlab-server + # nbconvert +joblib==1.5.3 + # via scikit-learn +json5==0.14.0 + # via jupyterlab-server +jsonpointer==3.1.1 + # via jsonschema +jsonschema[format-nongpl]==4.26.0 + # via + # jupyter-events + # jupyterlab-server + # nbformat +jsonschema-specifications==2025.9.1 + # via jsonschema +jupyter-client==8.9.1 + # via + # ipykernel + # jupyter-server + # nbclient +jupyter-core==5.9.1 + # via + # ipykernel + # jupyter-client + # jupyter-server + # jupyterlab + # nbclient + # nbconvert + # nbformat +jupyter-events==0.12.1 + # via jupyter-server +jupyter-lsp==2.3.1 + # via jupyterlab +jupyter-server==2.19.0 + # via + # jupyter-lsp + # jupyterlab + # jupyterlab-server + # notebook-shim +jupyter-server-terminals==0.5.4 + # via jupyter-server +jupyterlab==4.5.8 + # via -r requirements/in/base.in +jupyterlab-pygments==0.3.0 + # via nbconvert +jupyterlab-server==2.28.0 + # via jupyterlab +jupytext==1.19.3 + # via kaggle +kaggle==2.2.2 + # via -r requirements/in/base.in +kagglesdk==0.1.30 + # via kaggle +kiwisolver==1.5.0 + # via matplotlib +lark==1.3.1 + # via rfc3987-syntax +librt==0.11.0 + # via mypy +llvmlite==0.47.0 + # via + # numba + # shap +markdown-it-py==4.2.0 + # via + # jupytext + # mdit-py-plugins + # rich +markupsafe==3.0.3 + # via + # jinja2 + # nbconvert +matplotlib==3.11.0 + # via + # -r requirements/in/base.in + # seaborn +matplotlib-inline==0.2.2 + # via + # ipykernel + # ipython +mdit-py-plugins==0.6.1 + # via jupytext +mdurl==0.1.2 + # via markdown-it-py +mistune==3.2.1 + # via nbconvert +mypy==2.1.0 + # via -r requirements/in/base.in +mypy-extensions==1.1.0 + # via mypy +narwhals==2.22.1 + # via scikit-learn +nbclient==0.11.0 + # via nbconvert +nbconvert==7.17.1 + # via jupyter-server +nbformat==5.10.4 + # via + # jupyter-server + # jupytext + # nbclient + # nbconvert +nest-asyncio2==1.7.2 + # via ipykernel +notebook-shim==0.2.4 + # via jupyterlab +numba==0.65.1 + # via shap +numpy==2.4.6 + # via + # -r requirements/in/base.in + # contourpy + # matplotlib + # numba + # pandas + # scikit-learn + # scipy + # seaborn + # shap +packaging==26.2 + # via + # ipykernel + # jupyter-events + # jupyter-server + # jupyterlab + # jupyterlab-server + # jupytext + # kaggle + # matplotlib + # nbconvert + # shap +pandas==3.0.3 + # via + # -r requirements/in/base.in + # seaborn + # shap +pandocfilters==1.5.1 + # via nbconvert +parso==0.8.7 + # via jedi +pathspec==1.1.1 + # via mypy +pexpect==4.9.0 + # via ipython +pillow==12.2.0 + # via matplotlib +platformdirs==4.10.0 + # via jupyter-core +prometheus-client==0.25.0 + # via jupyter-server +prompt-toolkit==3.0.52 + # via ipython +protobuf==7.35.1 + # via + # kaggle + # kagglesdk +psutil==7.2.2 + # via + # ipykernel + # ipython +ptyprocess==0.7.0 + # via + # pexpect + # terminado +pure-eval==0.2.3 + # via stack-data +pycparser==3.0 + # via cffi +pygments==2.20.0 + # via + # ipython + # ipython-pygments-lexers + # nbconvert + # rich +pyparsing==3.3.2 + # via matplotlib +python-dateutil==2.9.0.post0 + # via + # arrow + # jupyter-client + # kaggle + # matplotlib + # pandas +python-dotenv==1.2.2 + # via + # -r requirements/in/base.in + # kaggle +python-json-logger==4.1.0 + # via jupyter-events +python-slugify==8.0.4 + # via kaggle +pyyaml==6.0.3 + # via + # jupyter-events + # jupytext +pyzmq==27.1.0 + # via + # ipykernel + # jupyter-client + # jupyter-server +rapidfuzz==3.14.5 + # via -r requirements/in/base.in +referencing==0.37.0 + # via + # jsonschema + # jsonschema-specifications + # jupyter-events +requests==2.34.2 + # via + # -r requirements/in/base.in + # jupyterlab-server + # kaggle + # kagglesdk +rfc3339-validator==0.1.4 + # via + # jsonschema + # jupyter-events +rfc3986-validator==0.1.1 + # via + # jsonschema + # jupyter-events +rfc3987-syntax==1.1.0 + # via jsonschema +rich==15.0.0 + # via typer +rpds-py==2026.5.1 + # via + # jsonschema + # referencing +scikit-learn==1.9.0 + # via + # -r requirements/in/base.in + # shap +scipy==1.17.1 + # via + # -r requirements/in/base.in + # scikit-learn + # shap +seaborn==0.13.2 + # via -r requirements/in/base.in +send2trash==2.1.0 + # via jupyter-server +shap==0.52.0 + # via -r requirements/in/base.in +shellingham==1.5.4 + # via typer +six==1.17.0 + # via + # python-dateutil + # rfc3339-validator +slicer==0.0.8 + # via shap +soupsieve==2.8.4 + # via beautifulsoup4 +stack-data==0.6.3 + # via ipython +terminado==0.18.1 + # via + # jupyter-server + # jupyter-server-terminals +text-unidecode==1.3 + # via python-slugify +threadpoolctl==3.6.0 + # via scikit-learn +tinycss2==1.5.1 + # via bleach +tornado==6.5.7 + # via + # ipykernel + # jupyter-client + # jupyter-server + # jupyterlab + # terminado +tqdm==4.68.2 + # via + # -r requirements/in/base.in + # kaggle + # shap +traitlets==5.15.1 + # via + # ipykernel + # ipython + # jupyter-client + # jupyter-core + # jupyter-events + # jupyter-server + # jupyterlab + # matplotlib-inline + # nbclient + # nbconvert + # nbformat +typer==0.26.7 + # via -r requirements/in/base.in +types-requests==2.33.0.20260518 + # via types-tqdm +types-tqdm==4.68.0.20260608 + # via -r requirements/in/base.in +typing-extensions==4.15.0 + # via + # beautifulsoup4 + # jupyter-client + # mypy +tzdata==2026.2 + # via arrow +uri-template==1.3.0 + # via jsonschema +urllib3==2.7.0 + # via + # kaggle + # requests + # types-requests +wcwidth==0.8.1 + # via prompt-toolkit +webcolors==25.10.0 + # via jsonschema +webencodings==0.5.1 + # via + # bleach + # tinycss2 +websocket-client==1.9.0 + # via jupyter-server + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/requirements/in/base.in b/requirements/in/base.in index fbd21eb..e18309c 100644 --- a/requirements/in/base.in +++ b/requirements/in/base.in @@ -18,49 +18,48 @@ seaborn # Statistical data visualization scipy # Scientific computing algorithms scikit-learn # Machine learning algorithms and utilities shap # Explainable machine learning models - -# Secondary Data Science & Machine Learning -# ----------------------------------------------------------------------------- -statsmodels # Statistical modeling and inference -feature-engine # Feature engineering transformers -category-encoders # Encoding methods for categorical features -xgboost # Extreme gradient boosting models -lightgbm # Fast gradient boosting framework -catboost # Gradient boosting with categorical support -plotly # Interactive charts and dashboards rapidfuzz # -doublemetaphone # # Data Acquisition & Management # ----------------------------------------------------------------------------- python-dotenv # Load environment variables from .env requests # Simple synchronous HTTP client -httpx # Modern HTTP client with async support -tenacity # Retry logic with backoff strategies kaggle # Download datasets from Kaggle duckdb # In-process analytical database engine -pyarrow # Apache Arrow and Parquet support -pandera # Data validation for pandas DataFrames -pyyaml # YAML parsing and serialization - -# Low-Level Engines & Specialized Frameworks -# ----------------------------------------------------------------------------- # Development & Tooling # ----------------------------------------------------------------------------- ipykernel # Jupyter kernel for the environment jupyterlab # Interactive notebook environment -ruff # Fast Python linter and formatter mypy # Static type checking -loguru # Simplified structured logging -rich # Rich terminal output and progress bars tqdm # Progress bars for loops and tasks types-tqdm # stubs for tqdm typer # Build command-line interfaces -# Might use later on ... +############################################################################### +# Other popular, commonly-used packages ... +############################################################################### + +# Secondary Data Science & Machine Learning +# ----------------------------------------------------------------------------- +# statsmodels # Statistical modeling and inference +# feature-engine # Feature engineering transformers +# category-encoders # Encoding methods for categorical features +# xgboost # Extreme gradient boosting models +# lightgbm # Fast gradient boosting framework +# catboost # Gradient boosting with categorical support +# plotly # Interactive charts and dashboards +# doublemetaphone # + + +# Data Acquisition & Management # ----------------------------------------------------------------------------- +# httpx # Modern HTTP client with async support +# tenacity # Retry logic with backoff strategies +# pyarrow # Apache Arrow and Parquet support +# pandera # Data validation for pandas DataFrames +# pyyaml # YAML parsing and serialization # torch # PyTorch deep learning framework # transformers # Hugging Face transformer models # sentence-transformers # Text embeddings and semantic search @@ -70,3 +69,9 @@ typer # Build command-line interfaces # beautifulsoup4 # HTML parsing and web scraping # lxml # Fast XML and HTML processing # mlflow # Experiment tracking and model registry + +# Development & Tooling +# ----------------------------------------------------------------------------- +# ruff # Fast Python linter and formatter +# loguru # Simplified structured logging +# rich # Rich terminal output and progress bars diff --git a/requirements/local.txt b/requirements/local.txt index fa962fa..2b3185f 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -4,25 +4,12 @@ # # pip-compile --output-file=requirements/local.txt requirements/in/local.in # -aiohappyeyeballs==2.6.2 - # via aiohttp -aiohttp==3.14.1 - # via mlflow -aiosignal==1.4.0 - # via aiohttp -alembic==1.18.4 - # via mlflow annotated-doc==0.0.4 - # via - # fastapi - # typer -annotated-types==0.7.0 - # via pydantic + # via typer anyio==4.13.0 # via # httpx # jupyter-server - # starlette appnope==0.1.4 # via ipykernel argon2-cffi==25.1.0 @@ -41,7 +28,6 @@ async-lru==2.3.0 # via jupyterlab attrs==26.1.0 # via - # aiohttp # jsonschema # referencing babel==2.18.0 @@ -50,7 +36,6 @@ bandit==1.9.4 # via -r requirements/in/local.in beautifulsoup4==4.15.0 # via - # -r requirements/in/base.in # nbconvert # types-lxml black==26.5.1 @@ -59,28 +44,17 @@ bleach[css]==6.4.0 # via # kaggle # nbconvert -blinker==1.9.0 - # via flask build==1.5.0 # via pip-tools cachetools==7.1.4 - # via - # mlflow-skinny - # mlflow-tracing - # tox -catboost==1.2.10 - # via -r requirements/in/base.in -category-encoders==2.9.0 - # via -r requirements/in/base.in + # via tox certifi==2026.5.20 # via # httpcore # httpx # requests cffi==2.0.0 - # via - # argon2-cffi-bindings - # cryptography + # via argon2-cffi-bindings cfgv==3.5.0 # via pre-commit charset-normalizer==3.4.7 @@ -88,14 +62,9 @@ charset-normalizer==3.4.7 click==8.4.1 # via # black - # flask - # mlflow-skinny # pip-tools - # uvicorn cloudpickle==3.1.2 - # via - # mlflow-skinny - # shap + # via shap codespell==2.4.2 # via -r requirements/in/local.in colorama==0.4.6 @@ -106,18 +75,10 @@ contourpy==1.3.3 # via matplotlib coverage==7.14.1 # via -r requirements/in/local.in -cryptography==48.0.1 - # via - # google-auth - # mlflow cssselect==1.4.0 # via types-lxml cycler==0.12.1 # via matplotlib -databricks-sdk==0.117.0 - # via - # mlflow-skinny - # mlflow-tracing debugpy==1.8.21 # via ipykernel decorator==5.3.1 @@ -128,18 +89,12 @@ dill==0.4.1 # via pylint distlib==0.4.3 # via virtualenv -docker==7.1.0 - # via mlflow duckdb==1.5.3 # via -r requirements/in/base.in executing==2.2.1 # via stack-data -fastapi==0.137.1 - # via mlflow-skinny fastjsonschema==2.21.2 # via nbformat -feature-engine==1.9.4 - # via -r requirements/in/base.in filelock==3.29.4 # via # python-discovery @@ -151,50 +106,16 @@ flake8==7.3.0 # flake8-coding flake8-coding==1.3.2 # via -r requirements/in/local.in -flask==3.1.3 - # via - # flask-cors - # mlflow -flask-cors==6.0.5 - # via mlflow fonttools==4.63.0 # via matplotlib fqdn==1.5.1 # via jsonschema -frozenlist==1.8.0 - # via - # aiohttp - # aiosignal -gitdb==4.0.12 - # via gitpython -gitpython==3.1.50 - # via mlflow-skinny -google-auth==2.54.0 - # via databricks-sdk -graphene==3.4.3 - # via mlflow -graphql-core==3.2.11 - # via - # graphene - # graphql-relay -graphql-relay==3.2.0 - # via graphene -graphviz==0.21 - # via catboost -gunicorn==26.0.0 - # via mlflow h11==0.16.0 - # via - # httpcore - # uvicorn + # via httpcore httpcore==1.0.9 # via httpx httpx==0.28.1 - # via - # -r requirements/in/base.in - # jupyterlab -huey==3.0.3 - # via mlflow + # via jupyterlab identify==2.6.19 # via pre-commit idna==3.18 @@ -203,9 +124,6 @@ idna==3.18 # httpx # jsonschema # requests - # yarl -importlib-metadata==9.0.0 - # via mlflow-skinny iniconfig==2.3.0 # via pytest ipykernel==7.3.0 @@ -220,13 +138,10 @@ isoduration==20.11.0 # via jsonschema isort==8.0.1 # via pylint -itsdangerous==2.2.0 - # via flask jedi==0.20.0 # via ipython jinja2==3.1.6 # via - # flask # jupyter-server # jupyterlab # jupyterlab-server @@ -288,18 +203,10 @@ lark==1.3.1 # via rfc3987-syntax librt==0.11.0 # via mypy -lightgbm==4.6.0 - # via -r requirements/in/base.in llvmlite==0.47.0 # via # numba # shap -loguru==0.7.3 - # via -r requirements/in/base.in -lxml==6.1.1 - # via -r requirements/in/base.in -mako==1.3.12 - # via alembic markdown-it-py==4.2.0 # via # jupytext @@ -307,16 +214,11 @@ markdown-it-py==4.2.0 # rich markupsafe==3.0.3 # via - # flask # jinja2 - # mako # nbconvert - # werkzeug matplotlib==3.11.0 # via # -r requirements/in/base.in - # catboost - # mlflow # seaborn matplotlib-inline==0.2.2 # via @@ -332,16 +234,6 @@ mdurl==0.1.2 # via markdown-it-py mistune==3.2.1 # via nbconvert -mlflow==3.13.0 - # via -r requirements/in/base.in -mlflow-skinny==3.13.0 - # via mlflow -mlflow-tracing==3.13.0 - # via mlflow -multidict==6.7.1 - # via - # aiohttp - # yarl mypy==2.1.0 # via # -r requirements/in/base.in @@ -351,11 +243,8 @@ mypy-extensions==1.1.0 # -r requirements/in/local.in # black # mypy - # typing-inspect narwhals==2.22.1 - # via - # plotly - # scikit-learn + # via scikit-learn nbclient==0.11.0 # via nbconvert nbconvert==7.17.1 @@ -377,45 +266,19 @@ numba==0.65.1 numpy==2.4.6 # via # -r requirements/in/base.in - # catboost - # category-encoders # contourpy - # feature-engine - # lightgbm # matplotlib - # mlflow # numba # pandas # pandas-stubs - # patsy # scikit-learn # scipy # seaborn # shap - # skops - # statsmodels - # xgboost -opentelemetry-api==1.42.1 - # via - # mlflow-skinny - # mlflow-tracing - # opentelemetry-sdk - # opentelemetry-semantic-conventions -opentelemetry-proto==1.42.1 - # via - # mlflow-skinny - # mlflow-tracing -opentelemetry-sdk==1.42.1 - # via - # mlflow-skinny - # mlflow-tracing -opentelemetry-semantic-conventions==0.63b1 - # via opentelemetry-sdk packaging==26.2 # via # black # build - # gunicorn # ipykernel # jupyter-events # jupyter-server @@ -424,32 +287,19 @@ packaging==26.2 # jupytext # kaggle # matplotlib - # mlflow-skinny - # mlflow-tracing # nbconvert - # pandera - # plotly # pyproject-api # pytest # shap - # skops - # statsmodels # tox # wheel pandas==2.3.3 # via # -r requirements/in/base.in - # catboost - # category-encoders - # feature-engine - # mlflow # seaborn # shap - # statsmodels pandas-stubs==3.0.3.260530 # via -r requirements/in/local.in -pandera==0.31.1 - # via -r requirements/in/base.in pandocfilters==1.5.1 # via nbconvert parso==0.8.7 @@ -458,10 +308,6 @@ pathspec==1.1.1 # via # black # mypy -patsy==1.0.2 - # via - # category-encoders - # statsmodels pexpect==4.9.0 # via ipython pillow==12.2.0 @@ -476,34 +322,20 @@ platformdirs==4.10.0 # python-discovery # tox # virtualenv -plotly==6.8.0 - # via - # -r requirements/in/base.in - # catboost pluggy==1.6.0 # via # pytest # tox pre-commit==4.6.0 # via -r requirements/in/local.in -prettytable==3.17.0 - # via skops prometheus-client==0.25.0 # via jupyter-server prompt-toolkit==3.0.52 # via ipython -propcache==0.5.2 - # via - # aiohttp - # yarl protobuf==6.33.6 # via - # databricks-sdk # kaggle # kagglesdk - # mlflow-skinny - # mlflow-tracing - # opentelemetry-proto psutil==7.2.2 # via # ipykernel @@ -514,26 +346,10 @@ ptyprocess==0.7.0 # terminado pure-eval==0.2.3 # via stack-data -pyarrow==24.0.0 - # via - # -r requirements/in/base.in - # mlflow -pyasn1==0.6.3 - # via pyasn1-modules -pyasn1-modules==0.4.2 - # via google-auth pycodestyle==2.14.0 # via flake8 pycparser==3.0 # via cffi -pydantic==2.13.4 - # via - # fastapi - # mlflow-skinny - # mlflow-tracing - # pandera -pydantic-core==2.46.4 - # via pydantic pyflakes==3.4.0 # via flake8 pygments==2.20.0 @@ -569,7 +385,6 @@ pytest-mock==3.15.1 python-dateutil==2.9.0.post0 # via # arrow - # graphene # jupyter-client # kaggle # matplotlib @@ -582,7 +397,6 @@ python-dotenv==1.2.2 # via # -r requirements/in/base.in # kaggle - # mlflow-skinny python-json-logger==4.1.0 # via jupyter-events python-slugify==8.0.4 @@ -593,17 +407,17 @@ pytz==2026.2 # via pandas pyyaml==6.0.3 # via - # -r requirements/in/base.in # bandit # jupyter-events # jupytext - # mlflow-skinny # pre-commit pyzmq==27.1.0 # via # ipykernel # jupyter-client # jupyter-server +rapidfuzz==3.14.5 + # via -r requirements/in/base.in referencing==0.37.0 # via # jsonschema @@ -612,12 +426,9 @@ referencing==0.37.0 requests==2.34.2 # via # -r requirements/in/base.in - # databricks-sdk - # docker # jupyterlab-server # kaggle # kagglesdk - # mlflow-skinny rfc3339-validator==0.1.4 # via # jsonschema @@ -630,36 +441,21 @@ rfc3987-syntax==1.1.0 # via jsonschema rich==15.0.0 # via - # -r requirements/in/base.in # bandit # typer rpds-py==2026.5.1 # via # jsonschema # referencing -ruff==0.15.17 - # via -r requirements/in/base.in scikit-learn==1.9.0 # via # -r requirements/in/base.in - # category-encoders - # feature-engine - # mlflow # shap - # skops scipy==1.17.1 # via # -r requirements/in/base.in - # catboost - # category-encoders - # feature-engine - # lightgbm - # mlflow # scikit-learn # shap - # skops - # statsmodels - # xgboost seaborn==0.13.2 # via -r requirements/in/base.in send2trash==2.1.0 @@ -670,38 +466,16 @@ shellingham==1.5.4 # via typer six==1.17.0 # via - # catboost # python-dateutil # rfc3339-validator -skops==0.14.0 - # via mlflow slicer==0.0.8 # via shap -smmap==5.0.3 - # via gitdb soupsieve==2.8.4 # via beautifulsoup4 -sqlalchemy==2.0.51 - # via - # alembic - # mlflow -sqlparse==0.5.5 - # via mlflow-skinny stack-data==0.6.3 # via ipython -starlette==1.3.1 - # via - # fastapi - # mlflow-skinny -statsmodels==0.14.6 - # via - # -r requirements/in/base.in - # category-encoders - # feature-engine stevedore==5.8.0 # via bandit -tenacity==9.1.4 - # via -r requirements/in/base.in terminado==0.18.1 # via # jupyter-server @@ -743,8 +517,6 @@ traitlets==5.15.1 # nbclient # nbconvert # nbformat -typeguard==4.5.2 - # via pandera typer==0.26.7 # via -r requirements/in/base.in types-cachetools==7.0.0.20260518 @@ -754,35 +526,19 @@ types-html5lib==1.1.11.20260518 types-lxml==2026.2.16 # via -r requirements/in/local.in types-requests==2.33.0.20260518 - # via -r requirements/in/local.in + # via + # -r requirements/in/local.in + # types-tqdm +types-tqdm==4.68.0.20260608 + # via -r requirements/in/base.in types-webencodings==0.5.0.20260408 # via types-html5lib typing-extensions==4.15.0 # via - # alembic # beautifulsoup4 - # fastapi - # graphene # jupyter-client - # mlflow-skinny # mypy - # opentelemetry-api - # opentelemetry-sdk - # opentelemetry-semantic-conventions - # pandera - # pydantic - # pydantic-core - # sqlalchemy - # typeguard # types-lxml - # typing-inspect - # typing-inspection -typing-inspect==0.9.0 - # via pandera -typing-inspection==0.4.2 - # via - # fastapi - # pydantic tzdata==2026.2 # via # arrow @@ -791,21 +547,15 @@ uri-template==1.3.0 # via jsonschema urllib3==2.7.0 # via - # databricks-sdk - # docker # kaggle # requests # types-requests -uvicorn==0.49.0 - # via mlflow-skinny virtualenv==21.5.0 # via # pre-commit # tox wcwidth==0.8.1 - # via - # prettytable - # prompt-toolkit + # via prompt-toolkit webcolors==25.10.0 # via jsonschema webencodings==0.5.1 @@ -814,18 +564,8 @@ webencodings==0.5.1 # tinycss2 websocket-client==1.9.0 # via jupyter-server -werkzeug==3.1.8 - # via - # flask - # flask-cors wheel==0.47.0 # via pip-tools -xgboost==3.2.0 - # via -r requirements/in/base.in -yarl==1.24.2 - # via aiohttp -zipp==4.1.0 - # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/setup/linux/setup.sh b/setup/linux/setup.sh new file mode 100644 index 0000000..fc0e2c3 --- /dev/null +++ b/setup/linux/setup.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +############################################################################### +# Author: Lawrence McDaniel https://lawrencemcdaniel.com +# Date: 2026-06-26 +# +# setup.sh — Debian/Ubuntu Environment Setup for netflix-writers Project +# +# This script verifies and installs required development tools for the +# netflix-writers project on Debian and Ubuntu using Homebrew (Linuxbrew). +# +# Usage: +# bash setup.sh +# +# Requirements: +# - Debian 12+ or Ubuntu 22.04+ +# - sudo privileges +# - Internet connectivity +# +# Actions performed: +# - Installs prerequisite system packages +# - Verifies Homebrew is installed +# - Installs development dependencies via Homebrew +# +# Exit codes: +# 0 — Success +# 1 — Missing prerequisite or failed installation +# +############################################################################### + +set -euo pipefail + +echo "[INFO] Verifying operating system..." + +if [[ ! -f /etc/os-release ]]; then + echo "[ERROR] Unable to determine operating system." + exit 1 +fi + +source /etc/os-release + +if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then + echo "[ERROR] Unsupported operating system: ${PRETTY_NAME}" + echo "This script supports Debian and Ubuntu only." + exit 1 +fi + +echo "[OK] Detected ${PRETTY_NAME}" + +echo "" +echo "[INFO] Installing Linux prerequisites..." + +sudo apt-get update + +sudo apt-get install -y \ + build-essential \ + procps \ + curl \ + file \ + git + +echo "[OK] Linux prerequisites installed." + +echo "" +echo "[INFO] Verifying Homebrew..." + +if ! command -v brew >/dev/null 2>&1; then + echo "[ERROR] Homebrew is not installed." + echo "" + echo "Install Homebrew by running:" + echo "" + echo ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' + echo "" + echo "Then add Homebrew to your shell environment:" + echo "" + echo ' eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' + echo "" + echo "Afterward, re-run this script." + exit 1 +fi + +echo "[OK] Homebrew is installed." + +# +# Ensure brew is available in the current shell +# +eval "$(brew shellenv)" + +echo "" +echo "[INFO] Updating Homebrew..." + +brew update + +echo "" +echo "[INFO] Installing development dependencies..." + +brew install python@3.13 + +brew install \ + sqlite \ + readline \ + xz \ + ca-certificates \ + zstd \ + openblas \ + libffi \ + openssl@3 \ + libxml2 \ + libxslt \ + geos \ + jq + +echo "" +echo "==============================================" +echo " Installed Packages Summary" +echo "==============================================" + +echo "" + +echo "Homebrew:" +brew --version | head -n 1 + +echo "" + +echo "Python:" +python3 --version + +echo "" + +echo "Other Homebrew packages:" +brew list --versions \ + sqlite \ + readline \ + xz \ + ca-certificates \ + zstd \ + openblas \ + libffi \ + openssl@3 \ + libxml2 \ + libxslt \ + geos \ + jq + +echo "" +echo "==============================================" +echo "Setup complete." +echo "" +echo "Next steps:" +echo "" +echo " python3 -m venv .venv" +echo " source .venv/bin/activate" +echo " python -m pip install --upgrade pip" +echo " pip install -r requirements.txt" +echo "" diff --git a/setup/macos/setup.sh b/setup/macos/setup.sh index 8b49377..1945045 100755 --- a/setup/macos/setup.sh +++ b/setup/macos/setup.sh @@ -6,7 +6,7 @@ # setup.sh — macOS Environment Setup for netflix-writers Project # # This script verifies and installs required development tools for the netflix-writers project on macOS. -# It checks for Xcode Command Line Tools, Homebrew, and Docker Desktop, and installs essential +# It checks for Xcode Command Line Tools, and Homebrew and installs essential # packages and libraries via Homebrew. # # Usage: @@ -17,9 +17,8 @@ # - Administrator privileges (for some installations and symlinks) # # Actions performed: -# - Verifies Xcode Command Line Tools, Homebrew, Docker Desktop -# - Ensures docker-compose symlink exists -# - Installs development dependencies (gcc, python, node, nvm, etc.) +# - Verifies Xcode Command Line Tools, Homebrew +# - Installs development dependencies (python, etc.) # # Exit codes: # 0 — Success @@ -28,7 +27,13 @@ ############################################################################### # open "macappstore://itunes.apple.com/app/id497799835" -xcode-select --install +echo "Xcode Command Line Tools:" +if ! xcode-select -p &>/dev/null; then + echo "[INFO] Installing Xcode Command Line Tools..." + xcode-select --install + exit 1 +fi +echo -e "\033[0;32m[OK]\033[0m Xcode Command Line Tools are installed." # Verify prerequisites: Xcode, Homebrew, Docker Desktop @@ -53,31 +58,21 @@ else fi brew update -brew upgrade -brew install gcc python@3.13 node nvm -brew install blis zlib zstd openblas libffi openssl libxml2 libxslt geos jq k9s +brew install python@3.13 +brew install sqlite readline xz ca-certificates zlib zstd openblas libffi openssl@3 libxml2 libxslt geos jq echo "" echo "==============================================" echo " Installed Packages Summary" echo "==============================================" -echo "Xcode Command Line Tools:" -xcode-select -v - echo "Homebrew:" brew --version | head -n 1 -echo "gcc:" -gcc --version | head -n 1 - echo "python:" python3 --version -echo "node:" -node --version - echo "Other Homebrew packages:" -brew list --versions blis zlib zstd openblas libffi openssl libxml2 libxslt geos jq k9s +brew list --versions sqlite readline xz ca-certificates zlib zstd openblas libffi openssl@3 libxml2 libxslt geos jq echo "==============================================" diff --git a/setup/windows/setup.ps1 b/setup/windows/setup.ps1 new file mode 100644 index 0000000..61cee86 --- /dev/null +++ b/setup/windows/setup.ps1 @@ -0,0 +1,112 @@ +############################################################################### +# Author: Lawrence McDaniel https://lawrencemcdaniel.com +# Date: 2026-06-26 +# +# setup.ps1 — Windows Environment Setup for netflix-writers Project +# +# This script verifies and installs required development tools for the +# netflix-writers project on Windows. +# +# Usage: +# powershell -ExecutionPolicy Bypass -File setup.ps1 +# +# Requirements: +# - Windows 10 or Windows 11 +# - Administrator privileges +# +# Actions performed: +# - Verifies Winget is available +# - Installs Python 3.13 +# - Installs Git (optional but recommended) +# - Displays installed versions +# +# Exit codes: +# 0 — Success +# 1 — Missing prerequisite or failed installation +# +############################################################################### + +Write-Host "[INFO] Verifying required tools..." -ForegroundColor Cyan + +# +# Verify Winget +# +if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { + Write-Host "[ERROR] Winget is not installed." -ForegroundColor Red + Write-Host "Please install the Microsoft App Installer from the Microsoft Store." + exit 1 +} + +Write-Host "[OK] Winget is installed." -ForegroundColor Green + +# +# Install Python 3.13 +# +Write-Host "" +Write-Host "[INFO] Installing Python 3.13..." -ForegroundColor Cyan + +winget install ` + --id Python.Python.3.13 ` + --exact ` + --accept-package-agreements ` + --accept-source-agreements + +# +# Install Git (recommended) +# +Write-Host "" +Write-Host "[INFO] Installing Git..." -ForegroundColor Cyan + +winget install ` + --id Git.Git ` + --exact ` + --accept-package-agreements ` + --accept-source-agreements + +# +# Refresh PATH for this session if possible +# +$env:Path += ";$env:LOCALAPPDATA\Programs\Python\Python313\" +$env:Path += ";$env:LOCALAPPDATA\Programs\Python\Python313\Scripts\" + +Write-Host "" +Write-Host "==============================================" +Write-Host " Installed Packages Summary" +Write-Host "==============================================" + +Write-Host "" +Write-Host "Winget:" +winget --version + +Write-Host "" +Write-Host "Python:" +try { + python --version +} +catch { + Write-Host "Python was installed but is not yet available in this session." + Write-Host "Please open a new PowerShell window and run:" + Write-Host " python --version" +} + +Write-Host "" +Write-Host "Git:" +try { + git --version +} +catch { + Write-Host "Git was installed but is not yet available in this session." + Write-Host "Please open a new PowerShell window and run:" + Write-Host " git --version" +} + +Write-Host "" +Write-Host "==============================================" +Write-Host "Setup complete." +Write-Host "Next steps:" +Write-Host "" +Write-Host " python -m venv .venv" +Write-Host " .\.venv\Scripts\Activate.ps1" +Write-Host " python -m pip install --upgrade pip" +Write-Host " pip install -r requirements.txt" +Write-Host ""