diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 8bb26a7c1..be187a237 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -33,9 +33,10 @@ jobs: cancel-in-progress: true defaults: run: - shell: bash -l {0} + # bash -el required so conda activation persists (README: IMPORTANT) + shell: bash -el {0} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: conda-incubator/setup-miniconda@v3 with: @@ -45,6 +46,7 @@ jobs: activate-environment: osl-web auto-update-conda: true conda-solver: libmamba + conda-remove-defaults: true - name: Install dependencies run: | @@ -56,7 +58,7 @@ jobs: if: ${{ github.event_name == 'pull_request' }} run: | pre-commit install - pre-commit run --all-file --verbose + pre-commit run --all-files --verbose - name: Build the book run: | diff --git a/.makim.yaml b/.makim.yaml index 04a442dd4..16e126023 100644 --- a/.makim.yaml +++ b/.makim.yaml @@ -5,17 +5,28 @@ groups: help: pre-build step backend: bash run: | + set -e mkdir -p build - # Directory to search for .ipynb files + # Directory to search for blog sources export SEARCH_DIR="pages/blog" - # Find all .ipynb files, excluding .ipynb_checkpoints, - # and convert them to Markdown named 'index.md' + # 1) Convert legacy .ipynb posts to Markdown (for now) + # This keeps existing notebook-based posts working while we + # migrate everything to Quarto. find "$SEARCH_DIR" -path "*/.ipynb_checkpoints/*" -prune -o -name \ "*.ipynb" -exec sh -c \ 'jupyter nbconvert --to markdown --template=theme/custom-markdown.tpl --output-dir "$(dirname "$0")" --output "index" "$0"' {} \; - # remove console colors from md files + # 2) Convert .qmd blog posts to Markdown using Quarto + # Run quarto render from inside each blog folder (Quarto rejects --output path). + if command -v quarto >/dev/null 2>&1; then + find "$SEARCH_DIR" -name "*.qmd" -exec sh -c \ + 'dir=$(dirname "$0"); base=$(basename "$0" .qmd); (cd "$dir" && quarto render "$base.qmd" --to gfm)' {} \; + else + echo "[WW] Quarto is not available on PATH; .qmd files will not be rendered." + fi + + # 3) Remove console colors from generated md files find "$SEARCH_DIR" -name \ "index.md" -exec sh -c \ 'cat "$(dirname "$0")/index.md" | python scripts/clean-output.py > "$(dirname "$0")/temp_index.md" && mv "$(dirname "$0")/temp_index.md" "$(dirname "$0")/index.md"' {} \; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a0dbe1abf..4f0004a3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,15 +42,13 @@ dependencies and activate it. ### Creating a New Blog Post -1. **Prepare the Blog Post:** +1. **Prepare the Blog Post (Quarto-first workflow):** - Navigate to `pages/blog` and create a new folder with a slugified version of your blog post's title. Use [https://slugify.online/](https://slugify.online/) to generate a slug. - - Inside this folder, create your blog post file: - - For Markdown: `index.md` - - For Jupyter Notebooks: `index.ipynb` (use Jupyter Lab to create this - directly) + - Inside this folder, create your blog post as a **Quarto document**: + - `index.qmd` 2. **Include a Header Image:** - Place a header image (either `header.png` or `header.jpg`) in your blog @@ -58,8 +56,8 @@ dependencies and activate it. ### Metadata and Formatting -- **Markdown Posts:** Add a metadata block at the beginning of your `index.md` - file: +- **Quarto (`.qmd`) Posts:** Add a metadata block at the beginning of your + `index.qmd` file: ```markdown --- @@ -75,20 +73,22 @@ dependencies and activate it. --- ``` -- **Jupyter Notebook Posts:** The first cell of your `index.ipynb` should be in - `raw` mode containing the same metadata as above. + The body of the file uses standard Markdown plus any Quarto features you need + (code chunks, figures, etc.). During the build, `makim pages.build` will + render `index.qmd` to `index.md` using Quarto so that MkDocs can consume the + generated Markdown. 3. **Building and Viewing:** - - If using a Jupyter Notebook, run `makim pages.build` to convert the - notebook into a Markdown file (`index.md`). - - Add the generated `index.md` to your repository as it will be used to - render the webpage. + - Run `makim pages.build` to render `index.qmd` to `index.md` and build the + site. The generated `index.md` is used to render the webpage. ## Final Steps Before submitting your blog post: - Ensure all files are added to your repository. +- For new or migrated posts, confirm that `index.qmd` exists and that + `makim pages.build` successfully generates the corresponding `index.md`. - Submit a pull request to the main `opensciencelabs.github.io` repository for review. diff --git a/conda/dev.yaml b/conda/dev.yaml index afc455dbb..72d8cb39e 100644 --- a/conda/dev.yaml +++ b/conda/dev.yaml @@ -7,3 +7,4 @@ dependencies: - pip - poetry - nodejs + - quarto diff --git a/pages/blog/artbox-what-is-it-how-to-collaborete/index.ipynb b/pages/blog/artbox-what-is-it-how-to-collaborete/index.ipynb deleted file mode 100644 index 1827c391e..000000000 --- a/pages/blog/artbox-what-is-it-how-to-collaborete/index.ipynb +++ /dev/null @@ -1,439 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "id": "7e609acc", - "metadata": {}, - "source": [ - "---\n", - "title: \"ArtBox: What is it and how to collaborate?\"\n", - "slug: artbox-what-is-it-how-to-collaborete\n", - "date: 2024-04-02\n", - "authors: [\"Daniela Iglesias Rocabado\"]\n", - "tags: [open-source, art, python, multimedia processing]\n", - "categories: [python]\n", - "description: |\n", - " ArtBox is a tool set for handling multimedia files with a bunch of useful functions.\n", - "thumbnail: \"/header.jpg\"\n", - "template: \"blog-post.html\"\n", - "---" - ] - }, - { - "cell_type": "markdown", - "id": "24d656a5-38e6-4ed0-bf15-b8854b96b475", - "metadata": {}, - "source": [ - "# How to use it?\n", - "\n", - "## What is ArtBox?\n", - "\n", - "ArtBox is a versatile tool set designed for efficient multimedia file handling, offering a range of valuable functions to enhance your multimedia processing experience.\n", - "\n", - "\n", - "Key features of ArtBox include capabilities for text-to-audio conversion, YouTube video downloading, musical composition from notes, audio removal from video clips, audio extraction, and merging audio with video files. These functionalities position ArtBox as a pivotal tool for multimedia enthusiasts, content creators, and anyone in need of efficient multimedia processing solutions.\n" - ] - }, - { - "cell_type": "markdown", - "id": "ebda1853", - "metadata": {}, - "source": [ - "### Installation\n", - "\n", - "ArtBox relies on certain dependencies that may not function optimally on your machine. To ensure a smooth installation process, it is recommended to create a conda/mamba environment and install ArtBox within that environment.\n", - "\n", - "```bash\n", - "$ mamba create --name artbox \"python>=3.8.1,<3.12\" pygobject pip\n", - "```\n", - "\n", - "The command is creating a conda environment named \"artbox\" with Python version 3.8.1 or later, and includes the pygobject and pip packages in the environment. This is useful for setting up an isolated environment for a specific project or application, ensuring compatibility and reproducibility of the software stack.\n", - "\n", - "```bash\n", - "$ conda activate artbox\n", - "```\n", - "\n", - "To prevent dependency conflicts, please install the numpy library using the following command:\n", - "\n", - "```bash\n", - "$ pip install \"numpy>=1.20\"\n", - "```\n", - "\n", - "The `conda activate artbox` command is used to activate the \"artbox\" conda environment, ensuring that subsequent commands or scripts run within this isolated environment. Activation modifies the system's `PATH` to prioritize the \"artbox\" environment, allowing for the use of specific Python versions and packages associated with the project, thus maintaining a clean and reproducible development or execution environment." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "1ed9bbc9-2c65-4377-86a1-a4099944b065", - "metadata": {}, - "outputs": [], - "source": [ - "$ !mamba install -q -y -c conda-forge pygobject pip" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "df3d4252-6fc3-46c0-93a2-009b29a06950", - "metadata": {}, - "outputs": [], - "source": [ - "$ !pip install -q artbox" - ] - }, - { - "cell_type": "markdown", - "id": "70667c02", - "metadata": {}, - "source": [ - "The `pip install artbox` command is used to install the Python package named \"artbox\" using the pip package manager. This command fetches the \"artbox\" package from the Python Package Index (PyPI) and installs it into the currently active Python environment. The `pip install` command is commonly used to add external packages or libraries to a Python environment, expanding its functionality for a particular project or application." - ] - }, - { - "cell_type": "markdown", - "id": "4a4eae64-6196-4f02-8f6a-bc591f975bd5", - "metadata": {}, - "source": [ - "## Examples of Artbox usage.\n", - "For the following examples, create the a temporary folder for artbox:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4dd7d934", - "metadata": {}, - "outputs": [], - "source": [ - "$ mkdir /tmp/artbox" - ] - }, - { - "cell_type": "markdown", - "id": "11d1f56e-0465-47d4-8e9f-3e4b4c517159", - "metadata": {}, - "source": [ - "### Convert text to audio\n", - "\n", - "By default, the `artbox voice` uses\n", - "[`edge-tts`](https://pypi.org/project/edge-tts/) engine, but you can also\n", - "specify [`gtts`](https://github.com/pndurette/gTTS) with the flag\n", - "`--engine gtts`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "84661a75", - "metadata": {}, - "outputs": [], - "source": [ - "$ echo \"Are you ready to join Link and Zelda in fighting off this unprecedented threat to Hyrule?\" > /tmp/artbox/text.md\n", - "$ artbox speech from-text \\\n", - " --title artbox \\\n", - " --input-path /tmp/artbox/text.md \\\n", - " --output-path /tmp/artbox/speech.mp3 \\\n", - " --engine edge-tts" - ] - }, - { - "cell_type": "markdown", - "id": "f3b10b2b", - "metadata": {}, - "source": [ - "If you need to generate the audio for different language, you can use the flag\n", - "`--lang`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "357a98eb", - "metadata": {}, - "outputs": [], - "source": [ - "$ echo \"Bom dia, mundo!\" > /tmp/artbox/text.md\n", - "$ artbox speech from-text \\\n", - " --title artbox \\\n", - " --input-path /tmp/artbox/text.md \\\n", - " --output-path /tmp/artbox/speech.mp3 \\\n", - " --lang pt" - ] - }, - { - "cell_type": "markdown", - "id": "ec19fb71", - "metadata": {}, - "source": [ - "If you are using `edge-tts` engine (the default one), you can also specify the\n", - "locale for the language, for example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6968f686", - "metadata": {}, - "outputs": [], - "source": [ - "$ echo \"Are you ready to join Link and Zelda in fighting off this unprecedented threat to Hyrule?\" > /tmp/artbox/text.md\n", - "$ artbox speech from-text \\\n", - " --title artbox \\\n", - " --input-path /tmp/artbox/text.md \\\n", - " --output-path /tmp/artbox/speech.mp3 \\\n", - " --engine edge-tts \\\n", - " --lang en-IN" - ] - }, - { - "cell_type": "markdown", - "id": "35887f10", - "metadata": {}, - "source": [ - "Additionally, if you are using edge-tts, you can specify `--rate`, `--volume`, and `--pitch`, for example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8eafb782", - "metadata": {}, - "outputs": [], - "source": [ - "$ echo \"Do you want some coffee?\" > /tmp/artbox/text.md\n", - "$ artbox speech from-text \\\n", - " --title artbox \\\n", - " --input-path /tmp/artbox/text.md \\\n", - " --output-path /tmp/artbox/speech.mp3 \\\n", - " --engine edge-tts \\\n", - " --lang en \\\n", - " --rate +10% \\\n", - " --volume -10% \\\n", - " --pitch -5Hz" - ] - }, - { - "cell_type": "markdown", - "id": "1764b8ec", - "metadata": {}, - "source": [ - "### Convert audio to text\n", - "ArtBox uses `speechrecognition` to convert from audio to text. Currently, ArtBox just support the google engine.\n", - "\n", - "For this example, let's first create our audio:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "88c5fe25", - "metadata": {}, - "outputs": [], - "source": [ - "$ echo \"Are you ready to join Link and Zelda in fighting off this unprecedented threat to Hyrule?\" > /tmp/artbox/text.md\n", - "$ artbox speech from-text \\\n", - " --title artbox \\\n", - " --input-path /tmp/artbox/text.md \\\n", - " --output-path /tmp/artbox/speech.mp3 \\\n", - " --engine edge-tts" - ] - }, - { - "cell_type": "markdown", - "id": "e41a2821", - "metadata": {}, - "source": [ - "Now we can convert it back to text:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c2a45b8a", - "metadata": {}, - "outputs": [], - "source": [ - "$ artbox speech to-text \\\n", - " --input-path /tmp/artbox/speech.mp3 \\\n", - " --output-path /tmp/artbox/text-from-speech.md \\\n", - " --lang en" - ] - }, - { - "cell_type": "markdown", - "id": "828f8b46", - "metadata": {}, - "source": [ - "### Download a youtube video\n", - "\n", - "If you want to download videos from the youtube, you can use the following\n", - "command:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5012eb6b", - "metadata": {}, - "outputs": [], - "source": [ - "$ artbox youtube download \\\n", - " --url https://www.youtube.com/watch?v=zw47_q9wbBE \\\n", - " --output-path /tmp/artbox/" - ] - }, - { - "cell_type": "markdown", - "id": "83199889", - "metadata": {}, - "source": [ - "The command above downloads the video using a random resolution. If you want a specific resolution, use the flat `--resolution`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "06146a5c", - "metadata": {}, - "outputs": [], - "source": [ - "$ artbox youtube download \\\n", - " --url https://www.youtube.com/watch?v=zw47_q9wbBE \\\n", - " --output-path /tmp/artbox/ \\\n", - " --resolution 360p" - ] - }, - { - "cell_type": "markdown", - "id": "fe09ce73", - "metadata": {}, - "source": [ - "### Remove the audio from a video\n", - "\n", - "First, download the youtube video `https://www.youtube.com/watch?v=zw47_q9wbBE`, as explained before.\n", - "\n", - "Next, run the following command:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3effd893", - "metadata": {}, - "outputs": [], - "source": [ - "$ artbox video remove-audio \\\n", - " --input-path \"/tmp/artbox/The Legend of Zelda Breath of the Wild - Nintendo Switch Presentation 2017 Trailer.mp4\" \\\n", - " --output-path /tmp/artbox/botw.mp4" - ] - }, - { - "cell_type": "markdown", - "id": "605d4b77", - "metadata": {}, - "source": [ - "### Extract the audio from a video\n", - "\n", - "First, download the youtube video `https://www.youtube.com/watch?v=zw47_q9wbBE`, \n", - "as explained before.\n", - "\n", - "Next, run the following command:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "22980244", - "metadata": {}, - "outputs": [], - "source": [ - "$ artbox video extract-audio \\\n", - " --input-path \"/tmp/artbox/The Legend of Zelda Breath of the Wild - Nintendo Switch Presentation 2017 Trailer.mp4\" \\\n", - " --output-path /tmp/artbox/botw-audio.mp3" - ] - }, - { - "cell_type": "markdown", - "id": "2f032912", - "metadata": {}, - "source": [ - "### Combine audio and video files\n", - "\n", - "First, execute the previous steps:\n", - "\n", - "- Download a youtube video\n", - "- Remove the audio from a video\n", - "- Extract the audio from a video\n", - "\n", - "Next, run the following command:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "159d2762", - "metadata": {}, - "outputs": [], - "source": [ - "$ artbox video combine-video-and-audio \\\n", - " --video-path /tmp/artbox/botw.mp4 \\\n", - " --audio-path /tmp/artbox/botw-audio.mp3 \\\n", - " --output-path /tmp/artbox/botw-combined.mp4" - ] - }, - { - "cell_type": "markdown", - "id": "eee13338", - "metadata": {}, - "source": [ - "## Additional dependencies\n", - "\n", - "If you want to use Python to play your audio files, you can install `playsound`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "03b8a0d7", - "metadata": {}, - "outputs": [], - "source": [ - "$ pip wheel --use-pep517 \"playsound (==1.3.0)\"" - ] - }, - { - "cell_type": "markdown", - "id": "a99f84ca", - "metadata": {}, - "source": [ - "### Demo Video\n", - "\n", - "For a better explanation of the facilities and usage, please watch to the following video.\n", - "\n", - "" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pages/blog/artbox-what-is-it-how-to-collaborete/index.qmd b/pages/blog/artbox-what-is-it-how-to-collaborete/index.qmd new file mode 100644 index 000000000..63fc151d5 --- /dev/null +++ b/pages/blog/artbox-what-is-it-how-to-collaborete/index.qmd @@ -0,0 +1,230 @@ +--- +title: "ArtBox: What is it and how to collaborate?" +slug: artbox-what-is-it-how-to-collaborete +date: 2024-04-02 +authors: ["Daniela Iglesias Rocabado"] +tags: [open-source, art, python, multimedia processing] +categories: [python] +description: | + ArtBox is a tool set for handling multimedia files with a bunch of useful functions. +thumbnail: "/header.jpg" +template: "blog-post.html" +--- +# How to use it? + +## What is ArtBox? + +ArtBox is a versatile tool set designed for efficient multimedia file handling, offering a range of valuable functions to enhance your multimedia processing experience. + + +Key features of ArtBox include capabilities for text-to-audio conversion, YouTube video downloading, musical composition from notes, audio removal from video clips, audio extraction, and merging audio with video files. These functionalities position ArtBox as a pivotal tool for multimedia enthusiasts, content creators, and anyone in need of efficient multimedia processing solutions. + + +### Installation + +ArtBox relies on certain dependencies that may not function optimally on your machine. To ensure a smooth installation process, it is recommended to create a conda/mamba environment and install ArtBox within that environment. + +```bash +$ mamba create --name artbox "python>=3.8.1,<3.12" pygobject pip +``` + +The command is creating a conda environment named "artbox" with Python version 3.8.1 or later, and includes the pygobject and pip packages in the environment. This is useful for setting up an isolated environment for a specific project or application, ensuring compatibility and reproducibility of the software stack. + +```bash +$ conda activate artbox +``` + +To prevent dependency conflicts, please install the numpy library using the following command: + +```bash +$ pip install "numpy>=1.20" +``` + +The `conda activate artbox` command is used to activate the "artbox" conda environment, ensuring that subsequent commands or scripts run within this isolated environment. Activation modifies the system's `PATH` to prioritize the "artbox" environment, allowing for the use of specific Python versions and packages associated with the project, thus maintaining a clean and reproducible development or execution environment. + + +```python +$ !mamba install -q -y -c conda-forge pygobject pip +``` + + +```python +$ !pip install -q artbox +``` + +The `pip install artbox` command is used to install the Python package named "artbox" using the pip package manager. This command fetches the "artbox" package from the Python Package Index (PyPI) and installs it into the currently active Python environment. The `pip install` command is commonly used to add external packages or libraries to a Python environment, expanding its functionality for a particular project or application. + +## Examples of Artbox usage. +For the following examples, create the a temporary folder for artbox: + + +```python +$ mkdir /tmp/artbox +``` + +### Convert text to audio + +By default, the `artbox voice` uses +[`edge-tts`](https://pypi.org/project/edge-tts/) engine, but you can also +specify [`gtts`](https://github.com/pndurette/gTTS) with the flag +`--engine gtts`. + + +```python +$ echo "Are you ready to join Link and Zelda in fighting off this unprecedented threat to Hyrule?" > /tmp/artbox/text.md +$ artbox speech from-text \ + --title artbox \ + --input-path /tmp/artbox/text.md \ + --output-path /tmp/artbox/speech.mp3 \ + --engine edge-tts +``` + +If you need to generate the audio for different language, you can use the flag +`--lang`: + + +```python +$ echo "Bom dia, mundo!" > /tmp/artbox/text.md +$ artbox speech from-text \ + --title artbox \ + --input-path /tmp/artbox/text.md \ + --output-path /tmp/artbox/speech.mp3 \ + --lang pt +``` + +If you are using `edge-tts` engine (the default one), you can also specify the +locale for the language, for example: + + +```python +$ echo "Are you ready to join Link and Zelda in fighting off this unprecedented threat to Hyrule?" > /tmp/artbox/text.md +$ artbox speech from-text \ + --title artbox \ + --input-path /tmp/artbox/text.md \ + --output-path /tmp/artbox/speech.mp3 \ + --engine edge-tts \ + --lang en-IN +``` + +Additionally, if you are using edge-tts, you can specify `--rate`, `--volume`, and `--pitch`, for example: + + +```python +$ echo "Do you want some coffee?" > /tmp/artbox/text.md +$ artbox speech from-text \ + --title artbox \ + --input-path /tmp/artbox/text.md \ + --output-path /tmp/artbox/speech.mp3 \ + --engine edge-tts \ + --lang en \ + --rate +10% \ + --volume -10% \ + --pitch -5Hz +``` + +### Convert audio to text +ArtBox uses `speechrecognition` to convert from audio to text. Currently, ArtBox just support the google engine. + +For this example, let's first create our audio: + + +```python +$ echo "Are you ready to join Link and Zelda in fighting off this unprecedented threat to Hyrule?" > /tmp/artbox/text.md +$ artbox speech from-text \ + --title artbox \ + --input-path /tmp/artbox/text.md \ + --output-path /tmp/artbox/speech.mp3 \ + --engine edge-tts +``` + +Now we can convert it back to text: + + +```python +$ artbox speech to-text \ + --input-path /tmp/artbox/speech.mp3 \ + --output-path /tmp/artbox/text-from-speech.md \ + --lang en +``` + +### Download a youtube video + +If you want to download videos from the youtube, you can use the following +command: + + +```python +$ artbox youtube download \ + --url https://www.youtube.com/watch?v=zw47_q9wbBE \ + --output-path /tmp/artbox/ +``` + +The command above downloads the video using a random resolution. If you want a specific resolution, use the flat `--resolution`: + + +```python +$ artbox youtube download \ + --url https://www.youtube.com/watch?v=zw47_q9wbBE \ + --output-path /tmp/artbox/ \ + --resolution 360p +``` + +### Remove the audio from a video + +First, download the youtube video `https://www.youtube.com/watch?v=zw47_q9wbBE`, as explained before. + +Next, run the following command: + + +```python +$ artbox video remove-audio \ + --input-path "/tmp/artbox/The Legend of Zelda Breath of the Wild - Nintendo Switch Presentation 2017 Trailer.mp4" \ + --output-path /tmp/artbox/botw.mp4 +``` + +### Extract the audio from a video + +First, download the youtube video `https://www.youtube.com/watch?v=zw47_q9wbBE`, +as explained before. + +Next, run the following command: + + +```python +$ artbox video extract-audio \ + --input-path "/tmp/artbox/The Legend of Zelda Breath of the Wild - Nintendo Switch Presentation 2017 Trailer.mp4" \ + --output-path /tmp/artbox/botw-audio.mp3 +``` + +### Combine audio and video files + +First, execute the previous steps: + +- Download a youtube video +- Remove the audio from a video +- Extract the audio from a video + +Next, run the following command: + + +```python +$ artbox video combine-video-and-audio \ + --video-path /tmp/artbox/botw.mp4 \ + --audio-path /tmp/artbox/botw-audio.mp3 \ + --output-path /tmp/artbox/botw-combined.mp4 +``` + +## Additional dependencies + +If you want to use Python to play your audio files, you can install `playsound`: + + +```python +$ pip wheel --use-pep517 "playsound (==1.3.0)" +``` + +### Demo Video + +For a better explanation of the facilities and usage, please watch to the following video. + + diff --git a/pages/blog/avances-scicookie-grant-2024/index.qmd b/pages/blog/avances-scicookie-grant-2024/index.qmd new file mode 100644 index 000000000..818d613cf --- /dev/null +++ b/pages/blog/avances-scicookie-grant-2024/index.qmd @@ -0,0 +1,36 @@ +--- +title: "Implementaciones recientes en SciCookie gracias a la subvención de la PSF" +slug: avances-scicookie-grant-2024 +date: 2024-06-21 +authors: ["Yurely Camacho"] +tags: [psf, osl, scicookie, subvención, grant, comunidad, colaboración, desarrollo] +categories: [código abierto, desarrollo de software, python] +description: | + Descripción de las mejoras y tareas realizadas en SciCookie gracias a la subvención de la PSF. +thumbnail: "/header.png" +template: "blog-post.html" +--- + +Como mencionamos en el post anterior [SciCookie recibe nueva subvención de PSF para mejoras y crecimiento](https://opensciencelabs.org/blog/scicookie-recibe-nueva-subvencion-de-psf-para-mejoras-crecimiento/), en enero de 2024 la PSF aprobó nuestra solicitud de subvención. Esto nos ha permitido implementar una serie de mejoras significativas en SciCookie. A continuación, presentamos los avances y tareas realizadas. + + + +Se han incorporado nuevas opciones de herramientas a la plantilla, incluyendo los códigos de conducta (CoC) de Python (adaptado) y de NumFOCUS. También se ha añadido soporte para prettier como una opción de linter, proporcionando más flexibilidad a los usuarios. + +![Nuevas opciones de CoC](scr1.png) + +En cuanto a la estructura de la herramienta, se han realizado varias optimizaciones. Se implementó una opción condicional para las preguntas de uso de herramientas en el proyecto (`depends_on`) y se corrigió la indentación en algunos archivos de configuración para mejorar la legibilidad. Además, se unificaron estos archivos según las distintas opciones de sistemas de construcción (*build systems*) para evitar la repetición innecesaria de código y texto. También se creó un ejemplo para la opción de Interfaz de línea de comandos (CLI) como base del contenido de ese archivo. + +En el ámbito de la documentación, se ha automatizado el proceso de mover archivos de configuración a la raíz del proyecto, manteniendo una estructura ordenada. La selección del motor de documentación sphinx se ha dividido en `sphinx-rst` y `sphinx-md(myst)` para ofrecer opciones específicas. Se ha generado la documentación de la API para estas opciones, añadido `quarto` como un motor de documentación adicional, y se han incorporado distintos temas para cada motor de documentación disponible en la plantilla. + +![Nuevas opciones de documentación](scr2.png) + +Se ha iniciado comunicación con Leah Wasser, directora ejecutiva de pyOpenSci, quien ha revisado directamente el uso de SciCookie para las necesidades de pyOpenSci como plantilla de proyectos para recomendar a su comunidad. A partir de sus revisiones y sugerencias, se ha mejorado SciCookie para cumplir con sus principales expectativas. Leah también ha comenzado la creación de un perfil (conjunto de configuraciones por defecto) específico para pyOpenSci en [SciCookie](https://github.com/osl-incubator/scicookie/pull/273). Debido a sus prioridades y cronograma, este trabajo aún está en progreso. + +Con estos avances, SciCookie se presenta como una herramienta más completa y adaptable a diversas necesidades. Continuaremos trabajando para ofrecer mejoras a la comunidad de código abierto. + +Puedes consultar nuestro post [Collaborating and learning from SciCookie](https://opensciencelabs.org/blog/scicookie-collaborating-and-learning/) para más información sobre la herramienta. + +Mantente atento a futuras actualizaciones sobre SciCookie en próximos posts. + +Elementos gráficos de la portada fueron extraídos de [Job illustrations by Storyset](https://storyset.com/job) diff --git a/pages/blog/console-based-representation-in-astx/index.ipynb b/pages/blog/console-based-representation-in-astx/index.ipynb deleted file mode 100644 index c012793f7..000000000 --- a/pages/blog/console-based-representation-in-astx/index.ipynb +++ /dev/null @@ -1,532 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "id": "087f2f63-a7a4-4ca7-a007-ac84e0f40aad", - "metadata": {}, - "source": [ - "---\n", - "title: \"Console-based representation in ASTx\"\n", - "slug: \"console-based-representation-in-astx\"\n", - "date: 2024-08-08\n", - "authors: [\"Ana Krelling\", \"Ivan Ogasawara\"]\n", - "tags: [\"abstract syntax tree\", \"ascii\", \"console\"]\n", - "categories: [\"abstract syntax tree\", \"console\"]\n", - "description: |\n", - " Recently, console-based AST representation was included in the ASTx framework. Such feature can enhance the debugging and analysis capabilities of ASTx, particularly in environments such as a pdb session. In this tutorial, we'll explore this new feature as well as the ASTx Graphviz visualization.\n", - "thumbnail: \"/header.png\"\n", - "template: \"blog-post.html\"\n", - "---" - ] - }, - { - "cell_type": "markdown", - "id": "dc9b5a46", - "metadata": {}, - "source": [ - "# Introduction\n", - "\n", - "The ASTx library is an agnostic framework for constructing and representing Abstract Syntax Trees (ASTs). Its primary objective is to provide a versatile and language-independent structure for ASTs, with the flexibility to be utilized across various programming languages and parsing tools. ASTx doesn't aim to be a lexer or a parser, although it could be used by any programming language or parser written in Python in order to provide a high level representation of the AST.\n", - "\n", - "Many kinds of nodes (classes) are currently supported. Below is a list with just some examples:\n", - "\n", - "##### Statements:\n", - "* Function\n", - "* Function Prototype\n", - "* FunctionReturn\n", - "* ForRangeLoop \n", - "* VarDecl\n", - "\n", - "##### Operators:\n", - "* BinaryOp\n", - "* UnaryOp\n", - "\n", - "##### Data types:\n", - "* Boolean\n", - "* Literal \n", - "* Variable \n", - "\n", - "\n", - "The ASTx project is still under development, so new classes may be added to the ones above at any time.\n", - "\n", - "Below are installation instructions and an example, so you can have an overview of how you can leverage the ASTx library for your needs.\n", - "\n", - "# Installation\n", - "The first step is to install ASTx. You can do it simply by running the command below in your terminal:\\\n", - "`$ pip install astx`\\\n", - "If you need more information on installation, you can get it in the [ASTx installation page](https://github.com/arxlang/astx/blob/main/docs/installation.md).\n", - "After that, you can just open a Jupyter Notebook instance and start writing your first AST.\n", - "\n", - "\n", - "# Example: an AST of a series of mathematical operations\n", - "Here we will present a quick example of an AST of the expression \\\n", - "`basic_op = lit_1 + b - a * c / a + (b - a / a)`, in which \\\n", - "$~~~~$ `lit_1` is a defined integer, and \\\n", - "$~~~~$ `a`, `b`, and `c` are variables.\\\n", - "The first thing to do is, in your Jupyter Notebook instance, import `display`, which will allow you to have a basic visualization of the AST, and the astx library itself. " - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "774dd917-ba8d-4718-b359-9faccd1d918e", - "metadata": {}, - "outputs": [], - "source": [ - "# import display for AST visualization\n", - "import astx\n", - "\n", - "from astx.viz import graph_to_ascii, traverse_ast_ascii" - ] - }, - { - "cell_type": "markdown", - "id": "870951f8-904f-4947-8d17-e6483674ac7e", - "metadata": {}, - "source": [ - "Then we create an instance of the Module class, and this instance will be the first node of the tree, or the root node. After that, we declare the variables and literal that will be part of the basic operation that we will parse into an AST." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "7b733009-e028-458d-b2ee-6e2310044e0f", - "metadata": {}, - "outputs": [], - "source": [ - "# Create module\n", - "module = astx.Module()\n", - "\n", - "# Declare variables\n", - "decl_a = astx.VariableDeclaration(name=\"a\", type_=astx.Int32, value=astx.LiteralInt32(1))\n", - "decl_b = astx.VariableDeclaration(name=\"b\", type_=astx.Int32, value=astx.LiteralInt32(2))\n", - "decl_c = astx.VariableDeclaration(name=\"c\", type_=astx.Int32, value=astx.LiteralInt32(4))\n", - "\n", - "a = astx.Variable(name=\"a\")\n", - "b = astx.Variable(name=\"b\")\n", - "c = astx.Variable(name=\"c\")\n", - "\n", - "# Declare literal\n", - "lit_1 = astx.LiteralInt32(1)\n", - "\n", - "# State the expression\n", - "basic_op = lit_1 + b - a * c / a + (b - a / a)" - ] - }, - { - "cell_type": "markdown", - "id": "84c6d62e-253d-4e93-aa54-998aa1889ce1", - "metadata": {}, - "source": [ - "After the basic expression is stated, we create an instance of the Function class. As mentioned in the API documentation, each instance of the Function class must have a prototype and a body, so we'll create those first.\n", - "\n", - "The body is made of a block that is created and the variables, as well as the basic operation, are appended to it afterwards." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "78e439f2-22d2-47bc-aa41-5f32f19320e5", - "metadata": {}, - "outputs": [], - "source": [ - "# Create FunctionPrototype\n", - "main_proto = astx.FunctionPrototype(\n", - " name=\"main\", args=astx.Arguments(), return_type=astx.Int32\n", - ")\n", - "\n", - "# Create FunctionReturn\n", - "main_block = astx.Block()\n", - "main_block.append(decl_a)\n", - "main_block.append(decl_b)\n", - "main_block.append(decl_c)\n", - "main_block.append(astx.FunctionReturn(basic_op))\n", - "\n", - "# Create Function\n", - "main_fn = astx.Function(prototype=main_proto, body=main_block)\n", - "\n", - "# Append function to module\n", - "module.block.append(main_fn)" - ] - }, - { - "cell_type": "markdown", - "id": "7e67f20e-1ffa-4cb6-b7e6-669c7a53e955", - "metadata": {}, - "source": [ - "After this, the module is complete. We can get its AST structure as a dictionary, as well as a PNG representation." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "a183ca30-a5e3-4445-b0df-3833bca3cd58", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'MODULE[main]': {'content': [{'FUNCTION[main]': {'content': {'args': {'Arguments(0)': {'content': [],\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}},\n", - " 'body': {'BLOCK': {'content': [{'VariableDeclaration[a, Int32]': {'content': {'Literal[Int32]: 1': {'content': 1,\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': 'c4848732a3c542f1b3818bc799dc0b26',\n", - " 'kind': }}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}},\n", - " {'VariableDeclaration[b, Int32]': {'content': {'Literal[Int32]: 2': {'content': 2,\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': 'b63f0bf700194bb7abbdf99d8cc20336',\n", - " 'kind': }}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}},\n", - " {'VariableDeclaration[c, Int32]': {'content': {'Literal[Int32]: 4': {'content': 4,\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '0c0686b5f12a45bd9ff1a20da82702a0',\n", - " 'kind': }}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}},\n", - " {'RETURN': {'content': {'BINARY[+]': {'content': {'lhs': {'BINARY[-]': {'content': {'lhs': {'BINARY[+]': {'content': {'lhs': {'Literal[Int32]: 1': {'content': 1,\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '8d5d86d52b98484a8e5947ae4e6556f1',\n", - " 'kind': }}},\n", - " 'rhs': {'Variable[b]': {'content': 'b',\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}},\n", - " 'rhs': {'BINARY[/]': {'content': {'lhs': {'BINARY[*]': {'content': {'lhs': {'Variable[a]': {'content': 'a',\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}},\n", - " 'rhs': {'Variable[c]': {'content': 'c',\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}},\n", - " 'rhs': {'Variable[a]': {'content': 'a',\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}},\n", - " 'rhs': {'BINARY[-]': {'content': {'lhs': {'Variable[b]': {'content': 'b',\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}},\n", - " 'rhs': {'BINARY[/]': {'content': {'lhs': {'Variable[a]': {'content': 'a',\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}},\n", - " 'rhs': {'Variable[a]': {'content': 'a',\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}}],\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}}},\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}}],\n", - " 'metadata': {'loc': {line: -1, col: -1},\n", - " 'comment': '',\n", - " 'ref': '',\n", - " 'kind': }}}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Create dictionary representation\n", - "module.get_struct()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e551c944-a801-47b3-a995-bed0ae10762b", - "metadata": {}, - "outputs": [], - "source": [ - "# Create ascii representation\n", - "dot_graph = traverse_ast_ascii(module.get_struct(simplified=True))\n", - "graph = graph_to_ascii(dot_graph) \n", - "print(graph)" - ] - }, - { - "attachments": { - "0d8393a6-fb00-4364-8f2f-319295075948.png": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABMQAAAcACAYAAAA/oZ/FAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nOzdf5DddX0v/ufJyWZ3IZAsCcYoNwQIIkGWH3LFGW/jD4xiAlXvWOtQ4I5FOU2VW6VAb4VihxupYm1NLdMiMiySq7n2DmKyadWMOoncemGHxGBQJD+QBBLCJmGzBDcnIdnvH47n25gFQpLdz+5+Ho+ZM+ac1/vz+Tw/yzh75jmfz2cr73rXu/oDAAAAACVROe644xRiAAAAAJTG2Gq1WnQGAAAAABgyYyuVStEZAACOirvvvjstLS1JknHjxiVJ9uzZc9C6w50lSU9PTyZOnDjgrFKppL9/4IvvR+PsxhtvzIYNGwZcCwAwnI0tOgAAwNHyX/7Lf8nHPvaxJElTU1MqlcqAxdbhzpJk3759eakr7FtbW9PX11eK2U033ZQJEyYMuA4AYLhTiAEAo8b+/fuzfPnyomOUwrx584qOAABw2MYUHQAAAAAAhpJCDAAAAIBSUYgBAAAAUCoKMQAAAABKRSEGAIway5YtKzpCaXR1daWnp6foGAAAh6XS1tbWX3QIAAAAABgqrhADAEaNlStXFh2hNDo6OtLe3l50DACAw6IQAwBGjWOPPbboCEdk2rRp2bZtW1avXp1Zs2Ydtf3eddddueyyy17VNvfee2/Wr1+f22+/fcB5S0tLqtXq0YgHADDkFGIAwKh14403ZseOHTn77LOTJOecc0527NiRG2+8MUlyxhln5Ac/+EGeeeaZ/OAHP8gZZ5yRJLnggguyY8eObNu2LWvWrMn//J//M83NzY39rlu3Lm9961sb77/97W/n4x//eJLks5/97EuWSLfffnt27NhxwOujH/3oAWt6enpyzjnnZMWKFUft57Bw4cJ0dXW9qm2uuOKKfP7znz9qGQAAhhOFGAAwqq1fvz5z585Nklx66aVZv359Y/bVr341y5cvzxvf+MY88MADufPOOxuz3t7eTJ06NR/+8IdzwQUX5Lbbbjsqeb785S9n6tSpjdfXv/71o7Lfl/OjH/0oa9euHfTjAACMFAoxAGBU+9GPfpR3vvOdSZJ3vvOdjSuvpk+fnpkzZ+Zv//Zv09PTky9+8YuZOXNmTj311Ma2e/fuzc9//vN8+tOfzh/+4R9m/PjxR5xn3759qdfrjde+fftedv20adPyyCOP5IEHHsiSJUvypS99KY8//nje9ra3JUkuueSS/Pu//3s2bdqUn/zkJ43yL0lmzZqV1atX5+mnnz7olsk77rgjCxYsSGdnZ372s5/l2muvPeJzAwAYKRRiAMCoVq/Xs3nz5syZMye/+tWvUq/XkySnnnpqtm3bll//+tdJkl27dmXbtm057bTTDtrHL3/5yyRp3FI51I499tjMnj07kydPzpo1a3LLLbfkD/7gD5Ik/f39+eQnP5np06fnz/7sz3LnnXdm8uTJSZIVK1bknHPOyXe/+90B93vmmWfmgx/8YN797nfn05/+dCZMmDBk5wQAUCSFGAAw6nV2duaLX/xiOjs7G58dc8wx6evrO2BdX19fjjnmmIO27+/vT19f31G5QuwTn/hE1q1b13i9+c1vfsVtnnnmmfT19eWpp57Khg0bsmnTpkyaNClJsnTp0qxcuTL79u3LQw89lA0bNuQNb3jDIWXp7OzM3r17s3Xr1jzzzDOZNm3aEZ0bAMBIMbboAAAAg+373/9+Pvaxj2XZsmX5z//5PydJfv3rX6e1tfWAda2trY0rxv6jSqWS1tbW7Nq1K0myf//+g9YM9NlA7rnnnnzlK19pvN+2bdsrbvPb2yr37dvX+PfYsb/5Gnfuuefms5/9bE455ZRUKpVMmTIlTU1Nh5Tl+eefb/x77969B/zhAACA0cwVYgDAqPf888/nfe97X6PQSpInnngikydPblwRNn78+EyePPmAh+7/1hvf+MYk//+tk7/+9a9TrVYb82q1mhdeeOGQsuzatSubN29uvPbs2XNY51SpVJIkd911V775zW/m3HPPzTnnnJMnnnjisPYHAFAmCjEAoJSeeOKJPPbYY7n22mszYcKE/Pmf/3l+8YtfZMOGDY01LS0taW9vz4IFC/K///f/bhRqDz74YD7ykY9kwoQJueCCC/KmN70pDz/8cGO7arWa5ubmxus/XrH1u7PfXul1uI4//vg88sgjSZK3vOUtOf30049ofwAAZaAQAwBK6+qrr85FF12Uxx9/PO94xzvy8Y9/vDE7/vjj89RTT+Wb3/xmHnroodxwww2N2c0335wpU6bk0Ucfzd13352bb745a9eubcw//OEPZ8uWLY3XN7/5zcbsU5/61AGzv/iLvziic7j++utz99135/7778+HPvShrFmzpjG7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UEKVtra2/qJDAAAAAMBQccskAIesvb09HR0dRccYNKP9/ACGo1qtllqtVnQMAEpGIQbAIatWq2lpaTkq+zr99NPz6KOPHvF+Lrvsstxzzz1HIdHRPT8ADk1TU1OampqKjgFAySjEAAAAACgVhRgAAAAApaIQA6BQX/ziF7Np06YsWbIkU6dObXx+ySWXpKurK+vXr89dd92V448/vjG76qqr8otf/CKrV69Oe3t74/Prr78+X/nKVxrvm5ub8+STT2batGlDczIAAMCIoBADoDBTp07Nhg0bctppp+XRRx/N3/zN3yRJTjzxxPzTP/1Trrnmmpx11lkZN25c/vIv/zJJMmPGjNx888354Ac/mHe/+91597vf3djffffdlzlz5jSeRfOOd7wja9euzcaNG4f+5AAAgGFLIQZAYfbs2ZOvfe1r2bNnT7761a/mPe95T5LfFFk//elP8//+3//L7t2780//9E9573vfmySZPXt2fvjDH+axxx5Ld3d3vvWtbzX2t379+mzcuDG/93u/lyS59NJLc9999w39iQEAAMOaQgyAwuzcuTN79+5NknR3d6elpSXHHntsJk+enO7u7sa6rVu35jWveU2SZNKkSXnmmWcas//47yT59re/nQ984AMZO3Zs3vve9+Y73/nOEJwJAAAwkijEACjMhAkTMm7cuCS/uU2yr68vL7zwQrZt25YTTzyxsW7KlCmNgmzbtm2ZMGFCYzZx4sQD9vntb38773vf+/KOd7wj69aty9NPPz0EZwIAAIwkCjEACjNu3LhcddVVaWpqytVXX53vfe97SZLly5fnvPPOy1vf+ta0tLRk3rx5jdmyZcvyzne+MxMnTkxTU1Pe//73H7DPTZs25Yknnsj8+fPz7W9/e8jPCQAAGP4UYgAUZsuWLTnllFOyYcOGzJw5s/Hg/GeffTbz5s3LP/7jP+bnP/95XnzxxcYD99evX5/bbrstS5cuzXe+852sW7fuoP3ed999Of3007NkyZIhPR8AAGBkGFt0AADKae3atTnrrLOSJDfccMNB8yVLlrxkoXX33Xfn7rvvfsl9b968OV1dXdmyZcvRCQsAAIwqrhADYFSpVqv56Ec/esBfnwQAAPiPFGIAHLJ6vX7AX38cbs4+++w88cQT6e3tzb333vuqtx/u5wcwGvX29qa3t7foGACUTKWtra2/6BAAjAzNzc2ZOHFitm7dWnSUQTHazw8AAPgNV4gBcMhmzpyZBQsWFB1j0Iz28wMYjmq1Wmq1WtExACgZD9UHAAAK09TUVHQEAErIFWIAAAAAlIpCDAAAAIBSUYgBAAAAUCoKMQAAAABKxUP1AXhVzjvvvCxatChJMmbMmOzfv3/AdSNxdvzxx6e3t3fAdQAAwOihEAPgkK1duzZXX311431ra2v6+voGXHs0ZjNmzMhHPvKRzJ8/f0iOlyTbt28fcB0AADB6KMQAOGS7du3K8uXLh+x4vb29mT179pAeEwAAGP08QwwAAACAUlGIAQAAAFAqCjEAAAAASkUhBgAAAECpeKg+AMNWT09Purq6io4BwCDq7e0tOgIAJVRpa2vrLzoEAAAAAAwVt0wCMGy1t7eno6Oj6BgADKJarZZarVZ0DABKxi2TAAxb1Wo1LS0tRccAYBA1NTUVHQGAEnKFGAAAAAClohADAAAAoFQUYgAAAACUikIMAAAAgFJRiAEAAABQKgoxAAAAAEpFIQYAAABAqSjEAAAAACgVhRgAAAAApaIQAwAAAKBUFGIAAAAAlMrYogMAwEup1+vp7u4uOgYAg6i3t7foCACUUKWtra2/6BAAAAAAMFTcMgkAABSmVqulVqsVHQOAknHLJAAAUJimpqaiIwBQQq4QAwAAAKBUFGIAAAAAlIpCDIBh5+abb87nPve5xvuJEydm06ZNGT9+fIGpAACA0UIhBsCws2TJksydO7fx/uKLL84DDzyQXbt2FZgKAAAYLRRiAAw7q1atSqVSydlnn50kmTt3bpYsWVJwKgAAYLRQiAEwLHV2dmbu3LlpbW3NrFmz8m//9m9FRwIAAEYJhRgAw9LixYszd+7cXHTRRXn44Yfz3HPPFR0JAAAYJRRiAAxLDz30UCZPnpx58+Zl8eLFRccBAABGEYUYAMNSf39/Ojs7c+GFF2bp0qVFxwEAAEYRhRgAw9b111+fyZMnp7u7u+goAADAKKIQAwAAAKBUxhYdAAAAKK/e3t6iIwBQQpW2trb+okMAAAAAwFBxyyQAAAAApaIQAwAAClOr1VKr1YqOAUDJeIYYAABQmKampqIjAFBCrhADAAAAoFQUYgAAAACUikIMAAAAgFJRiAEAAABQKh6qD8ABTj311Nx6662N92PGjMn+/fsHXFu22cqVK3PbbbcNuBYAABg5FGIAHGDChAmZNGlS5s+fnyRpbW1NX1/fgGvLNJsxY0Zmz5494DoAAGBkUYgBcJDt27dn+fLlRccYVnp7exViAAAwSniGGAAAAAClohADAAAAoFQUYgAAAACUikIMAAAAgFLxUH0ADtDT05Ourq6iYww7fi4Ag6O3t7foCACUUKWtra2/6BAAAAAAMFTcMgnAAdrb29PR0VF0jH4l1XIAACAASURBVGHHzwVgcNRqtdRqtaJjAFAybpkE4ADVajUtLS1Fxxh2/FwABkdTU1PREQAoIVeIATBo5s6dmx07dmTHjh156qmnct999+Xkk09OkjQ3N2fHjh2ZNm3agNteeumlWb16dbZs2ZJ77rkn48ePb8za2try1a9+NRs3bsz69evzN3/zN0mST33qU1m4cGFj3QUXXJDHH38855133iCeJQAAMNIoxAAYVOvWrcsJJ5yQ008/PevXr8/f/u3fvuI2r3nNa/LP//zPufHGG9Pe3p62trZ85jOfacy/8pWvpLm5ORdeeGHe9ra35bHHHjtoHzNmzMjChQszb968rFq16qieEwAAMLIpxAAYEn19ffne976XN77xja+4dvbs2fn5z3+ezs7OdHd35x//8R/z/ve/P0ly2mmn5aKLLsq1116bLVu25Jlnnsk999xzwPZTpkzJv/zLv+Sv//qv84Mf/GBQzgcAABi5FGIADIljjjkm73//+w/paq3TTjstv/rVrxrvN2zYkKlTp6a1tTVvetObsmHDhmzfvn3AbcePH59vfetb6ezszKJFi45WfAAAYBTxUH0ABtWMGTOyY8eOJMmWLVvywQ9+8BW3aW1tze7duxvvf/vvY489Nq95zWvy3HPPveS2s2bNyurVq/OOd7wjTU1N2bt37xGeAQAAMNq4QgyAQfXbZ4i97nWvyxe+8IV861vfyrhx4152m76+vgP+ouNv//3CCy9k27ZtaWtre8ltH3744cyePTsvvvhi/vt//+9H5yQAAIBRRSEGwJDYvXt3vv71r+e1r31tTjvttJddu2HDhkyfPr3x/pRTTsmWLVvS19eXRx99NKeddlomTZo04LZbt27Niy++mD//8z/Ptdde+4rHAgAAykchBsCgGzt2bI455ph8+MMfTn9/fzZv3tyYjRs3Ls3NzY1XpVLJsmXLMnPmzLzvfe/LpEmT8olPfCKLFy9Okjz++ONZ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4YQTms65dOnS2LlzZ1xwwQUH1C688MJ417veFV/60pfitttui7lz58YPfvCDePLJJ+PKK6+MiDf/leZP//RP43Of+9xh78ell1461rADAAAOz6mnnhonnXRSrF69Or7xjW/EzJkzIyLinHPOidNOOy0iIk477bQ455xzxrZpNBrxiU984qBzLl26NJ5//vlYunTpft9vNBpx7LHHxqmnnhpPPvlk3HrrrRER8Rd/8Rdx5plnjh2L/PLr1atXH/Z+XHXVVYc9Fpg4DTE6yujoaDz55JOxdevWOPXUUyMi4vXXX49GoxGjo6MxPDx8wKdefec734k/+IM/aDrf0qVL4+tf/3ocffTR8b73ve+A+vDwcPz93/99DA0NjdU///nPx6c//emYNWtWXHXVVfHwww/Hj3/848Peh8suuyz6+voOezwAAPCm4eHhWL9+fRx99NFj/8i8b9++2Ldv3wFfR0Ts3r079uzZE4sXLz5grlwuFxdccEF88YtfjPPPPz9yudwBY37xi1/EmjVrYsGCBTFz5sx45ZVX4ktf+tLYGWErV66M//Jf/ku88sorh70P11xzzbj2GZgYDTE6Si6Xi4ULF8Zv/dZvxU9+8pPD2mbNmjVN/1XoqKOOig9/+MPxv/7X/4oHH3zwgH8V+uWYZcuWxdvf/vbYtm1bRERs3rw5vv/978e1114bV155ZVx//fVHtlMAAMBhKRQK8bGPfSx+/vOf73dVyKGsWbMmli9ffsD3zzjjjCgUCnHnnXdGPp+PRYsWHTCmWCzGRz/60di+fftYo+1v/uZvoq+vL6655po46aST4vbbbz+ynQKmxFGpA8BkufXWW8dOVf7a174WmzdvPqztfvazn8Xzzz8fZ5999n7f/+3f/u0YGRmJxx9/PO6///645JJL4itf+cpY/Xvf+15ERNRqtfg3/+bfxI4dO8ZqN9xwQ/T398eaNWvi6aeffssMZ511Vtx5550RETFr1qw488wz4/XXX4//83/+z0HPXgMAAP6/n/70pxHx5uWMV1111X5ngh3KvffeG9dee+0BN7hfunRpPPTQQ/H666+P/QP5Lz/JsaurK3bv3h0REY899th+DbXh4eG4/vrr4/bbb4/LL788hoeH3zLDn/zJn8Sf/MmfRETE2972tti6dWtERHz1q1+N//pf/+th7QcwPs4Qo2NcccUVMWfOnFi0aFGcffbZ8a//9b8+7G3vuOOOuOyyy/b73oUXXhgPPvhgjIyMxP333x9nnXVWzJo1a7/6b/zGb8TGjRvH7knwSwMDA/HMM8/EAw88cFjP/+ijj8aSJUtiyZIl8b//9/+OT3/607FkyZL49Kc/fdj7AAAAWXbqqafGCSecEP/iX/yLuOmmm8ZudP9WhoaG4nvf+15ceuml+33/wgsvHHs//8ADD+x3xcgv7yG2YMGCyOfzB9yT+MEHH9zv/9/K7bffPnY8MDQ0NPb1L//BH5h8GmJ0lNHR0di+fXvcd999cf755x/2dvfdd1986EMfire//e1j31u6dGn8s3/2z2LnagiYEgAAIABJREFUzp3xgx/8IAqFQnz4wx/eb7uhoaG49tpro1wux4knnjjh3I1GI55//vl4/vnnY9++ffHSSy/F888/Hy+99NKE5wQAgKx5/fXXo7+/PzZv3hxLliw57O1+/R/I58yZE2eccUZcf/31sXPnzli1alWcccYZcdxxx+233QsvvBA33nhjrFq1quk9xg7XK6+8MnY8MDo6Ovb1eO49BoyPhhgdY8aMGVEoFOJd73pXXHjhhWP39DrqqKOiq6srcrlcFAqF6OrqOmDbRqMR69evj3/6T/9pRET8xm/8Rpx66qmxcOHCOPHEE+PEE0+MO+64Iy688MIDtq3VarFu3TpncwEAQEL5fD66urpi0aJFsXDhwrHjgZkzZ4594uSvfv2rNm3aFLNmzYpjjz02IiIuuOCCeOaZZ+Id73hHnHjiifGOd7wjnn322ab/6P7d7343Zs6cGcuWLZvCvQMmm4YYHePrX/96vPDCC/EP//APsW3btvjSl74UERFf/vKXY+fOnfHe9743/vqv/zp27tw59okzv2rNmjVjzbKlS5fGo48+Gjt37hyrf+tb3zroWWdf//rX4w//8A/HFtAj8bGPfSx+9KMfHfE8AACQJU888UT87Gc/i0qlEn/5l38Z//AP/xAREQ8//HA88cQTY2Mefvjhptvfeeed+x0PfPvb396v/u1vf7vpB22Njo7GbbfdFp/5zGcmZT9OOumkSZkHODQ31acjHOp06F+9QeWv+9XF5oknntivoVWpVPYb+93vfjcWLFgQERHz58/fr/b4448fsHCde+65TZ9zZGQkZsyYEY899lj823/7b+P+++8/aPZfdccdd8SZ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zU/0y+VyTZtXU1GLiHjjjTcO+mnHk/Hf7kRrK1eujNmzZzcdBwDA/jTEgI7w9NNPx8aNG1PHaCn1el1DjI40MjLiv/cmVqxYkToCAEDbcA8xAAAAADJFQwwAAACATNEQAwAAACBTNMQAAAAAyBQ31W9TAwMDqSMkU6vVotFopI5BC9m2bVvqCC1pcHAw+vv7U8eAg5roWrZhw4ZJTtIZ+vv7Y3BwMHWMzKvX66kj8BYajUbUarXUMTiELLxGWT6eg1aR6+3tHU0dAgAAAACmi0sm21S1Wk0dIZlKpRKlUil1DFpIuVyOcrmcOkbLKZVKUalUUseAg5roWpblNfBQrI+twZrU+qyPrS8Lr5G1DNLTEGtTPT09qSMkUywWI5/Pp45BCykUClEoFFLHOCJf/epX4+mnn46//du/PextPvjBD8Zjjz0WL730UvT19R1Qz+fzUSwWJzMmTKqJrmXtvgb29fXFSy+9FI899lgsWbLksLe744474umnn46vfvWrTevWx9bQCWtSp7M+tr4svEbtvpZBJ9AQAzrSjTfeGLt3747du3fH1q1b4ytf+Up0dXVFRMTv//7vj9V++b/f//3fj4iIu+66K/7xH/9x7GBm+fLlcc899+w3949+9KP48Y9/vN/31qxZE7t3745du3bF5s2b48/+7M8iImLOnDnx7LPPxhlnnDE29owzzohnn3025syZs98cX/jCF+LjH//4Ye/jQw89FAsXLnTPIDJv9uzZY/8tv/jii/HAAw/Ehz70obH6I488st9/74888khERCxatCh2794dK1asGBv7xBNPxJlnnjn2+NJLL43du3fHRz7ykf2e89lnn43du3fHCy+8EA899FBcdNFFERHxO7/zO/HTn/40uru7x8b+8R//cXznO9/Zb/vBwcFYuHBh3H///Ye9n8uXL48vfOELhz0eAICD0xADOlalUoljjz02zjnnnHjf+94Xn/rUpyIi4p577okTTzwxbr/99rj99tvjxBNP3K/pNWvWrFi2bFnTOd/1rnfFiSeeGHPnzo358+fvV7v++uvjuOOOi0984hNRLpfjkksuiV27dsVXvvKVuPbaa8fGrVy5MlavXh27du2a/J2GDDv11FPjpJNOitWrV8c3vvGNmDlzZkREnHPOOXHaaadFRMRpp50W55xzztg2jUYjPvGJTxx0zqVLl8bzzz8fS5cuPaB24YUXxrve9a740pe+FLfddlvMnTs3fvCDH8STTz4ZV155ZUS8eQbAn/7pn8bnPve5ydxVAACOkIYY0PFqtVo89NBDceqpp0ZExMjISDQajXjjjTfijTfeiEajESMjI2Pj169fH8uXL28614UXXhg//OEP44c//GHTA+TR0dH4yU9+Eg8//PDYWWF/+Zd/GQsWLIizzz47zjnnnDj11FPja1/72hTsKTA8PBzr16+Po48+Ot75zndGRMS+ffti3759B3wdEbF79+7Ys2dPLF68+IC5crlcXHDBBfHFL34xzj///Mjlck2f7+///u9jaGgo3ve+90VExOc///n49Kc/HbNmzYqrrroqHn744QPOKgUAIC0NMaDjveMd74gPf/jD8eijjx7W+AceeCB+8zd/c+xg+lctXbo07r///rj//vubNsRyuVz81m/9Vvz2b/92bNmyJSIi9u7dGzfeeGOsXLkyVq5cGf/5P//n2Lt375HtFNBUoVCIj33sY/Hzn/88nn/++cPaZs2aNU2b4GeccUYUCoW48847I5/Px6JFiw4Yc9RRR8WyZcvi7W9/e2zbti0iIjZv3hzf//7349prr40rr7wyrr/++iPbKQAAJp2GGNCxPvnJT8bu3bvjySefjKGhofjGN75xWNuNjo7GXXfddcD9vLq6uuKf/JN/Eg888EBs3LgxzjvvvLH7kkVEfO5zn4tdu3bFgw8+GLfeemvcfffdY7W/+7u/i//L3h0HyXnW9wH/3a1Wt8cR684SNiJEOLIce9R4jZLMmAmGJBiBB0FxM7R1qRTXDXgrGmZaCpmJ45ppRva0ToYZZVyooU02gSQa2imqLNcOHhJkrEmKyqrUhuLKlhN5Y2EW4dNW9Wkl665/OFwRWl108t0+77vP5zPDjPae933e73uPtL/xj32fnZqaisnJyfijP/qjpblB4Czf+ta34vnnn4/f/M3fjF/7tV8765NgC9m9e3ds3rz5rH2/Il5ugO/fvz9eeumleOyxx85pgn/xi1+M73znO7Fz58745V/+5Thy5Mj82N133x233npr7N69O55++ulXfnMAACwpDTFgaH1/D7Err7wyDh06FL/92799wef+4R/+Yfz9v//3z3pE6i1veUvMzMzEN77xjfhf/+t/xf/5P/8n3vKWt8yP/8Zv/Ea85jWviV//9V+Pn/u5n4u5ubn5sdnZ2di/f3/s37//rMcz/yY/8zM/M78R+Be+8IULPg9ydM0118Rll10Wf/tv/+2499574+qrr76g82ZmZuKLX/xi3HzzzWf9/B3veEd85StfiYiXPzn6ww2xd7zjHfGjP/qjsW/fvvk9yr6v3W7HM888M3/+hfpP/+k/zf+bf9Ob3rSocwEAuHAaYsDQe+GFF+Jzn/tcvO1tb7vgc44ePRqHDx8+q+G1efPmmJycjOeeey6ee+65WL169Tn/gXzmzJn4zGc+ExMTE/Ge97znFWf/7//9v8ell14al156afydv/N3XvF8MOxeeumlOHDgQDz++OPx1re+9YLP++xnPxtbt26df7169ep44xvfGL/xG78RR48ejR07dsQb3/jGWLNmzVnnzczMxK//+q9Ho9GItWvXvuL873vf++b/zf/5n//5K54PAID+NMSAoTUyMhIrVqyIqampeN/73hfPPPNMRESMjo7G2NhYVCqVqFQqMTY2FqOj574dfvazn413v/vd86/f/va3xz/5J/8k1q5dG2vXro3t27fH29/+9r7X/nf/7t/FRz7ykeW5MaCv7/973rRpU1x33XXze3qtXLly/hsnf/DPP+jgwYPxIz/yI3HppZdGxMv/3p955pl47WtfG2vXro3Xvva18Rd/8Rdx4403nnNup9OJBx54IH7lV35lGe8OAIClpCEGDK1bb701vvOd78T/+B//I6666qr5/1h93/veF0ePHo3bbrstbrvttjh69Gi8733vO+f8P/7jP45utxsRERs2bIgf+7Efi0ceeWR+/Itf/GL82I/9WGzYsOGcc3fv3h1r165d1KfSgFfmG9/4Rjz77LPRbDbjk5/8ZHzpS1+KiIg/+7M/i2984xvzx/zZn/1Z3/P/4A/+YH5fwM2bN8dDDz101vhDDz3U98s0IiI+/elPx6233jrfUAMAoNhWpA4AsBzuuOOOuOOOO/qOff7zn4/Pf/7zfcduueWW+T+fPn36rD2ILrvssrOOPX78+PzPfvBRq4iIU6dOnbN/0a/+6q+eN+/MzEx85CMfiV/4hV84ZzP/83nzm98cn/zkJ2N0dHRR+5LBsDl+/PiCjaif/umf7vvzgwcPnrX31/333x/3339/RER84AMfOOf4f/kv/+X8n6+44oqzxp544ol4/etff9bPfvZnf7bvdWdn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yZ/8SVx66aXxlre8Jd7whjfEr/3ar0VExF133RX/7J/9s/lPHjUajXjiiSfiy1/+8qLu5wMf+EB85jOfWdQ5AABAeWiIAbBkZmZm4o//+I/jmmuumf9Zr9eL06dPx9zcXPR6vXjppZfmx/7kT/4ktmzZEmNjY+fMdfXVV8fk5GR8+tOfjne84x19r/ftb387/st/+S+xadOmiIj46le/Gn/+538e27dvj0suuST+6T/9p/Hxj398Uffwcz/3c3HmzJl47LHHFnUeAABQHitSBwBgeLzqVa+K9773vXHw4MELOv7EiRPx5S9/Od797nefc87mzZtj//798ad/+qfxL/7Fv4gVK1ac1UyLiFizZk3cdNNNcejQofmf/at/9a/i4Ycfjte85jXxyCOPxBNPPLGoe7j99tvj05/+9KLOAQAAysUnxAB4xTZs2DC/h9jb3va22LFjxwWf+9nPfja2bt16zs83b94cX/nKV+KJJ56IU6dOxfXXXz8/9ra3vS2+973vxf/+3/87Xnjhhbjrrrvmx55++unYvXt3/NIv/VLcfffdi7qPN7zhDXH99dfH5z//+UWdBwAAlIuGGACv2Pf3EHvd614X/+bf/Jv4/Oc/HytXrrygc//bf/tvsXbt2rO+vfFHfuRH4k1velM8+uijMTc3F/v374/NmzfPj39/D7F3vvOdsWnTpnO+De0rX/lKPPPMM/FXf/VXi7qPX/7lX44/+qM/ipmZmUWdBwAAlIuGGABL5uTJk/H7v//78drXvjauvPLKCz7vD/7gD87aXP/nf/7no1qtxpe+9KU4evRovOtd7zqrIfZ9Bw4ciAcffDA++tGPvuLs4+Pj8Q/+wT+I//Af/sMrngsAACg2DTEAlsSKFSviVa96Vfy9v/f3Ym5uLp577rmIiBgbG4tqtRojIyMxNjYWK1acu33lrl274p3vfOf863e84x3x+7//+7F27dpYu3ZtvPGNb4xrrrkmfvRHf/Scc++///54//vfH695zWteUf6/+3f/bhw4cCD+4i/+4hXNAwAAFJ+GGACv2IYNG+I73/lOPP300/HhD384/vE//sdx/PjxeN3rXhdHjx6Nz3zmM3H11VfH0aNH4xOf+MQ553c6nfjyl788//rGG2+M//pf/+v86+eeey6+/vWv9/2U2LPPPht/+qd/Gtu3b39F9/DBD37QZvoA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bIuls+y4FnnllVdUXl6uhoaGrI/9eRs3bky5Ftm4cWO7+z7yyCO67777Tvr6ggULtGPHDn3wwQdatGhRshDetWtXPfHEE3r33Xe1e/du/fGPf0x57+GGaxHALnlVEHv11Vc1atSolK+NGjVKK1euzMr4zz33nI4cOZKVsTIVj8dVVlamG264QcOHD9fMmTNtjSdXVq1ape3bt9sdRkbuvfdelZWVadu2bbr99ttVVlamDz/8sFNjrly5UuPHj9fgwYM1Y8YM/eEPf5BhGJKkXr166bHHHtOFF16ou+++W48++qiGDx+ejV3pkMOHD6uqqirld3D06NHauHFjxovlft57772nysrKTo/TWQ8++KCCwaAWLVqkhQsXpr0L1O3q6+u1YMECu8PIyIcffqiysjLdfvvt2rZtm8rKynTvvfd2asx4PK4HHnhAw4YN06WXXqo+ffrorrvuknTi7qqXX35Zl1xyia6++mqNGTNG3//+97OxKwCADKxcuTLlWqRPnz7q1auX1q5d2+mxnZIfX3zxRfXv318PPPCApkyZon/5l3+xO6Ssc8qxztSIESM0dOhQSdLQoUM1YsSITo85Z84cjRw5UhdccIHeeustzZo1S9KJOyUPHjyob33rW/ryl78s0zSTbQBal1cFsfXr16t79+7JavlZZ52lM844Q+vWrZN04i6bzZs3629/+5tefPFFDR48OKX/3/72N918882qqqrStm3b9K//+q+SThQ4Wrolva0xb7zxRu3cuVNVVVUaOXJku/ajd+/eWrBggWpra7V27dqTihtNTU3aunWr7rnnHn3729/Wqaee2q5+l19+uSorK7V7926tWrVKAwYMaDOWCRMm6PXXX9cHH3ygdevWafz48Snt8+fP1/33368VK1bo3XffTT4O1Vq/mpoaPfbYY/ra176mmpoa1dTU6IwzzpAkXXrppS3e0nv66afrmWeeSR7Pz4/51FNPadasWaqoqNDmzZs79AY4k+PSmk8//VSmaSqRSKipqSnlU71ai7O14/Lmm2+qrq5Ohw8f1t69e3Xqqacm//Lz3HPPqaKiQtFoVJWVlaqpqdGXv/zlTu1DplatWpXycz5q1Ci9+uqrkk7c+v7iiy+qrq5Ob7/9tn70ox/J5/Mlt508ebKefPJJPfPMM3rnnXe0efNmnXLKKSoqKmrxca62xjz99NO1bNky7dq1S48//ri6du3arv249dZbtXHjRm3fvv2kO34k6eDBg5o3b55WrlypiRMntqvfgAED9Kc//Um1tbWqqak56XcpnS5duugPf/iDduzYoffff19z587VaaedlmwfNmyYNm7cqHvuuUdvv/22tm3bppEjR7bab/LkyaqpqdG5556rcDismpoaTZs2LTnm888/3+IjthMmTFBVVZV27typZ555Jnnn3znnnKNt27ZpxowZWrt2rSorK3XWWWe161hL0n/8x3/ogw8+0NKlS1VWVtbufi0xTVNNTU1KJBIyTVOffvppm3G2dlwOHjyoN998Ux9//LEOHjyoeDyePNe/8cYbmjt3riKRiHbs2KElS5bY9vuXS9dcc43effddvffeexozZkzy6+eee65eeeUV7dq1S5s3b9YNN9xgX5A50tK1gSQNHDhQa9euVV1dXcr5/JRTTtGzzz6rnTt36v3339eMGTPsCN2RWrpukDie2VZUVKQFCxYoEono2WefTXnsZ8qUKdq2bZtqa2u1bNkydeni6g+eT7Fq1aqT/jj3xhtv6OjRoxnnVanl/NjWmFJmee5rX/uaXn31Ve3atUsLFy5MXhN+5pNPPtGqVav0q1/9Srfffnu7+p122mmaNWuW3nvvPe3YsUM//vGP24zDadcigwcP1ksvvaS6ujq99NJLKe+93nnnHU2bNk0rV65UTU1NSr5qTVvv5zLR2NioxsbGk/7fWpzDhw9XTU2NJk6cqDvvvFM1NTVaunRpsl9lZaX279+v+vp6RaPRZJxHjx7VtGnT9N577ykajWrevHmuvBbJ9H1nz5499Ze//EUffPCBfvOb3+jtt9/W+eefL4n8gbblVUGssbFRa9euTSbB0aNH6/XXX08WJPbv368JEybonHPO0Ysvvqi5c+eeNMY//dM/6etf/7rOO+88VVRUSJL+67/+q8Vb0tsac/DgwRoyZIhmzJihuXPntusN+WeJ4dxzz9W0adM0b968tP22bdsm6cQbkrb6DRgwQHPnztXUqVPVv+EcvEYAACAASURBVH9//du//Vu7nodOJBK666671K9fP/3whz/U008/rR49eqRsc+ONN+r222/X+eefr5/85Cdt9isvL9c999yj9evXq7y8XOXl5dq/f7+kE7dOt3TL9o9//GN17dpVQ4cO1Q9+8AM9+eST6tWrV7L9/PPP17e+9S1dccUVuueee1RSUtLm/mV6XDqjpThbOy6SdNddd2nHjh3661//qlWrVmnXrl0njV1YWKhBgwbprbfeyuk+tOSLf5X9fEGssLBQv/3tb3Xuuefqyiuv1A033KCrr746pf8111yjP/7xjxo6dKjGjx+vTz/9VA0NDS0+ztXWmJdddpkeeughBYNBnX/++br55pvb3IfPvi833HCDLrzwQg0aNKjFfps3b9awYcPa7BcIBPQ///M/euWVV3Teeefp6quvbtfdpj6fT+vWrdPw4cM1bNgwBQIBPfDAAynb9O7dW8XFxSovL9fFF1+s2traVvvNmTNH5eXl2rZtm2655RaVl5frZz/7WXK8m266Ke0j1D179tScOXP0gx/8QEOHDlXXrl1TYunZs6fWrFmj0aNHa926dSkX560pKytTbW2tBgwYoHfeeScnj0d8XktxtnVcpBPn3Lq6Oo0bN07PPfdc2vGDwaCj1mPMlsGDB+vCCy/Ur371q+R5Xjrxpnr58uXq37+/xowZo/fee8/GKHMn3bWBJI0ZM0bf+ta3NGHCBN13333J8/l3vvMdGYah8847T8OGDdOyZcvsCt2R0l03SBzPbBszZox++ctf6oILLtCQIUN02WWXSTpxHfLd735XI0eO1KBBg/TII48okcifD51/8803VVJSoj59+kiSRo4cqVWrVknKPK9KLefHtsbMJM/17NlTzz//vB599FENGjRImzZt0i9/+cu0237+WqStfjNnztRpp52mf/zHf9SFF17YrnU8nXQtIklPP/20li1bpoEDB6qiouKk916BQECXX365pk6dmnJ+aU173iNmW7o433zzTZWXl2v+/PmaPXu2ysvLdc0116T0e/bZZ1VXV6dHH320xXUC3Xotkun7zocffli7d+/WwIEDtXnz5pQ/ypI/0Ja8KohJJx6b/OwvOV98XHLevHmqq6tTc3OznnvuOQ0ZMuSk/k888YQ++eQTJRKJZAJsTVtjPv300zJNU4sXL1YikdAFF1zQ6ngDBw7Uueeeq5kzZ6qpqUkrV67UgQMHkonu8xKJhBoaGnTqqae22e/6669XRUWF1qxZo+PHj6u6ulrvv/9+m/u3bNkyVVdX6/jx49qwYYNqa2tP+qvJCy+8oL/97W+SpJ07d7a7X0ddeeWVeuqpp3Ts2DGtX79e1dXVKX/5qaioUFNTk/bt26ePPvqoXYulZ3pcOiOTOKUTP2uXXXaZHnzwQS1cuDDtxeu0adO0evVqvfHGG9kOu11qamp06qmn6qyzztKZZ56pkpISbdq0SZK0ZcsWVVRU6NixY/roo4+0YsWK5K3kn9m4cWNyfYPdu3en/DUtnbbGfOONN7Rx40YdPnxYzz77rK688so29+HGG2/U7373O+3cuVNHjx7V7373O40bNy7ttkePHk3eodlav6985SsqKCjQnDlz1NjYqD179rTr8c+mpib99re/VSwWk2maWrRo0UnHTDrxl+dEIqGjR4/qo48+ane/jhgzZozeeustrV+/XseOHdOcOXNS1mM5duxYspBdVVXV7jstGxsbNXfuXDU2Nup3v/tdu75HnZFpnJJ0ySWX6Jvf/KZ+//vf69133z2pfcKECRowYECLH+bgZv/93/8t0zRVUVGRcsyam5vVv39/9ezZUwcPHtTbb79tY5S509K1wZ/+9CdFo1Ft3rxZH374YfJ83tzcrF69eunss8+WaZp6/fXX7QrdkdJdN0gcz2xbv369Nm3apFgsptdee00DBw6UdOJ4nnrqqcnrstdee03Hjx+3M9Ss+vTTT7V69erkH+g+/8e5TPNqa9oaM5M8N378eG3YsEErVqzQp59+qt/85jctroF29OhRFRYWqkuXLq3269Kli6677jr95Cc/0eHDh3XkyJGUu48y3b/PWHEtUlZWpvPOO09PPvmkmpqa9NRTT2nQoEE688wzk9v8+c9/liRt2LCh3Tm+Pe8Rsy2TOKUTH5Y1duxY/eY3v0kWej/v7LPP1p133qmHHnooa7FaJdP3nVdddZVmz54t0zQ1b968lKdzyB9oS/7cH/3/vfrqq8mPXx41apSeeOKJZNt1112nO++8U927d5fP51NBQYH8fn/KRcDu3bs7NF9bY0aj0eS20WhUPXv2bHW8M888U4ZhpHyE7SmnnKIePXqcdIeaz+dTUVGRjhw50mo/6cRfbiKRSIf2TZIuvPBCTZs2Tf3795fP51OvXr1Oenws3TFrT7+O6tmzZ8odU/v37085nocPH07+v6mpqV0fOJDpcemMTOKUTlzwHD16VHPmzNH69eu1efNmbd26Ndl+880366tf/epJf0myUnNzsyorK5MXoZWVlcnfhR49emjGjBkaPny4unTpom7dup1UOOjo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OXtMLYvFolatWmnUqFH6r//6Ly1YsMDvfKdPn9abb76pxx57TK1atVJSUpLGjh3rt5ZoOhYpKSnR3r17Pec7U6dO1b59+zxXh4UikPO59u3ba968eZ5xmwORkJDgOR44+/8D8e2339bZLr169dKdd96pLl266LzzztNjjz2moqIiHT58WFarVYsWLdKOHTu0cOFC2e32oM4/okWo553vvfeepk6dqubNm2vChAle25r8gD9x2SG2du1ajRw50isA9+zZo+XLlysnJ0d///vfPZeWBmLz5s1yOp1q166d/vnPf8rpdKpfv34BLTM/P1+7du3SjBkzNHnyZM8l09KZXu8vvvhCe/fu9fSA19TU6M4779Tw4cO1e/dubd26Vb/4xS88v2y1atVKxcXFev3117Vp0yY98MADAc23b98+TZkyxfPl+eyzz3r1sLvdbj366KPKzs5WcXGxZzDC+++/X4sWLdLKlSs1btw4rw69hvibb8uWLdq0aZO2bdumnTt3em69eumll+R0OjV69Gg9+uijcjqdnoHJn3jiCZ0+fVq7d+/W/PnzNW3aNL/jOvjjb7tIZwb29DWGW33mzZunkpIS9enTRwsWLFBJSYnniZD+1Ldd+vXrp9WrV2vnzp2aOHGi7rrrLk9w14bjp59+qpKSEpWUlOjBBx8MuN5wKykpUUVFhU6fPu11m+BTTz2l22+/XWvWrNG8efMCfvz5lClT5HQ6NXPmTN1www1yOp16/vnnA1rm2rVrNWvWLG3btk27du3Sq6++6tU+Z84cPfDAAyouLvY8svz999/XggULtHz5ch04cEBZWVlevzTNnj1bTqdTv/zlL3XLLbd49kND81VUVOjWW2/VyJEjlZ+fr3fffbfOE6iysrKUmpqq4uJizZs3T5K0dOlSJSYmau3atXrjjTcCGvw20Pmee+45TZ8+Xbt27fIMZDtgwAA5nU699dZb6tKli5xOp+e76fDhw5o2bZrmz5+vXbt26fTp02EZAL+kpEQ9evTQ/v371a9fvzoD9V500UW64447Av6ltkuXLiopKdGCBQvUp08flZSUeLZnIHxtl+rqas2cOVPbtm3T6tWrtXv3bs2cOVPSmXErevbsqYULF3o+f2+99VbA64smvjLJnyFDhujTTz/Vl19+qbFjx9bZf2bVrVs3LV++XEVFRXrppZc0depUr/xH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JORlnHu84U+4878hdrtdR48ebbDzoqHv+vXr19c53m0KgWb1uXncpUuXOsd2zz33XMDr9fX6evTooezsbBUXF6ugoEB/+tOfZLfbJUm9evXSsmXL9OWXX2rv3r168sknvU5amyL/58yZo7y8PB09elQDBgwI23Lrw/lj/ep7fUuXLtW+fft08OBBrVixwvN5S0hI0Pz587V7924dOHBAr7/+urp06eKZrymOUy677DK9/fbbOnTokJYuXRq25TakQ4cOOnLkSFDfg9GmMeeyt912mz777DOvHElISNCLL76oQ4cOafPmzV59CpK0bt069enTR3369GlU3eEWdx1igViyZIl++OEHo8sIyZAhQ3TxxRdLki6++GINGTKk0ctcu3atxowZowsvvFBz5szRq6++6rkiIykpSdOnT9cll1yiN998U0uWLInILw6zZs3S3r17m3w9gfjwww9VUFBgdBkhWbFihTp37qxFixZp0aJF6ty5s1asWNGoZRYUFOjOO+9Unz59dOutt+qee+7RoEGDJEmdOnXSn/70J1166aX6j//4Dz3++OO6/PLLw/FSfIrk+o4fP67c3FwNGzbM87fhw4dr8+bNKisra/Tyjx07FrEQj3Vr16712g/nn3++OnXqpA0bNjR62eyHwK1bt85rP0hnrhRYu3ZtWJYfy1kdSZ988onat2+vCy64wPO3oUOHhq1DLJYzMJKaOiP27NmjnJycRi8HZ8Tyd/2hQ4fUuXNnTZkyRfn5+ercuXOj7w4oKyvTH/7wB/Xv319XXHGFzj//fE2fPl3SmSuT3n//ff3kJz/RddddpyuvvFK//e1vw/FS6rVr1y5NnTpVx48fb9L1BCOWM6kpzh9ffPFFDR06VJdccom2bt2qzMxMSWeu8jly5Ih+8YtfaODAgXK73Z62puJ2u5WVlaW//OUvTbqes40aNUolJSW66qqrIrbOaHLnnXfq73//u9ff7r77bvXp00dpaWl6+umntXDhwjr9BitXrmzwqm4jmKpD7D//8z99XtJ7+eWXy+l06o477tDdd98tp9OpVatWedq7dOmipUuXav/+/dqwYUOdE+6vvvpKEyZMUG5urvLz8/X73/9e0pkrr7Zv366vvvpK77zzji688MJGv4aKigpVVFTU+X9J2rlzpx5++GGtXbtWTqdTV155pafN6XTqT3/6kwYPHiyn0ymn06mOHTtKkr744gsVFRXp+PHjKikpUYsWLTw9+U8++aQ+//xzHT16VC+99JJat27t85aU+nz88ceaMGFCUK/xxz/+sex2u7744oug5gu3K664ot5Lehvanna7XU888YR2796t7du313n9ixcv1v3336/s7Gzt3r3bcxn82LFj9emnn+rgwYPauHGjxowZ0+jXUF1dLbfbraqqKlVVVcntdqu6ulrSmV/N33zzTS1evFhffPGFXnnlFc8lwQsXLtSGDRtktVo9r6+2noKCAu3Zs0dlZWX69ttvVV5e7nlvL1myRKtXr5bL5VJOTo6cTqcGDhwYcL2ZmZlBhWZj1xesDz/8UEOHDvX8e9iwYZ6TztTUVL3zzjsqKirStm3b9OCDD8pisXimnTZtmv7yl79o4cKF2rlzp7Zv364f/ehHkqTXXnvN560kzZs316uvvqp9+/Zp7969evnll9WyZUuvaZ5++mkdPHhQq1atUufOnQN6HYMHD9a6dev05Zdfavny5Z73bkMCqSVSPvzwwzonnZ9//rlOnDjht87+/ftr8+bNuvfee7Vt2zbl5+d79mmk94O/bKmPxWLR9OnTlZeXp6KiIr3xxhuGXM7/2WefqV27dp487datmzp27KiNGzdK8p+B9WVnfVkdyDJvu+02FRYWKjc31+uz2pBQ90NTfGeHory8XJ999lmdz0Ttd5O/OuvLpIYy0N8y+/fvry1btig/Pz/g8UD8ZWdDQllfU2iKjEhMTKz3Nil/y2zVqpXWrFmjL7/8Us8995wSEhICeh2TJk3S5s2bVVBQUOdqoIaEur5Iq++7ftq0aXI6nerTp4+ysrLkdDr18MMPe9obys76sqWpstPtdquyslI1NTVyu906ffq0JKlnz57Kz8/XnDlztGHDBuXk5Khbt25+X9+RI0f0xRdf6Pvvv9eRI0dUVlbm+X79/PPP9fLLL6u4uFj79u3T22+/HdSx1oQJE/Txxx8H9fqWLFmiTZs2qabG+Ge/mfX80d/ry8nJ0eHDh3Xs2DG5XC5PnSdOnNDDDz+sPXv2yOVyadGiRUEfmwd7/rhz5069+eabPu9saSqjRo3SX//6V7Vq1UoXXXSRV1t9udqhQwf94x//0MGDB/X8889r27ZtnnlfeuklTZo0ybOMTz/9VJdffrlSUlK0bds2bdiwQatWrdL//u//au/evZ7Mbyg7X3rpJWVmZmr16tXavn27V8d5Q+eyP/rRj/TKK6+osLBQe/fu1Zw5c7xeX9u2bdWvX7865+o///nPtWDBAn333XdaunSpTp48qZ/85Cde03z66acaOXJkSNu8qZiqQ2zevHk+LxH/4osvlJ6ersWLF+uFF15Qenq6rr/+ek97bWj06dNHDz/8sBYtWlQn5K+99lr99Kc/Vd++fbV69WpJZy7HHjt2rHr27Kl33nmnwXtmw8Vms2nkyJGaOXOm/vjHP3r+np6ernvvvVefffaZ0tPTlZ6ersOHD3vap0+frn379umjjz7Shx9+qC+//LLOsi+55BIdP348qEei9u7dW23btg14+mbNmmnOnDlhu+WmMdavX1/vpewNbc8//OEP6t27twYNGqSxY8fqgQceqPNFedttt2nKlCm66KKLPPuppqZG06dPV/fu3XXPPfdowYIFat++fZO+xssvv1z33XefBg8erJSUFI0YMUKSdNddd2n48OGqqqryvL41a9Z45vvxj3+s/Px85efny2az6b333quzbLvdrtTUVG3dujXgerp27aquXbuG9FpCWV+wzr0y6eyTHbvdrj//+c/q06ePrr76at1yyy267rrrvOa//vrr9frrr+viiy/WmDFjPAewt99+u8/biCwWizZu3KjLL79c/fv3l81m0x/+8AdPe+fOnbV//3716tVLO3fuDOhWvw4dOui1117T448/rtTUVG3ZskVPPvmk3/n81RJJX3zxhZKTk3X++edLOnM1zIcffhhwnV26dFFSUpLS09N12WWXeb7TIrkfpMCyxZfbb79dv/rVr3TTTTepd+/eev3119WsWeTjvKKiQhs2bPB8JoYPH65PP/1UbrdbUmAZ6Cs768vqQJZ54YUXql+/fpozZ45efvnlgLZnqPvBiO/s+qxbt87TEdOtWze1a9fOc5tjIHX6yqSGMtDfMkeMGKGrr75aY8aM0YMPPlgnA30JJDvrE8r6mkJTZER5eXm9t0n5W+aIESP00EMPKS0tTRdddFFAJ5hXXXWV7r33Xt1yyy269NJLlZqaGvCJaSjrM0J93/Uvvvii0tPTlZ+fr4kTJyo9PV2zZs2SFFh2+soWI7KzQ4cO+vjjjzV8+HBt3LhRU6ZM8fv6auXn56uoqEijR4/WkiVLfC4/LS0tqPHX2rZtq969e4f+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lXRm/+3Zs0dut1vt2rUL+BaThrRq1UoHDx7U0aNH1bx5c916661e7QkJCbrrrrtks9n0m9/8ps5Ybvv379fgwYO9wm7NmjUaOXKkhg0b5rmyw9dYKsHWEmm1txPcfPPNXiedTVFnOPbDud/1oWaLdOY75p577lGPHj1ks9l0/fXXG3ZlknSmI+bBBx/0+jwEkoHBCmSZkydPlt1u14033qiamhqvjpFw7wd/39mRtm7dOt18882yWCxet582RZ3+lnnzzTerQ4cO6tWrl0aNGqX333/f01ZfloWanf7WF2mRzoiGljlo0CANHDhQLVu21IQJE+p8NxUWFtZ5+MSyZcv029/+Vt27d5fFYtGFF17o+bXfH3/ri7SEhASv47tAfhSWzlwlde75QFNmp6+s9qf2eNVisQR97OPr9V199dW65ppr1L59e/Xt21f33nuvNm3aJOnMDw3Lly/XrFmztHXrVtnt9qDOPyZOnBj0DwY2m81zfH32/zclzh998/X6evXqpTvvvFNdunTReeedp8cee0xFRUU6fPiwrFarFi1apB07dmjhwoWe7RmMYM4fJXk+B1ar1evcpyl07dpVffv2VXp6ujp37qzOnTvr1VdfDegOp/fee09Tp05V8+bNNWHCBK/9UFhYqMsuu0ySNHDgwIDHVG5MdtZ3Ljt06FDPrbOnTp3S6dOnvW7hP3r0qHbt2lXnQRFvv/22Jk+erDZt2mjcuHFKSkqq80CNcD6NPFziskPs2WefVUlJiee/3/3ud5LOjM3gdDrVrl07/fOf/5TT6fS6L/uNN95QSkqK9uzZo3feeUfSmV7xO++8U8OHD9fu3bu1detW/eIXv/D71JM9e/Zo+fLlysnJ0d///nefvaAbN27UF198ob179wYcFBs3btTOnTslnblFIpiA2bJlizZt2qRt27Zp586dnidJ9OvXT6tXr9bOnTs1ceJE3XXXXZ4v4oyMDM/AoLXb89ynRTSke/fuXr3f0eiWW27xer/UXmb60ksvyel0avTo0Xr00UfldDq9Boitb3vOnTtXLpdLGzduVEFBgWbMmBHQl/L999+vRYsWaeXKlRo3blydX7jdbrceffRRZWdnq7i4OKCBGMeNG6eSkhJNmjRJkyZNUklJSVBjdM2ePVtvv/22du7cqbFjx0o6czXMwoULtWfPHs2fP19//vOfPdus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" - } - }, - "cell_type": "markdown", - "id": "3f8481ce-adb3-450a-be4b-31b1b131af12", - "metadata": {}, - "source": [ - "![non-funky_ascii_tree.png](attachment:0d8393a6-fb00-4364-8f2f-319295075948.png)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "d53ada78-86da-4b8d-9d14-cc1337e17999", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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wgWFhobq9OnTl+wXFBSk48ePy9vbu5gqAwAAAFAKLOCdlAAA4LK8vLz00EMPXTJ89PLyUq9evQgoAQAAABQaISUAACiQXr166ffff8/3+oULF/Twww8XY0UAAAAASguOewMAgALJzc1V1apV9csvv7i9XqlSJaWlpTneWQkAAAAABcRxbwAAUDAeHh7q3bu32+Pc3t7eeuyxxwgoAQAAAFwR/ksCAAAUWH5Hvn///Xf16tXLgooAAAAAlAYc9wYAAIVy8803a8+ePU5tN954o/bv329NQQAAAABKOo57AwCAwundu7e8vLwcv/f29tbjjz9uYUUAAAAASjqepAQAAIWye/du3XLLLU5tO3bsUJ06dSyqCAAAAEAJx5OUAACgcG6++WY1aNBANptNNptNDRo0IKAEAAAAcFUIKQEAQKE9+uijstvtstvtevTRR60uBwAAAEAJx3FvAABQaEeOHFGNGjVkjNHPP/+s6tWrW10SAAAAgJJrgafVFQAAUBg2m83qEnCRGjVqWF0C/g//7xkAAAAlFSElAKDEiYuLU3R0tNVllHlffPGFbDab7rvvPqtLKfPWr1+viRMnWl0GAAAAcMUIKQEAJU50dLR69OhhdRllXl44GRISYnElkERICQAAgBKNkBIAAFwRwkkAAAAA1wpf9wYAAAAAAABgKUJKAAAAAAAAAJYipAQAAAAAAABgKUJKAAAAAAAAAJYipAQAAAAAAABgKUJKAAAAAAAAAJYipAQAAAAAAABgKUJKAAAAAAAAAJYipAQAAAAAAABgKUJKAAAAAAAAAJYipAQAAAAAAABgKUJKAAAAAAAAAJYipAQAlGqBgYGy2WwF+pk5c6YOHTrk9trixYud5h0+fLhLn9TUVB0/ftyp7c4779T58+dd6rq4n81mU1RUlNt72Lhxo/r27avw8HD5+fmpYsWKqlevnh588EFNmzZNe/bscfS94447Cny/NptNo0ePznefxo8f77aeTZs2qW/fvrrpppvk6+urChUqqFGjRnrttdeUnp5+2Xu90j358MMPna4FBga6ra+kGTZsmNN9NWnSxOqSAAAAgGJHSAkAKNUyMzP1ww8/SJI6deokY4zbnxYtWkiSqlevLmOMPv74Y0nSCy+8IGOMOnfu7DTv6NGjHePeffddGWMUERGh0NBQGWO0ceNGSdKWLVsUFxfnUlde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Gj16tKQ//mz/3N6tW7crujcAAACgSBkAAFCmZGRkGF9fXzNr1ixH26effmokmTFjxpiTJ0+aX3/91bz11lvGw8PDPPfccy5zdOrUyUgyLVq0MGvWrDFnzpwxGzZsMHa73fz6669m165dpkKFCqZatWrm888/NxkZGebHH380rVu3NpUqVTI+Pj5O8/Xr18+UL1/efP7552DNWwQAACAASURBVObs2bMmLS3NPPfcc0aSWbNmTVFviZPExETHXhRUZGSkKV++vLn33nvNunXrTEZGhtm4caNp0KCB8fb2Nl999ZWj79GjR014eLipUqWK+fTTT82pU6fMjh07TNeuXY3NZjPvvvuuo6+7fdy2bZtp27atuemmm1z28f777zceHh5m9+7dLjVGR0ebuXPnXsGOAAAAAEVuPiElAABlTFJSkrHb7ebYsWOOtk8//dS0bNnSpW/v3r2Nl5eXOXXqlFN7Xki5bNkyt2t0797dSDILFy50aj927Jjx9/d3CdfCw8NN06ZNXeapU6dOsYeUxhQ+qIyMjDSSzPr1653at23bZiSZyMhIR1vfvn2NJPPxxx879T1//rypWrWq8fPzM2lpacaY/Pfx8OHDxsfHx2UfV65caSSZgQMHOrWvW7fOVKtWzfz+++8Fuh8AAACgmM3nuDcAAGVMcnKymjdvrkqVKjnaYmJi3B41joyM1IULF7R9+3a3c919991u21esWCFJuv/++53aK1WqpIiICJf+7dq103/+8x8NGDBAGzZscBzx3rFjh1q2bFmg+7qWruTot6+vrxo3buzUVr9+fVWtWlVbt27V0aNHJf2x/5LUoUMHp74+Pj667777dO7cOa1cuVJS/vtYtWpV1alTx6WGtm3bqn79+nr//fd14sQJR/sbb7yhwYMHy8vLq0D3AgAAABQ3QkoAAMqQrKwsLVu2TF26dHFqP3XqlF5++WXVr19fwcHBjvcXDh06VJJ09uxZt/MFBAS4XSMjI0O+vr4KDAx0uR4cHOzSNmXKFH3wwQfau3ev7rvvPpUrV07t2rVzBHpWKGxQmfdezotVrlxZknTs2DFlZWXp1KlT8vX1VVBQkEvfKlWqSJLS0tIuu49587qr++zZs5o6daokaefOnVq9erUGDBhw2XsAAAAArEJICQBAGfLll18qIyNDnTt3dmrv2LGjRo0apf79+2vnzp3Kzc2VMUaJiYmSJGNMgdfw8fFRUFCQzp8/r8zMTJfrx44dc2mz2Wzq06ePvvjiC6Wnp2vx4sUyxqhr166aMGFCIe/y2ilMUHnq1Cm37Xn3W7lyZfn4+Kh8+fI6f/68MjIyXPr+8ssvkqSwsLDL7uPJkyfdrvfII4+oSpUqmjx5srKysvTmm2/qsccecxsOAwAAANcLQkoAAMqQ5ORkNWzYUDVr1nS05eTkKCUlRWFhYRoyZIgqVarkeCLw3LlzV7RO+/btJf3/ceU8aWlp2rlzp0v/ChUqKDU1VZLk5eWlNm3aOL4k/tlnn11RDddKQYPKzMxMbd261antf/7nf3TkyBFFRkbqhhtukCTHU6wX31dWVpa+/PJL+fn5OY5357ePx48f144dO9zW4ePjo4EDB+rYsWN68803NXfuXD399NOFuGMAAACg+BFSAgBQRuTm5mrp0qUuR73tdrtatmyptLQ0vfHGGzp+/LjOnTunNWvWaPr06Ve01pgxY1SxYkXFxcVp1apVyszM1I8//qjHH39cYWFhbsc8+eST2rZtm7KysnTs2DG9/vrrMsaoVatWV1TDtVSQoDIgIECDBg3St99+qzNnzmjTpk3q3bu3vL29NWnSJEe/sWPHKjw8XHFxcVq6dKkyMjK0c+dOPfzwwzp69KgmTZrkOPbtbh//+9//qnfv3m6PgOcZOHCg/Pz8NHz4cLVu3Vo333zztd0QAAAA4BojpAQAoIxYt26d0tLSXEJKSUpKSlJsbKzefvttVa1aVeHh4frggw/08MMPS5LatGmjqKgobdiwQTabTUuWLJEk+fn5uX0PY+3atbV+/Xo1atRI3bp1U5UqVRQbG6uEhASFh4fLbrc79f/6668VERGhhx56SBUrVtRtt92mFStW6N1339WLL75YBLtReO6CyvHjx8tms2nr1q2qUKGCJkyYoISEBIWFhemee+5RcHCwVq9erRYtWjjmCQsL08aNG9WrVy8NGTJEISEhuvvuu3XmzBl98cUX6t+/v6PvxftYuXJl9e3bV4MHD1b9+vWVlZUlm82mfv36OdUaGhqq3r17yxij+Pj44tkgAAAA4CrYTGFeMgUAAEqsZ555RsuWLcv3mHBxiYiI0Llz53TgwAFL67hSEydO1DPPPKMxY8YoISHB6nLyNXv2bE2ZMkWbNm2yuhQAAADgchZ4Wl0BAAAoHkuWLNFDDz1ULGulpaXp9ttv1y+//CIvLy9H+/79+7Vnzx717t27WOooCnFxcZL+CH0lXbdB5fTp03mKEgAAACUGx70BACgDfvjhB+3bt8/tUe+i8ttvvyk2NlYHDx7U2bNn9d1336lnz54qV66cRowYUWx1FIXCfPW7uMycOVNdunRRZmampk+frt9++009evSwuiwAAACgQHiSEgCAMiA5OVnVqlVTVFRUsawXFhamL774QlOmTNE999yjI0eOKDg4WK1bt9ZHH32kWrVqFUsdRel6fKJy8eLFCg4O1u2336558+bJ05O/6gEAAKBk4J2UAACUAfXq1VOrVq301ltvWV1KqVNS3lEJAAAAXMd4JyUAAKXd7t27tX37dr399ttWl1IqXY9PVAIAAAAlDSElAACl3L///W+FhISoefPmVpdSahFUAgAAAFeHkBIAgFIuOTlZDzzwAO8nLGIElQAAAMCV479WAAAoxY4cOaLvvvtOL730ktWllAkElQAAAMCVIaQEAKAUS05OVkBAgFq3bm11KWUGQSUAAABQeISUAACUYsnJyWrfvr38/PysLqVMIagEAAAACoeQEgCAUio9PV1r167Vv/71L6tLKZMIKgEAAICCI6QEAKCUWrJkiSSpffv2FldSdhFUAgAAAAVDSAkAQCkwc+ZMRUVF6Y477nC0JScnq3Xr1qpQoYKFlYGgEgAAALg8QkoAAEqByZMna+vWrapevbp69uypdu3aadWqVZo4caLVpUEFDyr37Nmj2rVrF1tdAAAAwPXCw+oCAADA1fP29pYkHTp0SG+99ZbatGmj3NxcrV27VitXrtTvv/9ucYWIi4tTYmKiXnzxRY0dO9bl+sSJE9W0aVOdO3fOguoAAAAAaxFSAgBQCvj6+jp+feHCBUnS+fPnNW/ePLVr107BwcHq1auXVqxYYVWJUP5B5cSJExUfH69ff/1VM2fOtLBCAAAAwBoc9wYAoBTw8/Nz256dnS1JOnv2rJKSktS9e/fiLAtuXHz028/Pz/FrSfrHP/6h2NhYx9OxAAAAQFlASAkAQCmQX0iZx9PTUwMHDlTXrl2LqSJcSlxcnHJzczV58mTt27fP6dqvv/6qOXPm6IknnrCoOgAAAKD4cdwbAIBSwM/PTzabze01T09P1atXT6+//noxV4VL8fDwcAkoJckYo9dee83xFCwA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F1u8eLHTRzl27NihHj16KCQkxNF2/PhxSX88Lda5c2eVL19e/v7+uvvuu7V06VK1bt3a0bdfv34aPXq02498rFixwtH+51Dp4hoOHDignj17KigoSCEhIerTp49+++037d+/Xx07dlRQUJBuuOEG9e/fXxkZGS739Ouvv2rIkCG66aab5O3trUqVKqlr167asmVLvmvu379fPXv2VIUKFRQSEqKYmBinD5yMHz9eNptNZ86cUUpKimPcn8OmrKwsvfzyy4qIiJC/v78qVqyojh076pNPPnH6uMvWrVv1yy+/KDIy0qX2vGPE1atXd7lWrVo1SdLOnTtdrt1xxx2SpJUrV7pcK20yMzP1zTff6Mknn5S/v7/j2PfV+PrrryXJ7Z/JxfL6fPPNN462tWvXSpIaNGhw1bVMnjxZAwcO1LJlyxQTE3PV8wEAAKD0IKQEAJQoH330kaKjoxUeHi5J6t27t7y8vPTZZ5/p2LFjTn07d+4sY4w6deokSYqNjdXAgQN18OBBbdiwQXa7XdIfx2Gjo6O1adMmLVy4UMeOHdPs2bM1adIkbdu2TT4+Po4nBIcPHy5jjAICApzWateunYwxatiw4SVriI+P1/PPP6+0tDRNnDhRH374oR555BHFxcVp1KhROnr0qF555RXNnDlTI0eOdJrr6NGjatSokebPn6+pU6fq5MmT+uqrr3Ty5ElFR0dr/fr1bteMi4tTXFycDh8+rKSkJK1evVq9evVyzPvcc8857ukvf/mL4yMufz5ePWjQIL311lt6++23deLECf3000+KiIhQp06dnAKtH3/8UZL7IDLv69EX7530x/swJem3335zuZYXYObNXdosWbLEEQznvZMyKytLc+bMUdeuXa96/sOHD0uSQkJCLts3r8+RI0ccbXm/Lsj4S/nyyy81ePBg9e7dW3ffffdVzQUAAIDSh5ASAFCizJ492/H0pCSFhoYqJiZG2dnZmjNnziXHvvDCC2rZsqX8/f3VuHFjZWdnKzQ0VC+++KLS09M1adIktWnTRoGBgapbt64++ugjnTlz5prW/8QTT6hhw4YKCAhQnz59VLduXS1fvlzx8fG64447FBgYqNjYWIWHh7scYU9ISNCBAwc0YcIE/fWvf3XUOW/ePBljNHjwYLdr9uvXT9HR0QoICFDr1q3VoUMHbdy40fEUaUF8+eWXqlu3rtq0aSM/Pz9VqVJFb7zxhurUqePU7+jRo5Kk8uXLF2pfjDGSJJvN5nKtXLlystlsjrlLmz9/3fvChQvau3evHnroIXXr1k0PPvjgNflwjuR+bwvTtzDj3alWrZrKlSunN998U+PHj7+quQAAAFD6EFICAEqMbdu2adeuXXrwwQed2vNCy8t95Tu/p7dWrFghSbr//vud2itVqqSIiIgrLdeti9/HmPfexovbq1Wr5vQ0m/THMW4PDw+XY7JhYWGqW7euNm/erEOHDrms2ahRI6ff16hRQ5Jc5r+Udu3a6T//+Y8GDBigDRs2OI5479ixQy1btnT0O3/+vCTJy8vLZY4KFSpIktvgN68tr8/FPD09de7cuQLXW1J5enoqPDxcr7zyih5++GEtWrRIb7311lXNmffP2IkTJy7bN6/Pn98nmvfrwoTa7kRERGjFihUKCgrS0KFDNWHChKuaDwAAAKULISUAoMR47733lJGRoYCAAKd3Lj7wwAOSpO3bt+u7777Ld7y7Y8ZZWVnKyMiQr6+v48jxnwUHB1+7G9AfTwX+mYeHh+x2u/z9/Z3a7Xa7cnNzneo8deqUcnNzVb58eaf7t9ls+v777yVJu3btclnz4qcavb29Jclp/suZMmWKPvjgA+3du1f33XefypUrp3bt2ik5Odmpn6+vryS5ffovL/B1F6TmHUm++MnMPNnZ2U5fBC8L7rnnHknuPyZUGC1atJAkp/eW5mfr1q2S5BQ8543ftm3bVdUh/fFxneXLlyswMFDPPvusJk6ceNVzAgAAoHQgpAQAlAgXLlzQ3LlzlZKS4jga++efuLg4SZd/mvJiPj4+CgoK0vnz55WZmely/eL3XObx8PDQ77//7tKe997Fa83Hx0cVKlSQp6enLly44HYPjDG69957r3iNSx3ntdls6tOnj7744gulp6dr8eLFMsaoa9euTk/E3XDDDZKkU6dOucyRV9vmzZtdruW13XfffS7XTp8+LWOMY+6yIu8I/NmzZ69qntjYWHl6emrBggWX7Ldu3TodOXJEHTt2VM2aNV3GL1y48JLjn3/+eXl4eDg+kJSfv/zlL1q2bJkCAgL0zDPP6O233y74zQAAAKDUIqQEAJQIn376qUJDQ9W0aVO315944glJ0scff1zoY8Ht27eX9P/HvvOkpaW5/dq09EcYl/f035/7//zzz4VauzC6du2q7OxspaSkuFz75z//qZo1azp97Kaw/P39nYLXW2+9Ve+8846kP45h54VPXl5eatOmjeMr4p999pljTL169SS5f1qyRYsWuv3227Vw4ULHsXBJysnJ0bx581SjRg116NDBZVzePufNXVbkfZDo4uP6BeHp6en486pTp45Gjhyp77//XjNmzHDb/+zZs4qLi1NISIjL04154zdt2qT33nvP7fgdO3ZoxowZ6tGjR4FekdC8eXN99tln8vf315AhQzRlypRC3iEAAABKG0JKAECJMHv2bP3tb3/L93q9evV0991369SpU1q0aFGh5h4zZowqVqyouLg4rVq1SpmZmfrxxx/1+OOPKywszO2Ytm3b6siRI5o8ebIyMzO1Z88ePf3006pcuXKh1i6MsWPHqnbt2vrb3/6m5cuX69SpUzp58qRmzJih1157TePHj5enp+cVz3/XXXdp586dOnjwoNavX6+9e/eqefPmjutPPvmktm3bpqysLB07dkyvv/66jDFq1aqVo09kZKQqV67sODb8Zx4eHpo1a5ZOnjypxx9/XGlpaTpx4oSeeuop7dq1S++++67juPif5R1Tbtu27RXfW0mRnZ2t/fv365VXXtFHH32katWqKT4+/qrnHT58uBISEvTUU08pPj5e27dvV1ZWltLT07V06VI1a9ZMaWlpWrlypWrVquV2/LBhw/Tkk09q2LBh2rlzp37//XcdPnxYs2bN0r333qsGDRpo1qxZBa6pRYsWWrp0qfz8/DRo0CBNnTr1qu8TAAAAJZgBAMACkkxSUtJl+x08eNBIcvw0btzYpc++ffuc+kgyVapUMevXr3dpz+9ffTt27DCdO3c25cqVM/7+/qZp06bm66+/Ni1btjT+/v4u/dPT002/fv3MDTfcYPz8/EyzZs3Mxo0bTcOGDR3rvPDCC25reOmll8zGjRtd2seOHWu++eYbl/aRI0c61j1x4oSJj483tWrVMl5eXqZSpUqmbdu2ZtWqVY4++a2Zt+9//unQoYNjXGpqqmnevLkJCAgwNWrUMFOmTHFc27Jli4mNjTW33Xab8ff3NxUrVjRNmjQx7777rsnNzXXamxdffNF4enqaw4cPu93r77//3rRv396UK1fOBAYGmlat/pe9O4+rss7///88cNhk01xRScUdFATcwa2cHNNqaiRrNFvna01NmqXZx3a1zSbD9pm2SVvUppqxslIzDQNFREwFNc1RBxR3cQOB9+8Pf+fkEZTFAxfL4367nRtyneu63q/3OYeX1/U61/V+X2GSkpJKXdcYYxISEkyrVq1MQUHBBde5kPnz51/wPXeXUaNGmVGjRlVoG39//1I/mzabzQQGBpqoqCgzZcoUs2/fvnJtV9ojMzOzRLupqanm1ltvNW3atDHe3t4mMDDQ9OzZ08yYMcMcOXKkzLjXrFljbrnlFhMaGmq8vLxMYGCg6du3r0lMTDT5+fnl6uesWbNc1lm6dKnx8/NzPj99+vRyv47lzSMAAACo8RbYjPn/BzwCAKAa2Ww2zZ8/XzfeeKPVoVxUly5ddOrUKf33v/+1OpRa4+jRo4qIiNDIkSP15ptvXtK+MjIyFB0drY8++kg33XRThbdfsGCBRo8erao83ElISJCkMsd8hPvVljwCAACAMi3kdm8AQL23d+9eXXbZZSVmpN65c6e2b9/ucjszyhYcHKxFixbp008/vaSxBnfs2KEbbrhBjzzySKUKlAAAAABqD4qUAABIOnz4sMaPH6/du3fr5MmTWrNmjUaPHq2goCA99thjVodX60RHR2vt2rVavHixjh07Vql9vPXWW5o5c6Zmzpzp5ugAAAAA1DQUKQEA9V6LFi20dOlSHTlyRAMHDlSjRo107bXXqmPHjlqzZk2pE4mgbG3bttWXX36poKCgSm3//PPPcwUlAAAAUE9UfgpQAADqkCuvvFJXXnml1WEAqKB3331Xx44dU0xMjLp37y4vLy+rQwIAAEAlUKQEAABArfXrr79qwoQJOnnypLy9vdW9e3fFxMQoJiZGsbGx6t69u3x9fa0OEwAAAGWgSAkAAIBaa/r06frjH/+o//73v9q0aZPS0tKUlpamRx99VAcPHpTdblenTp0UGxvrfERHR8vf39/q0AEAAHAOipQAAACo1Tw9PRUWFqawsDBdc801zuXZ2dnOomVaWppmzpyp/fv3y9PTU507d1ZERITCw8MVGxuruLg4XXbZZRb2AgAAoH6jSAkAAIA6qWXLlmrZsuVFC5dvvvmm9u3bJ0kKCQlxueKyX79+atKkiVXhAwAA1CsUKQEAAFBvlFa43LVrl9LS0rRu3TqtW7dOb731lvbu3Subzab27dsrNjZWPXv2dBYvKztjPQAAAC6MIiUAAADqtcsvv1yXX365rr/+euey7OxsrVu3znnF5UsvvaScnBzZbDZ16tTJWbTs2bOnoqOjFRAQYGEPAAAAaj+KlAAAAMB5HFdcjhw50rns/FvFn3/+eeet4mFhYYqLi3NebRkTE6MGDRpYFT4AAECtQ5ESAAAAKIfyjHH5zDPPKDc31zk5z7ljXMbGxsrPz8/CHgAAANRcFCkBAACASipP4XLGjBk6cOCA7Ha7OnXq5FK07Nmzp3x9fS3sAQAAQM1AkRIAAKASDh48qEWLFmnr1q3q1KmT1eGgBilP4fLpp5/WoUOH5OXlpY4dO7oULnv16iUfHx8LewAAAFD9KFICAACU065du/TvCjUU+AAAIABJREFUf/9bn3/+uVauXCkvLy81bdrU6rBQC5xfuCwuLtaWLVu0du1arV27VmlpafrXv/6lkydPys/PT9HR0erVq5d69eql3r17q0OHDrLZbBb3AgAAoOpQpAQAALiAoqIipaamavHixfrqq6+0bt06BQUF6eqrr9bHH3+s3//+97rjjjusDhO1kIeHh7p27aquXbvqlltukXT287Z582alpaUpNTVVq1at0uuvv64zZ86oUaNGzoKlo3gZEhJicS8AAADchyIlAADAOfbv369vv/1Wixcv1rfffquDBw+qTZs2Gj58uGbOnKkhQ4bI29vb6jBRB3l6eqp79+7q3r27brvtNklSYWGhtmzZorS0NK1atUqfffaZnnnmGRUXFyskJMTlNvH+/furcePG1nYCAACgkmzGGGN1EACA+ofbFlGdLna4c+zYMa1cuVLLly/X8uXLlZGRIbvdrvj4eA0fPlxXX321wsPDL7h9QkKCPv3006oIG+Uwf/583XjjjVaHUa3y8vKUkZHhMsbl5s2bJUkhISGKj49XXFwcM4oDAIDaZCFFSgCAJRYsWGB1CKhHzi1inTx5UklJSc6iZFpamoqKihQREaErrrjC+QgMDCzXvpOTk7V7926XZUVFRVqyZIkWLFggb29v3XXXXerZs6db+1QffPDBB/rmm28UGhqq22+/XV26dCmxTv/+/dW6dWsLoqtZcnJynGNbpqWlKSUlpdQZxePj4xUdHS0PDw+rQwYAADgXRUoAAFC3HT9+XKtXr9bKlSv1/fffa82aNSooKFDnzp01ZMgQDRkyRIMHD1azZs3c0t7y5cs1YcIEZWVl6Z577tH06dMVFBTkln3XR1u2bNGkSZP09ddfa+TIkXrllVfUtm1bq8OqFRwziq9atUpJSUlat26dTp06pYCAAEVFRbncKh4REWF1uAAAoH6jSAkAAOqWffv2KSkpSUlJSVq1apXS09NVWFiosLAwDR48WEOGDNEVV1yhli1burXd3bt3a9q0aZo7d66GDh2qxMTEi94mjopZtGiRHnjgAeXk5Gjy5Ml6+OGHuY25gs6cOaMNGzZozZo1Sk1N1Zo1a5SVlaWioiKFhIS4zCbeq1cvNWrUyOqQAQBA/UGREgAA1G47duxwFiSTkpKUmZkpDw8Pde7c2Tk238CBA6vs6ruTJ0/qhRde0PPPP6/Q0FDNnj1bI0aMqJK26rszZ87o9ddf1+OPP66goCDNnDlT48aNszqsWu348eNav369y/iWmZmZMsaUGN+yV69e8vHxsTpkAABQN1GkBAAAtceJEye0du1apaSkKCUlRT/99JNyc3PVoEED9e7dWwMGDFBcXJz69etXLbdYL1q0SH/961918OBBPfjgg3rkkUco4lSD7OxsPfXUU3r77bc1cOBAJSYmKjIy0uqw6ox9+/ZpzZo1Wr16tVJSUpSamqpjx46pQYMGio2NVZ8+fdSvXz/16dNHrVq1sjpcAABQN1CkBAAANZMxRlu2bHEWSpKTk7Vp0yYVFhaqVatW6tOnj+Li4tS/f3/FxsbKy8ur2mJLT0/XhAkTlJSUpLFjx2rWrFlq3rx5tbWPs1JTUzVhwgStXr1aY8aM0d/+9jc1bdrU6rDqJMcVy44xLtevX++8TfzcSXn69++vBg0aWB0uAACofShSAgCAmiEvL08ZGRnO27YdsxN7eXkpMjLSecuplZN8HDx4UE8//bRee+01xcTEaM6cOerbt68lseAsY4zmzp2rKVOmqKCgQE888YTuvfde2e12q0Or0869TXzVqlVauXKl9u3b5zKbuONW8a5duzKbOAAAKAtFSgAAUP1OnTql9PR0paamOh/btm2TMUbt27dX37591adPH/Xp00fR0dHVepVkaRxjIT7xxBPy9/fXs88+q1tuuUU2m83SuPCbEydOaNasWXruuefUrl07vfzyyxo2bJjVYdUr588mnpaWptOnTysoKEjdu3d3Fi379eunJk2aWB0uAACoWShSAgCAqlVYWKiNGze6FCQ3btyowsJCNW7cWL169VLP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QVaHCwBATUGREgCAshw+fFgbN27Upk2btGHDBm3evFk///yzDh06JElq3ry5unfvroiICOfs2hERERTKqtiWLVs0adIkff311xo5cqReeeUVxoRDtVm+fLkmTJigrKws3XPPPZo+fToFJ1zQ+eNabt68WXa7XVFRUYqLi1N8fLyGDBmiJk2aWB0qAABWoUgJAIBDQUGBtm3b5jKJzebNm/Xrr7/KGOMykY3jdu2ePXsqJCTE6tDrlSNHjui5557T7NmzFRERocTERA0YMMDqsFAPFRYW6t1339W0adPk5eWlJ598UnfddZc8PDysDg01XE5OjpKSkpyFy/T0dBUXFyssLMxZtPzd736ndu3aWR0qAADVhSIlAKD+KSws1K5du1wKkZs2bdKWLVtUVFQkb29vdejQwWXcyIiICLVr146JbCxUXFysefPmafLkySosLNTjjz+u++67T56enlaHhnru0KFDeuqpp/Taa6+pR48emjNnjvr37291WKhF8vLytHr1amfR8scff1R+fr5CQkKct4fHx8crOjqaIjgAoK6iSAkAqNuys7NLXBm5adMmnT59Wna7XZdffrnLlZGxsbHq0qULha8aZs2aNbr//vuVlpamO+64QzNnzuS2SNQ4mZmZmjhxopYsWaJRo0bpb3/7m0JDQ60OC7XQyZMntW7dOuft4UlJSTpy5IiCgoLUu3dvZ9FywIABzDQPAKgrKFICAOqGw4cPlyhEZmRk6Pjx45KkkJCQEldGhoeHM25kDfe///1PjzzyiObNm6chQ4YoMTFR3bp1szos4KIWLVqk+++/X/v379dDDz2kRx55hEISLklRUZGysrKcRcsffvhBu3fvVoMGDRQdHe282nLAgAFq2LCh1eECAFAZFCkBALXLvn37tGnTJmVmZjons9m4caMOHz4sSWrZsqVzAhvHz/DwcPn7+1scOSri1KlTmjNnjmbOnKlmzZrp2WefVUJCgtVhAeXm+AzPmDFDLVq00DPPPMNnGG6VlZWlpKQk/fjjj0pKStKOHTtkt9sVGxurwYMHa9CgQYqPj1dgYKDVoQIAUB4UKQEANVNOTo42b97svCoyMzNTmzZt0sGDByVJl112mbp166bw8HBFRkYqIiJC3bp102WXXWZx5LhUixYt0oQJE7Rv3z5NnjxZU6dOla+vr9VhAZWyZ88e/d///Z/m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Y8cOBQcHV2NkQP315Zdf6pprrrng8x4eHvL19dXq1avVrVu3aowMcL+CggIlJyfr+++/17Jly7RmzRqdOXNG3bp10+9+9zsNHTpUgwYNuuRju927d8vHx0fNmjVzU+QAgAqgSAmg7jLG6Msvv9SUKVO0detWGWPkSHnr1q1TdHR0ufdV2izajuKkJHl7e6tDhw4lipFdunThFm3UaosXL9bIkSNVXFx80fXsdrseeOABvfDCC9UUGVB/FRQUqEuXLtq1a1epVzc72O12NW/eXGlpaWrevHk1RghUrePHj2vlypVatmyZlixZoo0bN8rLy0v9+vVzFi0rMwnP888/r1mzZumdd97RddddV0XRAwAugCIlgLpp6dKlmjx5stavXy8PDw+XAouHh4c+/PBD3XTTTS7b5Ofn65dffilRjMzMzNTJkyclnb1F+9wipONn27ZtmUkVdU56erri4uKUn59fZpFSOlsQ2bx5szp27FgN0QH116xZszRlypRyrevl5aWoqCj9+OOP8vX1reLIAGvk5uZqxYoVLpPwBAQEqG/fvs5JeGJjY8vcz6BBg/Tjjz/KGKPbbrtNiYmJTAIHANWHIiWAuiU1NVVTpkzRDz/8ILvdXupkAt7e3rrpppvUp08fl7Eid+/eLensCV379u2dY0V26dLF+W9uZUV98b///U8xMTE6dOhQqX9H0tmCv91uV0FBgSSpadOmmjJlih566KHqDBWoVwoLCzVixAitXLlSp0+fLvF3WBq73a7rr79e8+fPZ5gR1AvnTsLz3Xff6ejRo87xLEeOHKkRI0aocePGLtucOHFCjRo1co7x6uXlpaZNm+rDDz/U4MGDLegFANQ7FCkB1A1btmzRtGnT9Nlnn8lut5c6iYCDh4eHbDabAgMD1b59+xJjRUZERHC1Ceq148ePq1+/ftq8ebPzCkpPT0/ZbDZnwbJly5bq06ePevbsqejoaEVHR6tFixZWhg3UK0VFRdq2bZvS09O1bt06paamKj09XceOHZPNZpOvr2+Jq6CnTZumGTNmWBg1UP2Kioq0fv16Z9FyxYoVKioqUnR0tPMqy/j4eC1btkwjR4502dbT01PFxcW67777NGvWLPn4+FjUCwCoF0oWKZOTk/XSSy9ZFRAAVMipU6fUokULvf/++/L09LzolSTn6tKlizIzM8tc76WXXlJycvKlhgnUGsYY/fTTT8rJyZF0dlIcf39/XXbZZWrUqJEaNmyohg0bysvLy+JIcTELFy6ssn2TF2u2kydP6siRIzpy5IgOHTqkw4cPKz8/3/l8z5491bZtW+sCBKpZv379NGnSJOfvR44c0fLly7VkyRItWbJEv/zyixo0aKCmTZsqJyen1GNJu92usLAwffLJJy5jmpMPa7dJkyapX79+VocB4DcL7ecv2b17tz799FONGjXKioAAoFyKi4u1Zs0a7dmzR9LZQkp5xsxz2Llzp4wxZd72lpycrJSUFPXt2/eS4gVqi927d8vPz08xMTFq2LChgoODKzzxAKyzZ88epaSkVGkb5MWarUGDBmrQoIFatmzpXJafn68jR47o8OHDys3NVZMmTRQQEGBhlED1KC0fNmzYUNdff72uv/56SWePCZcsWaLJkydf8MvuwsJC7dixQ7169dKjjz6qxx57TJ6enuTDWuzTTz9VQkICRUqghilRpHSoym/gAcAd5s+fr5tuukkLFizQ+vXrtX79eqWlpWnfvn2Szo49aYwp9dbv06dPa/fu3br88svLbKdv377kRAC1woIFCzR69Ogqb4e8CKA2SEhIKHOdtm3b6qqrrtL/+3//76LrOYY7mT59ur7++mt99NFHksiHtRXj8wI10wWLlABQ0zkOLhISElwOQg8cOKD169crIyNDGRkZSk1N1bZt21RUVCRPT0/Z7Xbl5+dry5Yt5SpSAgAAoO769ttv5enpqaKiojLXLS4u1vr16xUZGamuXbsqLCysGiIEgPqBIiWAOqdJkybOgdAdCgoKtHHjRmVkZGj9+vVKT09Xdna2hVECAACgJvjmm2/KtZ6Xl5c8PDxkjNGpU6e0bt067d27V7m5uWrWrFkVRwkAdR9FSgD1gre3t2JiYhQTE2N1KAAAAKghioqK9P3335e4itLf31/BwcFq1KiRGjdurObNm6tRo0Zq1KiRczK5d999V97e3jpx4oRF0QNA3UKREgAAAABQL50+fVrz588vUYD08PAoc9vvvvtOktSuXbuqDhMA6gWKlAAAAACAesnf31/Dhg2zOgwAgKSyvx4CAAAAAAAAgCpEkRIAAAAAAACApShSAgAAAAAAALAURUoAAAAAAAAAlqJICQAAAAAAAMBSFCkBAAAAAAAAWIoiJQAAAAAAAABLUaQEAAAAAAAAYKlLKlKuX79eNpvN5dGhQ4cS6x05cqTEejXJiy++6IyrdevWbl//QgICAkq8Lh4eHmrUqJGioqL0l7/8RWlpaZXef0W5q1/1wbx581zet4CAgGppt7TPzIsvvlhl7RljtGrVKt17773q1KmTfHx81KxZM8XHx2vevHkyxrisf/jwYb355pu64oordNlll8nPz08dO3bUmDFjlJGRUWL/U6dOdelL3759q6wv1YGcSE6sry6UE6vjNSQv1mzkRfJifcWxIjnxQkp7jxwPX19fRUZG6rXXXivx2l1su/Mfa9eulST16NGj3NvYbDbNmDFDe/bsKfW5L774wiWeRx99tMQ6WVlZle7jgQMHXNaLjo7W6dOnS7x+569ns9nUs2dPd79NAKxizjN//nxTyuKLuvPOO40kM23atIuud+2115rnn3++Qvsuj7y8PNOhQwczYsSIS9pPVFSUadWqVZWtX5r09HQjyVx33XX+PmkcAAAgAElEQVTGGGMKCwvN3r17zRdffGGGDBliJJnbbrvNnDhx4pLaqQh39Ks6uev9r4i5c+caSeaNN96otjYdzv/MVKXMzEwjyQwdOtRkZGSYU6dOme3bt5ubb77ZSDIPPvigy/p33nmnsdvt5uWXXzY5OTnmxIkTZuXKlSY8PNx4enqazz///IJteXp6mj59+lQovsrkq4oaNWqUGTVqVIW2ISdWHjnx0tXEnFjVryF58TfkxdKRF92LvFg2jhVrRk6sTL6qjv2X9h7l5+eb9PR0ExcXZySZyZMnl2u78wUHB5vU1FRjzNm/1YULF7o8P378eCPJLF682GX56NGjzfTp052/f/zxx0aSefjhhy/al0GDBpl//OMfbutjamqqkWQkmfHjx1+w3eTkZNO4ceOLxnYxksz8+fMrvT2AKrHALbd733777ZKkDz74QMXFxaWuk5ubq++++0633HKLO5p0YYxRcXHxBduuTTw9PdW8eXNdd911+v777zVlyhS9//77uvnmm0t8m1afBAQEKD4+vtTn6tL7b4WLvbaSZLfbtWDBAkVGRsrX11dhYWF6//331bhxY7366qvKz893Wf+OO+7QhAkT1KJFCzVo0EADBgzQRx99pKKiIk2ZMqWqu1MjkBPdh5xYOnJi1SIvuh950X3Ii6UjL1YdcmL18Pb2Vo8ePfTxxx/Lw8NDs2fP1qFDh6wOy63K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uHTpkp544gmNHz9eY8aM0f79+zV06FCrYwFoIMMwlJGRofLycqujALgCRUoAAAAAqMHu3bvVt29frVixQmvWrFFycrLatWtndSwA1yEmJkYFBQXas2eP1VEAXIEiJQAAAABcpaysTPPmzdPw4cN18803Ky8vT7GxsVbHAtAIevfure7duzPlG3AyFCkBAAAA4ApffvmlIiIilJSUpLlz5+qjjz5SQECA1bEANKLo6GiKlICToUgJAAAAAP8vLS1NISEhOnPmjHJycjR79my5ufFrE9DSGIahffv26dixY1ZHAfD/+L8tAAAAgFavsLBQU6dOVXx8vOLi4rR7927169fP6lgAmkh4eLh8fX21fv16q6MA+H8UKQEAAAC0ajk5Oerfv782bdqkdevWafHixfL29rY6FoAmZLfbNXLkSKZ8A06EIiUAAACAVqm0tFRJSUkaMWKE+vTpo88++0xjxoyxOhaAZmIYhrZu3apz585Jkv79739r06ZNevzxx/X9999bnA5ofWymaZpWhwAAAACA5pSfn68pU6bo4MGDevHFF/XEE09YHQlXGTlypHbt2qUrf2UtKiqSp6dnpeeE2u127du3T4GBgVbEhAs7e/asunTpokcffVRff/21PvzwQ128eFHS5UdAtG/f3uKEQKuS5mF1AgAAAABoLJcuXVJ+fr5CQkJq3Cc5OVmPPfaYevbsqby8PPXo0aMZE6KufvGLX2jz5s1VtpeWljr+22azqX///hQoUS+HDx9WRkaG1qxZo7KyMi1cuFBubm6V+paPj4+FCYHWieneAAAAAFqMZ555RjExMSosLKzy3unTpxUbG6sHH3xQDz/8sHbs2EGB0olNmjSp1pXV3d3ddf/99zdTIri6rKws/fSnP1VQUJCee+455eTkyDRNlZeXVypQenp6yt3d3cKkQOtEkRIAAABAi7Bhwwa99dZb+vrrr/Xoo49Wem/Tpk3q16+f8vLytHXrVi1YsECenp4WJUVd/Nd//ZeGDRt2zUJleXm5Jk6c2Iyp4MpGjBghNzc3ubu7q6SkRDU9/a5t27bNnAyARJESAAAAQAtw+vRp3XfffbLZbCovL9fKlSu1YsUKFRUVKTExUZGRkRo2bJjy8vJ0++23Wx0XdTR16tQa33N3d1d4eLi6dOnSjIngynx8fLRmzZpa75L09vZupkQArkSREgAA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WMDBwUHsGAqTm5sLfX19/PrrrxgzZozYcWptz549YkdoMKZPnw6ZTIZ169aVGpiEaq5///5Ku6tZVd9fjx49QnJyMs6ePYs///wTL1++BAB069YNixcvhkQiUXqG1126dAmff/45hg8fjvfee0/l668LlP39parzb1ZWFhITE5GQkICUlBTIZDJIJBIEBgbC2tpaJRkI8PT0fH1SGIuURERERCq2Z88eeHl5iR2DRDJhwgSEhYWJHUOhmjZtiqCgILz//vtiR6k1MYofRIoUGhpa1o9/heD3V8Om7O8vnn8bljLKkWHSsmYkIiIiIuXj/ytWnNTUVFhZWYkdo1IeHh5iR1CKVq1a4e7du2LHUBhlFnnoXxs2bMCUKVM4AIkCqarII+b31/3799GyZUtR1v39999jzpw5DbKYpqrvLzHPv9nZ2Xj58iWaNWsmyvobior+ZweLlERERERU79WHAqU6a9OmDW7fvi12DKpn3n///QZZ7KHaEatACaDBFigbCg6cIz4OnENERERERLXSpk0b3Lp1S+wYVM+w2EP1DdsskXKxSElERERERLViYWHBIiURERHVCouURERERERUK8V3UvI5q0RERFRTLFISEREREVGttG3bFn///TcyMzPFjkJERET1FIuURERERERUK23atAEAdvkmIiKiGmORkoiIiIiIasXCwgIaGhq4efOm2FGIiIionmKRkoiIiIiIakVbWxtmZma8k5KIiIhqjEVKIiIiIiKqtTZt2uD27dtixyAiIqJ6ikVKIiIiIiKqtbZt2/JOSiIiIqoxFimJiIiIiKjW2rRpwyIlERER1RiLlEREREREVGsWFhYcOIeIiIhqjEVKIiIiIiKqtTZt2iAzMxN5eXliRyEiIqJ6iEVKIiIionrAwMAAEomkzD89PT306NEDX3/9NYqKiip9X1BQEADg0aNHJab37NkTf//9d6l1vz6fRCJBnz59ys2ampoKiUSCfv361XibdHR00L17d/z4448QBAEAkJCQUGo+Q0PDEsuMiIgo8bqrqysAYPv27SWmGxgYlJnJ0tISO3bsKPO1gICAEsuobPsamrZt20IQhBoPnvP6Z1dWW6yLKjrG1EVQUJB821q3bl3itYqOLVXvG0EQEBcXhw8++ABvvPEGGjVqhObNm2PAgAHYvn27/FxS7OnTp/jpp5/g7OyMZs2a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lERERERERHrq0qVLeOONN/DJJ59o3+d54wzgRUVF2q9ZkCQiIimxSElERERERKRnWlpa8Omnn2L16tW4fv06NBoNNBrNTftdvXoVwcHBKC4uxoULFyCTyTB06FAolUqsWrUKSqUSDz74IHr37i3BXRARkSFhkZKIiIiIiEiPpKWlYdmyZTh9+nSHhck/Mzc3x0svvQSlUgkPDw9YWlrqICUREVF7LFISERERERHpiUcffRRJSUkwNja+qwKliYkJlEolXnzxRR2kIyIiujUjqQMQERERERHRvaurq8PmzZsBAElJSZDJZGhtbb2rY9Vqdbv3UhIREUmFRUoiIiIiIqIeTCaT4eGHHwYAvP7663j88cfh4eEBc3Nz7T7Gxsa47777YGxs3O5YjUZzxxm+iYiIdIHDvYmIiIiIiHowCwsLuLq6AgBee+21dtuqqqpw4sQJnDp1CidPnsSJEydw9OhRnDlzBtevXwcAVFZW4tKlS5ytm4iIJMUiJRERERERkZ6ys7ODnZ0d/P39263XaDQoLS3FyZMncerUKTQ2NrJISUREkuJwbyIiIqJu7tChQ5DJZO2WIUOG3LRfbW3tTft1J++//742V//+/Tt9/1v55ptv2n0mFhYW93SemJiYdud56KGH7jlTd8L2ZZjty8jICAMGDEBoaCiWLVsGZ2fnLr0eERHRnbBISURERNTNubu7QwiBJ598EgCwevVqnD59+qb9rKysIITAlClTsHbtWgghOjVHfX09hg4disjIyHs6ftWqVRBCwM3NrUv2v5P169dDCIH6+vp7Ov6dd96BEAJCiJve69eTsX2xfREREXUHLFISERER9RBPPPEEACAuLg4ajabDfaqqqrBnzx7Mmzev068vhIBGo7nltalnY/siIiIiKbFISURERNRD+Pr6YujQoSgtLUVqamqH+8TFxSEkJASOjo6dfv37778fv/32G3bt2tXp5ybpsX0RERGRlFikJCIiIupBFi5cCADYuHFjh9s3btyo7RFH9FexfREREZFUWKQkIiIi6kHmz58PIyMjbN++HbW1te227d+/H1VVVZg8eTIAQK1WIz4+HqGhoXBwcMB9992HsWPHYt26de2G1G7fvr3dhB0nTpzAzJkzYWNjo123YcOGdvs0Nzdrj7/b6/zZ8ePHMWnSJFhaWsLMzAyBgYHIycm568/i4sWLWLFiBQYOHAgTExP07dsXM2bMwKFDh+76HABw6dIlrFy5EoMHD4apqSn69++PkJAQfPXVV2hqavpL5+rp2L7+X2e0r3vNTkREZIhYpCQiIiLqQdoKaM3NzdiyZUu7bRs3bkR0dDQUCgUAICUlBbNnz0ZQUBCOHTuG0tJSLF68GCtXrsTLL7+sPW7atGkQQmDq1KkAgCVLlmDZsmUoLS1Ffn4+jI2Nb9rnRnd7nRvV19dj2bJl+Oc//4ny8nJkZmaipqYGQUFB+Omnn+74OZw/fx5eXl5ISEjAp59+ipqaGuzbtw81NTV4+OGHkZeXd1efZ2VlJby8vLBlyxasW7cO1dXVKCoqQkBAAJ544gnExsbe1Xn0BdvXHzqrfd1LdiIiIoMliIiIiEjnHnvsMfHYY4/d07FbtmwRAISXl5d2XWNjo7C0tBRHjhzRrlOpVCIgIOCm46Ojo4VCoRBXrlxpt37q1KkCgNi1a9ctr922T1NT0z1fx83NTQAQeXl57dYfOXJEABBubm437d+vX7926xYsWCAAiM2bN7dbf/78eWFqaiqUSqV23aZNmwQAsX79+psyLly4UAAQ8fHxN22bOHGi+PDDD29aL4QQxsbGYty4cR1uu5X4+Hihq1+/2b66R/v6q9nbdPf2RURE1AUS2JOSiIiIqIeZNm0arKyscODAARQXFwMAkpKSMGTIEIwdO1a7X2RkJDIyMm463s3NDS0tLdpj/8zb2/sv5bmX6/Tq1Qvjxo1rt27s2LFwcnLC4cOHcf78+dtec/v27TAyMkJkZGS79Q4ODhg9ejSKiopQVlZ2x+zbtm0DAERERNy0bffu3Xj++efveA59w/bVee3rXj8jIiIiQ8QiJREREVEP06tXL8yePRsA8O9//1v730WLFrXb78qVK3j11VcxduxY9OnTR/u+v5deegkA0NjY2OH5zc3N/1Kee7lO2/sI/8zOzg4AUFVVdcvrXbt2DVeuXIFGo4GlpWW7dxnKZDIcPHgQAHDq1Knb5m47T69evXD//fff9f3qO7avzmlf95qdiIjIULFISURERNQDtc2w/M033+D06dPIy8vD448/3m6fyZMnY82aNXjqqadw8uRJaDQaCCHw4YcfAgCEEJ2S5V6uc+XKlQ7P1VY8aismdcTU1BRWVlaQy+VoaWmBEKLDJTAw8La5TU1NYWlpiebmZtTV1d3t7RoEtq+/377uNTsREZGhYpGSiIiIqAfy9vbGqFGjUFVVhblz52Lq1Kno06ePdntraytycnLg4OCAFStWoG/fvtqeZZ05Y/W9Xqe+vh6HDx9ut+6XX35BRUUF3Nzc4OjoeNvrzpgxA2q1usPZmteuXQsXFxeo1eo75p8+fToAYNeuXTdt8/DwwAsvvHDHc+gjtq+/37509RkRERHpCxYpiYiIiHqohQsXAgAKCgq0Pd/aGBsbIyAgAJWVlXjvvfdQXV2NpqYmZGRk4LPPPuu0DPd6HXNzcyxfvhz79+9HQ0MDCgsLER0dDRMTE6xbt+6O13377bcxePBgLFq0CLt378aVK1dQU1OD2NhYvPnmm3j//fchl8vv6jyDBg3CCy+8gJ07d6Kurg5lZWVYtmwZzp8/b7BFSoDt6++2L119RkRERHpDl9P0EBEREdEf/s7sy23Onz8v5HK5cHZ2Fq2trTdtv3jxoliyZIlwdnYWCoVC2Nvbi4ULF4qYmBgBQAAQSqVS5OXlab+/cbnRtm3bbto+d+7cv3Sd9957T/t9v379REFBgQgMDBQWFhbivvvuExMmTBDZ2dnaa964f9uyevVq7fZLly6JlStXCldXV6FQKETfvn1FWFiY2Lt3b7vst5t9WQghqqurxfPPPy8GDRokFAqFcHR0FLNnzxYnT5685Wff3WdfZvvqHu3rbrP/WXdvX0RERF0gQSYEX4RCREREpGtRUVEAgMTERImT6L9vvvkG8+bNw/r167F06dJOOadcLoenpyfy8/Pv+piEhATMmjVLJ+8hZPvSHUNsX0RERF0gkcO9iYiIiIiIiIiISFIsUhIRERGRQXj66achk8lgYWFxT8fHxMRAJpNBJpOhtbW1k9NRT8f2RURE9PewSElEREREei06OhpCCO1SX19/T+d555132p3nrwzFJf3F9kVERNQ5WKQkIiIiIiIiIiIiSbFISURERERERERERJJikZKIiIiIiIiIiIgkxSIlERERERERERERSYpFSiIiIiIiIiIiIpIUi5REREREREREREQkKRYpiYiIiIiIiIiISFIsUhIREREREREREZGkWKQkIiIiIiIiIiIiSbFISURERERERERERJJikZKIiIiIiIiIiIgkxSIlERERERERERERSYpFSiIiIiIiIiIiIpIUi5REREREREREREQkKbnUAYiIiIgMVVlZGRISEqSOQTqSl5en0+uxfRkWXbcvIiKizsYiJREREZFE8vPzMWvWLKljkJ5i+yIiIqKeRCaEEFKHICIiIiIiIiIiIoOVyHdSEhERERERERERkaRYpCQiIiIiIiIiIiJJsUhJREREREREREREkpIDSJQ6BBERERERERERERms/P8Fj6rs/ttsLNsAAAAASUVORK5CYII=", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Create PNG representation\n", - "module" - ] - }, - { - "cell_type": "markdown", - "id": "3bcc23a8-df87-468f-b100-84260d3347da", - "metadata": {}, - "source": [ - "We can also get the PNG representation of parts of the AST, such as `basic_op` and the variable `a`:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "ec46ee68-8e7e-4b45-a316-a5522349d310", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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S8DSTERERESkBkszEREREZEaLM1ERERERGqwNBMRERERqcHSTERERESkBkszEREREZEaLM1ERERERGqwNBMRERERqcHSTERERESkBkszEREREZEaLM1ERERERGqwNBMRERERqcHSTERERESkBkszEREREZEaMqkDkH5lZWVJHYH07MqVK2jdurXUMVBQUCB1BCIVfP8zf8by/keWgaXZzEVEREgdgQwgLCxM6gj49NNP8emnn0odg0iJ73+WwRje/8gyCKIoilKHIPN37NgxDBo0CN26dcOePXsgCIJexpk/fz7Wr1+Ps2fP6mX/RI/auHEjYmNjcf/+fVhZST/b7datW4iMjMSBAweQkJCA2bNnw9raWupYZCCCICAzMxOjR4826LinT5/GiBEjUF5ejrS0NAwcONCg4xMZSLb07/Jk9r7//nsEBQWhc+fO2Lx5s94KMwA4ODjgwYMHets/0aPOnTuHdu3aGUVhBoDnnnsOe/fuRWJiIubOnYtevXrh/PnzUsciM/f666/ju+++Q//+/REUFIT4+HjU1NRIHYtI54zjnZ7M1uHDhzFgwAD06NEDu3fvhpOTk17HY2kmQzp37hxeeeUVqWOoEAQB06dPx/fff4+HDx+ia9euSE1NlToWmblmzZohIyMDq1evxqeffoqgoCBcv35d6lhEOsXSTHqTn5+PN998E71790ZOTg7s7e31PqaDgwMePnyo93GIAOMszXU8PT1RWFiIKVOmYPz48Rg9ejR+/fVXqWORmZs8eTKOHDmCS5cuwdfXF4cOHZI6EpHOsDSTXhw4cABDhw7F4MGDkZOTAzs7O4OMa29vj6qqKigUCoOMR5bt/PnzaNeundQxGmVnZ4cFCxZgz549OHz4MLy9vXHgwAGpY5GZ69q1K44fPw5/f3/0798fCQkJqK2tlToW0VNjaSad27NnD958800MHToUGRkZsLGxMdjYDg4OAMCrzaR3ZWVl+PXXX432SvOjgoKCUFxcDB8fHwwYMADTp0/H77//LnUsMmPNmzdHVlYWFi9ejPnz5yM0NBR37tyROhbRU2FpJp3avXs3RowYgeHDhyMtLQ0ymWGfalhXmjmvmfTt3LlzAIBXX31V4iRN89xzz2Hr1q1Yv349/vWvfyEwMBA//vij1LHIjNXNrz98+DBOnjwJHx8fFBYWSh2LSGsszaQzO3fu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yEBBQcFrz3nq1Cloa2s3aU82aNAgjBkzBmlpacjLy2s1e0xMDABgxowZTdadO3cO69ata/UYPQ3HV8eNr/Z+RkRERJqow4vmXr16wcvLCwDwww8/KP+7YsUKle3Ky8vx5ZdfwsrKCv3791fOF920aRMAoKqqqtnj6+vrv1Ge9pyncT7rq4yNjQG8aG3WkufPn6O8vBwKhQKGhoZNHriRnp4OALh79+5rczcep1evXujbt2+b329Px/HVMeOrvdmJiIg0VYcXzcD/euoePXoUOTk5SEpKwqJFi1S2mT17NrZt24a//OUvyM7OhkKhgCAI2LNnDwB02MMp2nOe8vLyZo/VWMw0FjfN0dPTg5GRESQSCerq6pQPhHj15ebm9trcenp6MDQ0RE1NDSoqKtr6djUCx9fbj6/2ZiciItJUnVI029vbY/To0Xj06BGWLFmCuXPnon///sr1DQ0NkMvlGDRoENauXYuBAwcqr7x1ZEeI9p6nsrISGRkZKst+/fVX5Ofnw9rautU+wPPmzUN9fX2z3RB27twJMzMz1NfXt5r/o48+AoBmewPb2tri888/b/UYPRHH19uPL3V9RkRERD1FpxTNwP966qampjbpnaujowNXV1cUFhbi22+/RUlJCaqrq3Hx4kUcOHCgwzK09zz6+vpYvXo1UlJS8OzZM1y7dg3e3t7Q1dVFcHBwq+fdvn07LCwssGLFCpw7dw7l5eUoLS1FSEgIvv76a+zatQsSiaRNxxkx+hmK+wAAAnpJREFUYgQ+//xznD17FhUVFcjLy0NAQAAKCgo0tmgGOL7ednyp6zMiIiLqMZq7PfBtuhs0KigoECQSiWBqaio0NDQ0WV9cXCysWrVKMDU1FaRSqWBiYiL4+voKgYGBAgABgCCTyYSkpCTln19+vSwmJqbJ+iVLlrzReb799lvln4cOHSqkpqYKbm5ugoGBgdC7d29h0qRJQkJCgvKcL2/f+AoKClKuf/z4sbB+/XrB3NxckEqlwsCBA4Vp06YJ//nPf1Syv667gSAIQklJibBu3TphxIgRglQqFQYPHix4eXkJ2dnZLX72Xb27AcdX1xhfbc3+qq4+voiIiDpBlJYgNJ246OnpCQA4ceJEhxTm1LKjR49i6dKl2L9/P/z9/TvkmBKJBOPHj0dycnKb94mKisLChQvVMo+V40t9NHF8ERERdYITnTY9g4iIiIiop2DR3EV88skn0NLSgoGBQbv2DwwMVLYMa2ho6OB01N1xfBEREb0dFs0i8/b2VmkVVllZ2a7j7NixQ+U4b/LTOfVcHF9EREQdg0UzEREREVErWDQTEREREbWCRTMRERERUStYNBMRERERtYJFMxERERFRK1g0ExERERG1gkUzEREREVErWDQTEREREbWCRTMRERERUStYNBMRERERtYJFMxERERFRK1g0ExERERG1gkUzEREREVErWDQTEREREbVC0tKKvLw8REVFqTMLiSgpKUmt5+P40izqHl9EREQdrcWiOTk5GQsXLlRnFtIgHF9ERETUnWgJgiCIHYKIiIiIqAs7wTnNREREREStYNFMRERERNQKFs1ERERERK2QADghdggiIiIioi4s+f8ApJANXH/cQ5oAAAAASUVORK5CYII=", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Create PNG representation\n", - "basic_op" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "15f25501-fd77-4927-b17d-15e82b3105cf", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Create PNG representation\n", - "a" - ] - }, - { - "cell_type": "markdown", - "id": "c23049f4-e640-4059-94e1-26e8d0de8700", - "metadata": {}, - "source": [ - "## Custom shapes\n", - "\n", - "It is also possible to use custom shapes for the output using the function `viz.visualize`. The Default shape is `box`, but `diamond`, `ellipse`, and `circle` are also avaiable options. " - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "e765eebe-f2f8-498b-b47b-ac840b30a97f", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Import visualization module\n", - "from astx import viz\n", - "\n", - "# Create PNG representation with diamond shape\n", - "viz.visualize(a.get_struct(), shape=\"diamond\")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "b2543b53-7c97-4280-b369-deb514c4689c", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Create PNG representation with circle shape\n", - "viz.visualize(a.get_struct(), shape=\"circle\")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "a38ab2d0-7615-4c01-9bb1-08eedb6ca83c", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Create PNG representation with ellipse shape\n", - "viz.visualize(a.get_struct(), shape=\"ellipse\")" - ] - }, - { - "cell_type": "markdown", - "id": "26ef1289-119b-43f2-b683-37ffc73f237b", - "metadata": {}, - "source": [ - "# Conclusion\n", - "\n", - "This guide provides clear instructions and a simple example for you to start using the ASTx library. But this is just the beginning of your journey. Make sure to check out the other tutorials available, such as the one for [variables](https://github.com/arxlang/astx/blob/main/docs/tutorials/variables.ipynb) and the one for [functions](https://github.com/arxlang/astx/blob/main/docs/tutorials/functions.ipynb)." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pages/blog/console-based-representation-in-astx/index.qmd b/pages/blog/console-based-representation-in-astx/index.qmd new file mode 100644 index 000000000..9528c691a --- /dev/null +++ b/pages/blog/console-based-representation-in-astx/index.qmd @@ -0,0 +1,354 @@ +--- +title: "Console-based representation in ASTx" +slug: "console-based-representation-in-astx" +date: 2024-08-08 +authors: ["Ana Krelling", "Ivan Ogasawara"] +tags: ["abstract syntax tree", "ascii", "console"] +categories: ["abstract syntax tree", "console"] +description: | + Recently, console-based AST representation was included in the ASTx framework. Such feature can enhance the debugging and analysis capabilities of ASTx, particularly in environments such as a pdb session. In this tutorial, we'll explore this new feature as well as the ASTx Graphviz visualization. +thumbnail: "/header.png" +template: "blog-post.html" +--- +# Introduction + +The ASTx library is an agnostic framework for constructing and representing Abstract Syntax Trees (ASTs). Its primary objective is to provide a versatile and language-independent structure for ASTs, with the flexibility to be utilized across various programming languages and parsing tools. ASTx doesn't aim to be a lexer or a parser, although it could be used by any programming language or parser written in Python in order to provide a high level representation of the AST. + +Many kinds of nodes (classes) are currently supported. Below is a list with just some examples: + +##### Statements: +* Function +* Function Prototype +* FunctionReturn +* ForRangeLoop +* VarDecl + +##### Operators: +* BinaryOp +* UnaryOp + +##### Data types: +* Boolean +* Literal +* Variable + + +The ASTx project is still under development, so new classes may be added to the ones above at any time. + +Below are installation instructions and an example, so you can have an overview of how you can leverage the ASTx library for your needs. + +# Installation +The first step is to install ASTx. You can do it simply by running the command below in your terminal:\ +`$ pip install astx`\ +If you need more information on installation, you can get it in the [ASTx installation page](https://github.com/arxlang/astx/blob/main/docs/installation.md). +After that, you can just open a Jupyter Notebook instance and start writing your first AST. + + +# Example: an AST of a series of mathematical operations +Here we will present a quick example of an AST of the expression \ +`basic_op = lit_1 + b - a * c / a + (b - a / a)`, in which \ +$~~~~$ `lit_1` is a defined integer, and \ +$~~~~$ `a`, `b`, and `c` are variables.\ +The first thing to do is, in your Jupyter Notebook instance, import `display`, which will allow you to have a basic visualization of the AST, and the astx library itself. + + +```python +# import display for AST visualization +import astx + +from astx.viz import graph_to_ascii, traverse_ast_ascii +``` + +Then we create an instance of the Module class, and this instance will be the first node of the tree, or the root node. After that, we declare the variables and literal that will be part of the basic operation that we will parse into an AST. + + +```python +# Create module +module = astx.Module() + +# Declare variables +decl_a = astx.VariableDeclaration(name="a", type_=astx.Int32, value=astx.LiteralInt32(1)) +decl_b = astx.VariableDeclaration(name="b", type_=astx.Int32, value=astx.LiteralInt32(2)) +decl_c = astx.VariableDeclaration(name="c", type_=astx.Int32, value=astx.LiteralInt32(4)) + +a = astx.Variable(name="a") +b = astx.Variable(name="b") +c = astx.Variable(name="c") + +# Declare literal +lit_1 = astx.LiteralInt32(1) + +# State the expression +basic_op = lit_1 + b - a * c / a + (b - a / a) +``` + +After the basic expression is stated, we create an instance of the Function class. As mentioned in the API documentation, each instance of the Function class must have a prototype and a body, so we'll create those first. + +The body is made of a block that is created and the variables, as well as the basic operation, are appended to it afterwards. + + +```python +# Create FunctionPrototype +main_proto = astx.FunctionPrototype( + name="main", args=astx.Arguments(), return_type=astx.Int32 +) + +# Create FunctionReturn +main_block = astx.Block() +main_block.append(decl_a) +main_block.append(decl_b) +main_block.append(decl_c) +main_block.append(astx.FunctionReturn(basic_op)) + +# Create Function +main_fn = astx.Function(prototype=main_proto, body=main_block) + +# Append function to module +module.block.append(main_fn) +``` + +After this, the module is complete. We can get its AST structure as a dictionary, as well as a PNG representation. + + +```python +# Create dictionary representation +module.get_struct() +``` + + + + + {'MODULE[main]': {'content': [{'FUNCTION[main]': {'content': {'args': {'Arguments(0)': {'content': [], + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}, + 'body': {'BLOCK': {'content': [{'VariableDeclaration[a, Int32]': {'content': {'Literal[Int32]: 1': {'content': 1, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': 'c4848732a3c542f1b3818bc799dc0b26', + 'kind': }}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}, + {'VariableDeclaration[b, Int32]': {'content': {'Literal[Int32]: 2': {'content': 2, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': 'b63f0bf700194bb7abbdf99d8cc20336', + 'kind': }}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}, + {'VariableDeclaration[c, Int32]': {'content': {'Literal[Int32]: 4': {'content': 4, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '0c0686b5f12a45bd9ff1a20da82702a0', + 'kind': }}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}, + {'RETURN': {'content': {'BINARY[+]': {'content': {'lhs': {'BINARY[-]': {'content': {'lhs': {'BINARY[+]': {'content': {'lhs': {'Literal[Int32]: 1': {'content': 1, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '8d5d86d52b98484a8e5947ae4e6556f1', + 'kind': }}}, + 'rhs': {'Variable[b]': {'content': 'b', + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}, + 'rhs': {'BINARY[/]': {'content': {'lhs': {'BINARY[*]': {'content': {'lhs': {'Variable[a]': {'content': 'a', + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}, + 'rhs': {'Variable[c]': {'content': 'c', + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}, + 'rhs': {'Variable[a]': {'content': 'a', + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}, + 'rhs': {'BINARY[-]': {'content': {'lhs': {'Variable[b]': {'content': 'b', + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}, + 'rhs': {'BINARY[/]': {'content': {'lhs': {'Variable[a]': {'content': 'a', + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}, + 'rhs': {'Variable[a]': {'content': 'a', + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}], + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}}, + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}}], + 'metadata': {'loc': {line: -1, col: -1}, + 'comment': '', + 'ref': '', + 'kind': }}} + + + + +```python +# Create ascii representation +dot_graph = traverse_ast_ascii(module.get_struct(simplified=True)) +graph = graph_to_ascii(dot_graph) +print(graph) +``` + +![non-funky_ascii_tree.png](0d8393a6-fb00-4364-8f2f-319295075948.png) + + +```python +# Create PNG representation +module +``` + + + +![png](output_11_0.png) + + + + + + + + + + +We can also get the PNG representation of parts of the AST, such as `basic_op` and the variable `a`: + + +```python +# Create PNG representation +basic_op +``` + + + +![png](output_13_0.png) + + + + + + + + + + + +```python +# Create PNG representation +a +``` + + + +![png](output_14_0.png) + + + + + + + + + + +## Custom shapes + +It is also possible to use custom shapes for the output using the function `viz.visualize`. The Default shape is `box`, but `diamond`, `ellipse`, and `circle` are also avaiable options. + + +```python +# Import visualization module +from astx import viz + +# Create PNG representation with diamond shape +viz.visualize(a.get_struct(), shape="diamond") +``` + + + +![png](output_16_0.png) + + + + +```python +# Create PNG representation with circle shape +viz.visualize(a.get_struct(), shape="circle") +``` + + + +![png](output_17_0.png) + + + + +```python +# Create PNG representation with ellipse shape +viz.visualize(a.get_struct(), shape="ellipse") +``` + + + +![png](output_18_0.png) + + + +# Conclusion + +This guide provides clear instructions and a simple example for you to start using the ASTx library. But this is just the beginning of your journey. Make sure to check out the other tutorials available, such as the one for [variables](https://github.com/arxlang/astx/blob/main/docs/tutorials/variables.ipynb) and the one for [functions](https://github.com/arxlang/astx/blob/main/docs/tutorials/functions.ipynb). diff --git a/pages/blog/exploring-llama-2-a-paradigm-shift-in-large-language-models/index.ipynb b/pages/blog/exploring-llama-2-a-paradigm-shift-in-large-language-models/index.ipynb deleted file mode 100644 index cb92cbb98..000000000 --- a/pages/blog/exploring-llama-2-a-paradigm-shift-in-large-language-models/index.ipynb +++ /dev/null @@ -1,116 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "id": "00f06fca-0418-47c9-966d-e0afd0dc3399", - "metadata": {}, - "source": [ - "---\n", - "title: Exploring LLama-2,A Paradigm Shift in Large Language Models\n", - "slug: exploring-llama-2-a-paradigm-shift-in-large-language-models\n", - "date: 2024-02-05\n", - "authors:\n", - " - Satarupa Deb\n", - "tags:\n", - " - open-source\n", - " - Machine Learning\n", - " - LLM\n", - " - python\n", - "categories:\n", - " - Python\n", - " - Machine Learning\n", - " - Natural Language Processing\n", - "description: |\n", - " Embark on an exploration of LLama-2, a game-changing innovation in\n", - " large language models, igniting dialogue applications with cutting-edge\n", - " performance and a commitment to ethical research practices.\n", - "thumbnail: /header.png\n", - "template: blog-post.html\n", - "\n", - "---\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "693ca0dc-d620-43c3-aae6-13d9a8751178", - "metadata": {}, - "source": [ - "# Exploring LLama-2: A Paradigm Shift in Large Language Models\n", - "\n", - "In the realm of artificial intelligence, language models have evolved significantly, reshaping how humans interact with technology. Among these advancements, LLama-2 stands out as a remarkable leap forward, offering cutting-edge performance tailored for dialogue applications while championing responsible research practices within the community.\n", - "\n", - "## Understanding LLama-2\n", - "\n", - "LLama-2 is not just another run-of-the-mill language model; it's a comprehensive collection of large language models (LLMs) meticulously crafted and released by a team of dedicated researchers led by Hugo Touvron and Louis Martin. What sets LLama-2 apart is its scalability, boasting models ranging from 7 billion to a staggering 70 billion parameters. These models are finely tuned and optimized specifically for dialogue applications, earning them the moniker \"LLama 2-Chat.\"\n", - "\n", - "## Performance Beyond Par\n", - "\n", - "The prowess of LLama 2-Chat shines through empirical evidence, showcasing superior performance compared to existing open-source chat models across various benchmarks. Human evaluations, focusing on metrics such as helpfulness and safety, have underscored LLama 2-Chat's potential as a compelling alternative to closed-source models. This performance excellence is a testament to the rigorous fine-tuning process and safety enhancements meticulously integrated into LLama-2's development.\n", - "\n", - "## Unlocking LLama-2: Installation Guide\n", - "\n", - "For those eager to harness the power of LLama-2, the installation process is straightforward, albeit with a few essential steps:\n", - "\n", - "To install Llama-2 models, follow these steps:\n", - "\n", - "1. Visit the Llama [download form](https://ai.meta.com/resources/models-and-libraries/llama-downloads/) on the official website and accept the License Agreement.\n", - "\n", - "2. Once your request is approved, you will receive a signed URL over email. Check your email inbox (including spam/junk folders) for the email containing the signed URL.\n", - "\n", - "3. Clone the Llama 2 repository from the provided link. You can typically do this using a Git command like:\n", - "\n", - " ```\n", - " git clone \n", - " ```\n", - "\n", - " Replace `` with the URL of the Llama 2 repository provided to you.\n", - "\n", - "4. Navigate to the directory where you cloned the Llama 2 repository.\n", - "\n", - "5. Run the `download.sh` script from the terminal, passing the signed URL provided in the email when prompted to start the download. You can run the script using the following command:\n", - "\n", - " ```\n", - " bash download.sh\n", - " ```\n", - "\n", - " Follow the on-screen instructions and paste the signed URL when prompted.\n", - "\n", - "It's worth noting that signed URLs expire after 24 hours, so timely action is crucial. In case of errors during the download process, re-requesting a new signed URL is the solution to ensure a seamless installation experience.\n", - "\n", - "## Hardware Requirements: Unleashing LLama-2's Power\n", - "\n", - "To unleash the full potential of LLama-2, adequate hardware is paramount. While a graphics card with at least 10GB of VRAM suffices for the 7B model, higher-end GPUs like the RTX 3090 or RTX 4090 are recommended for optimal performance, especially with larger models. For the behemoth 70B parameter model, enterprise-grade hardware like the NVIDIA A100 with 80GB of memory is indispensable.\n", - "\n", - "## Navigating LLama-2 Restrictions\n", - "\n", - "Despite its open-source nature, LLama-2 comes with certain restrictions, particularly concerning commercial use. Developers and commercial users must adhere to Meta's guidelines, including obtaining a license for popular applications and refraining from using LLama-2 output to enhance other large language models.\n", - "\n", - "## Final Thoughts\n", - "\n", - "LLama-2 heralds a new era in dialogue applications, pushing the boundaries of what's possible with language models while upholding ethical standards and responsible research practices. Whether you're a researcher, developer, or enthusiast, LLama-2 opens doors to a world of innovative possibilities, paving the way for transformative interactions between humans and machines.\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pages/blog/exploring-llama-2-a-paradigm-shift-in-large-language-models/index.qmd b/pages/blog/exploring-llama-2-a-paradigm-shift-in-large-language-models/index.qmd new file mode 100644 index 000000000..2e751456c --- /dev/null +++ b/pages/blog/exploring-llama-2-a-paradigm-shift-in-large-language-models/index.qmd @@ -0,0 +1,79 @@ +--- +title: Exploring LLama-2,A Paradigm Shift in Large Language Models +slug: exploring-llama-2-a-paradigm-shift-in-large-language-models +date: 2024-02-05 +authors: + - Satarupa Deb +tags: + - open-source + - Machine Learning + - LLM + - python +categories: + - Python + - Machine Learning + - Natural Language Processing +description: | + Embark on an exploration of LLama-2, a game-changing innovation in + large language models, igniting dialogue applications with cutting-edge + performance and a commitment to ethical research practices. +thumbnail: /header.png +template: blog-post.html + +--- + + +# Exploring LLama-2: A Paradigm Shift in Large Language Models + +In the realm of artificial intelligence, language models have evolved significantly, reshaping how humans interact with technology. Among these advancements, LLama-2 stands out as a remarkable leap forward, offering cutting-edge performance tailored for dialogue applications while championing responsible research practices within the community. + +## Understanding LLama-2 + +LLama-2 is not just another run-of-the-mill language model; it's a comprehensive collection of large language models (LLMs) meticulously crafted and released by a team of dedicated researchers led by Hugo Touvron and Louis Martin. What sets LLama-2 apart is its scalability, boasting models ranging from 7 billion to a staggering 70 billion parameters. These models are finely tuned and optimized specifically for dialogue applications, earning them the moniker "LLama 2-Chat." + +## Performance Beyond Par + +The prowess of LLama 2-Chat shines through empirical evidence, showcasing superior performance compared to existing open-source chat models across various benchmarks. Human evaluations, focusing on metrics such as helpfulness and safety, have underscored LLama 2-Chat's potential as a compelling alternative to closed-source models. This performance excellence is a testament to the rigorous fine-tuning process and safety enhancements meticulously integrated into LLama-2's development. + +## Unlocking LLama-2: Installation Guide + +For those eager to harness the power of LLama-2, the installation process is straightforward, albeit with a few essential steps: + +To install Llama-2 models, follow these steps: + +1. Visit the Llama [download form](https://ai.meta.com/resources/models-and-libraries/llama-downloads/) on the official website and accept the License Agreement. + +2. Once your request is approved, you will receive a signed URL over email. Check your email inbox (including spam/junk folders) for the email containing the signed URL. + +3. Clone the Llama 2 repository from the provided link. You can typically do this using a Git command like: + + ``` + git clone + ``` + + Replace `` with the URL of the Llama 2 repository provided to you. + +4. Navigate to the directory where you cloned the Llama 2 repository. + +5. Run the `download.sh` script from the terminal, passing the signed URL provided in the email when prompted to start the download. You can run the script using the following command: + + ``` + bash download.sh + ``` + + Follow the on-screen instructions and paste the signed URL when prompted. + +It's worth noting that signed URLs expire after 24 hours, so timely action is crucial. In case of errors during the download process, re-requesting a new signed URL is the solution to ensure a seamless installation experience. + +## Hardware Requirements: Unleashing LLama-2's Power + +To unleash the full potential of LLama-2, adequate hardware is paramount. While a graphics card with at least 10GB of VRAM suffices for the 7B model, higher-end GPUs like the RTX 3090 or RTX 4090 are recommended for optimal performance, especially with larger models. For the behemoth 70B parameter model, enterprise-grade hardware like the NVIDIA A100 with 80GB of memory is indispensable. + +## Navigating LLama-2 Restrictions + +Despite its open-source nature, LLama-2 comes with certain restrictions, particularly concerning commercial use. Developers and commercial users must adhere to Meta's guidelines, including obtaining a license for popular applications and refraining from using LLama-2 output to enhance other large language models. + +## Final Thoughts + +LLama-2 heralds a new era in dialogue applications, pushing the boundaries of what's possible with language models while upholding ethical standards and responsible research practices. Whether you're a researcher, developer, or enthusiast, LLama-2 opens doors to a world of innovative possibilities, paving the way for transformative interactions between humans and machines. + diff --git a/pages/blog/first-time-contributors/index.ipynb b/pages/blog/first-time-contributors/index.ipynb deleted file mode 100644 index 3698d5a4a..000000000 --- a/pages/blog/first-time-contributors/index.ipynb +++ /dev/null @@ -1,707 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "---\n", - "title: \"First Time Contributors\"\n", - "slug: first-time-contributors\n", - "date: 2024-04-08\n", - "authors: [\"Daniela Iglesias Rocabado\"]\n", - "tags: [open-source, contributors, git, osl]\n", - "categories: [contributors]\n", - "description: |\n", - " First Time Contributors\" refers to individuals making their initial foray into contributing to open-source projects within scientific laboratories. These newcomers bring diverse skills and fresh perspectives, enriching the collaborative environment. Embracing inclusivity and providing guidance fosters their engagement, leading to innovative solutions in open science.\n", - "thumbnail: \"/header.jpeg\"\n", - "template: \"blog-post.html\"\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Diving into project development can be overwhelming for beginners. A first-timers guide is key to navigate this unfamiliar terrain. From understanding basics to mastering tools, it'll help you contribute effectively. Join us as we explore how to get started in a development project!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Avoiding Issues in Your First Contributions\n", - "\n", - "The world of open-source programming project development is a vibrant and collaborative space, but it can also be daunting for those venturing in for the first time. We've noticed that new contributors often face significant challenges when taking their first steps in this environment. That's why we've created this first-timers guide.\n", - "\n", - "Our aim is to provide a comprehensive resource that helps newcomers overcome initial barriers and start contributing effectively right from the get-go. By offering a guide that covers everything from the basics to best practices in open-source project development, we hope to streamline the onboarding process and foster a more inclusive and productive collaborative environment.\n", - "\n", - "This guide will not only be beneficial for those starting their journey in open-source projects but will also serve as the primary reference for all affiliated projects and projects under the OSL Incubator Program. By standardizing practices and promoting a common understanding of the processes involved, we aim to enhance the experience for all contributors and promote more efficient and high-quality development in our open-source projects." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Getting Started\n", - "\n", - "In this guide, we'll tackle the common challenges that newcomers encounter in open-source projects. These include navigating Git and establishing your development environment. We'll provide step-by-step instructions on Git fundamentals and setting up your virtual workspace seamlessly.\n", - "\n", - "Additionally, we will offer further advice for the contributor to start with a solid initial structure, thus ensuring that their project is well-organized from the outset. From the initial setup to active contribution in the project, this guide aims to provide contributors with the tools and knowledge needed to effectively contribute to open-source programming projects." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# GIT\n", - "\n", - "## What is Git?\n", - "Git is a widely used distributed version control system in software development. It allows project collaborators to work collaboratively on the same set of files, recording changes, merging contributions, and maintaining a detailed history of all modifications made to the source code.\n", - "\n", - "## How to install Git?\n", - "To install Git, you can follow these steps:\n", - "\n", - "1. **Windows Operating System:** You can download the Git installer from the [official Git website](https://gitforwindows.org/). Once downloaded, run the installer and follow the installation wizard instructions.\n", - "\n", - "2. **macOS Operating System:** Git is usually pre-installed on macOS. However, if it's not installed or you want to update it, you can do so through the command line or using package management tools like Homebrew.\n", - "\n", - "3. **Linux Operating System:** In most Linux distributions, Git is available in the default package repositories. You can install it using the package manager specific to your distribution, such as apt for Ubuntu or yum for CentOS.\n", - "\n", - "For more detailed material on Git, we recommend using the Software Carpentry material, which is more focused on people who already have basic knowledge of Git but still face difficulties. You can access the material at the following link: [Software Carpentry - Git Novice](https://carpentries.github.io/workshop-template/install_instructions/#git-1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## First Steps to Collaborating on a Project\n", - "\n", - "### Understanding Repository Forking\n", - "When embarking on a collaborative project, the first step often involves forking a repository. Forking is a fundamental aspect of version control systems like Git, enabling individuals or teams to create their own copy of a project's repository. This copy acts as a sandbox where contributors can freely experiment, make changes, and propose improvements without affecting the original project.\n", - "\n", - "#### Steps to Forking a Repository\n", - "1. **Navigate to the Repository:** Visit the project's repository on the hosting platform, such as GitHub, GitLab, or Bitbucket.\n", - "\n", - "2. **Locate the Fork Button:** Look for the \"Fork\" button on the repository's page. This button is usually located in the top-right corner. Clicking it will initiate the forking process.\n", - "\n", - "3. **Choose Destination:** When prompted, select where you want to fork the repository. You can fork it to your personal account or to an organization you're a part of.\n", - "\n", - "4. **Wait for Forking to Complete:** The platform will create a copy of the repository in your account or organization. Depending on the size of the repository and the platform's load, this process may take a few seconds to complete.\n", - "\n", - "5. **Clone Your Forked Repository:** Once the forking process is finished, clone the forked repository to your local machine using Git. This will create a local copy of the repository that you can work on.\n", - "\n", - "#### Benefits of Forking a Repository\n", - "- **Independence:** Forking gives you complete control over your copy of the project. You can modify it as you see fit without impacting the original.\n", - "\n", - "- **Experimentation:** Forking provides a safe environment for experimenting with changes. You can try out new features, fix bugs, or test ideas without risking the stability of the main project.\n", - "\n", - "- **Collaboration:** Forking facilitates collaboration by enabling contributors to work on different aspects of the project simultaneously. Once you've made improvements or fixes in your fork, you can propose them to the original project through a pull request.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### What is a Pull Request / Merge Request and why is it important?\n", - "\n", - "A Pull Request (PR) is a proposed change that a collaborator makes to a code repository managed by a version control system such as Git. It's essentially a request for the changes made in one branch of a repository to be incorporated into another branch, usually the main branch. Pull Requests are essential in the collaborative workflow of software development, as they allow teams to review, discuss, and approve changes before they are merged into the codebase. This facilitates collaboration, improves code quality, and helps maintain a clear history of modifications made to the project.\n", - "\n", - "### Steps for creating a good Pull Request:\n", - "\n", - "1. **Create a feature branch:** Before starting to make changes to the code, create a new branch in the repository that clearly describes the purpose of the changes you plan to make. This helps keep the development organized and makes code review easier.\n", - "\n", - "2. **Make necessary changes:** Once you're on your feature branch, make the changes to the code as planned. Make sure to follow the project's coding conventions and write unit tests if necessary.\n", - "\n", - "3. **Update documentation if necessary:** If your changes affect existing functionality or introduce new features, it's important to update the corresponding documentation, such as code comments or user documentation.\n", - "\n", - "4. **Create the Pull Request:** Once you've completed your changes and are ready to request review, create a Pull Request. Provide a clear and concise description of the purpose of your changes, as well as any relevant context to facilitate review by your team members.\n", - "\n", - "5. **Request review:** After creating the Pull Request, assign relevant reviewers to examine your changes. This may include other developers on the team, technical leads, or anyone with expertise in the area affected by your modifications.\n", - "\n", - "6. **Respond to comments and make adjustments if necessary:** Once reviewers have provided feedback on your Pull Request, take the time to respond to their questions and make any necessary adjustments to your code. It's important to collaborate constructively during this process to ensure that the proposed changes are of the highest possible quality.\n", - "\n", - "By following these steps, you can effectively contribute to a project's development through Pull Requests that are easy to review, approve, and merge, ultimately leading to stronger code and more efficient teamwork.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Remotes Repositories\n", - "\n", - "In version control systems like Git, a remote repository is essentially a copy of your local repository stored on a server somewhere else, often online. It acts as a central hub for collaboration and version control.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Origin: This remote, typically created by default when cloning the repository, points to your fork on GitHub.\n", - "- Upstream: This remote points to the original (upstream) repository you forked from. It allows you to stay updated with the main project's development.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Creating the Upstream Remote\n", - "\n", - "```bash\n", - "git remote add upstream \n", - "```\n", - "\n", - "Using the Upstream Remote:\n", - "\n", - "Fetching Updates: Regularly use `git fetch upstream` to download the latest changes from the upstream repository without merging them into your local branch.\n", - "Creating Feature Branches: When starting work on a new feature, it's recommended to base your branch on the latest upstream main branch:\n", - "\n", - "```bash\n", - "git checkout upstream/main\n", - "git checkout -b my-feature-branch\n", - "```\n", - "This ensures your feature branch incorporates the most recent upstream developments." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Understanding Merge Commit vs. Rebase in Git\n", - "\n", - "In Git, managing branches is a fundamental aspect of collaboration and version control. When integrating changes from one branch to another, developers often encounter two primary methods: merge commit and rebase. Both approaches have their advantages and trade-offs, influencing how teams collaborate and maintain a clean project history. Let's delve into each method:\n", - "\n", - "### Merge Commit\n", - "\n", - "A merge commit, as the name suggests, involves creating a new commit to merge changes from one branch into another. Here's how it typically works:\n", - "\n", - "1. **Branch Divergence**: Suppose you have a feature branch (`feature`) and a main branch (`main` or `master`). As work progresses, both branches diverge, accumulating different commits.\n", - "\n", - "2. **Merge Process**: When it's time to integrate changes from `feature` into `main`, you execute a merge command. Git creates a new commit, known as a merge commit, to combine the histories of both branches.\n", - "\n", - "3. **Commit History**: The merge commit preserves the entire history of changes from both branches, making it clear when and how the integration occurred.\n", - "\n", - "4. **Parallel Development**: Merge commits allow parallel development, enabling team members to work independently without affecting each other's changes.\n", - "\n", - "### Rebase\n", - "\n", - "Rebasing is an alternative method for integrating changes, involving rewriting commit history to maintain a linear project history. Here's how it differs from merge commit:\n", - "\n", - "1. **Branch Adjustment**: Instead of creating a merge commit, rebasing adjusts the commit history of the feature branch (`feature`) to appear as if it originated from the tip of the main branch (`main` or `master`).\n", - "\n", - "2. **Commit Replay**: Git replays each commit from the feature branch onto the tip of the main branch, effectively transplanting the changes onto a different base.\n", - "\n", - "3. **Linear History**: By rewriting commit history, rebasing creates a linear sequence of commits, making the project history cleaner and easier to follow.\n", - "\n", - "4. **Conflict Resolution**: Rebasing can lead to conflicts if changes from the feature branch conflict with those on the main branch. These conflicts must be resolved manually during the rebase process." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " Recommendations and Best Practices\n", - "\n", - "While both merge commit and rebase have their merits, the choice often depends on the team's workflow and preferences. Here are some considerations:\n", - "\n", - "**Merge Commit**:\n", - "\n", - "* Suitable for preserving a detailed history of parallel development.\n", - "* Preferred when collaboration involves multiple contributors or when maintaining a clear record of individual contributions is essential.\n", - "\n", - "**Rebase**:\n", - "\n", - " - Promotes a cleaner, linear project history.\n", - " - Recommended for feature branches with short-lived changes or when maintaining a tidy commit history is a priority.\n", - "\n", - "It's worth noting that some organizations, such as the Open Source Initiative (OSI), recommend the usage of git rebase to maintain a clean and linear project history. You can configure Git to use rebase by default for pull operations with the command:\n", - "\n", - "```bash\n", - "$ git config --global pull.rebase true\n", - "```\n", - "\n", - "OSL recommends the usage of git rebase\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Mergin a Pull Requests (PRs)\n", - "Pull requests (PRs) are essential for collaborative software development, enabling contributors to propose changes to a project's codebase. Once a PR is submitted, it undergoes a review process before being merged into the main codebase. Here are three common methods for merging PRs:\n", - "\n", - "#### 1. Merge Commit:\n", - "A merge commit combines changes from different branches in version control systems like Git. It records the integration of these changes, preserving their histories and keeping the project's development organized.\n", - "\n", - "Use when you want to keep a detailed history of changes from multiple branches. It preserves individual commit histories, making it suitable for tracking the development of feature branches and bug fixes separately.\n", - "\n", - "\n", - "#### 2. Squash and Merge:\n", - "\"Squash and merge\" condenses multiple commits into one before merging, simplifying the project's commit history.\n", - "\n", - "Employ when you have multiple small, related commits that you want to consolidate into a single, more meaningful commit. It's useful for cleaning up the commit history, especially before merging feature branches into the main branch.\n", - "\n", - "#### 3. Rebase and Merge:\n", - "\"Rebase and merge\" rewrites commit history to integrate changes from one branch into another, maintaining a cleaner and more linear history.\n", - "\n", - "Opt for this method when you want to maintain a clean and linear commit history by incorporating changes from one branch into another. It helps to avoid unnecessary merge commits, keeping the commit history straightforward and easier to follow.\n", - "\n", - "\n", - "Each method offers distinct advantages and considerations, influencing the project's commit history and overall workflow.\n", - "\n", - "In their development workflow, **Open Science Labs** recommend using squash and merge." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Pre-commit\n", - "\n", - "Pre-commit is a tool used in software development to automatically run various checks and tests on files before they are committed to a version control system, such as Git. These checks can include code formatting, linting, static analysis, and other quality assurance tasks. The goal is to catch potential issues early in the development process, ensuring that only high-quality code is committed to the repository.\n", - "\n", - "Here's how to install and use pre-commit:\n", - "\n", - "1. Installation:\n", - "You can install pre-commit using pip, the Python package manager. Open your terminal or command prompt and run:\n", - "\n", - "``` bash\n", - "$ pip install pre-commit\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "2. Configuration:\n", - "Once pre-commit is installed, you need to set up a configuration file named .pre-commit-config.yaml in the root directory of your project. This file specifies the hooks (checks) that pre-commit should run.\n", - "\n", - "Here's a basic example of a `.pre-commit-config.yaml` file:\n", - "\n", - "``` yaml\n", - "repos:\n", - " - repo: https://github.com/pre-commit/pre-commit-hooks\n", - " rev: v3.3.0\n", - " hooks:\n", - " - id: trailing-whitespace\n", - " - id: end-of-file-fixer\n", - " - id: check-yaml\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "3. Installation of Git Hooks:\n", - "After configuring `.pre-commit-config.yaml`, you need to install pre-commit hooks into your Git repository. Navigate to your project directory in the terminal and run:\n", - "\n", - "``` bash\n", - "$ pre-commit install\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "4. Running Pre-commit:\n", - "Once the pre-commit hooks are installed, you can run them manually using the following command:\n", - "\n", - "```bash\n", - "$ pre-commit run --all-files\n", - "```\n", - "This command tells pre-commit to run all configured hooks on all files in the repository. It will check for issues according to the configuration specified in `.pre-commit-config.yaml` and provide feedback on any problems found.\n", - "\n", - "\n", - "The Git hooks are triggered each time the user initiates a git commit command. By default, they operate on the files that have been modified, but their behavior can be adjusted to encompass all files if configured accordingly in the .pre-commit-config.yaml file." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Navigating Git Workflows: A Dive into GitHub Flow and GitFlow\n", - "\n", - "In the ever-evolving world of software development, efficient collaboration and streamlined workflows are paramount. Git, the popular version control system, offers a plethora of options for managing code changes, each tailored to different team structures and project requirements. Two widely used workflows, GitHub Flow and GitFlow, stand out for their simplicity and effectiveness. Let's explore these options in detail.\n", - "\n", - "### GitHub Flow:\n", - "\n", - "GitHub Flow is a lightweight, branch-based workflow specifically designed for teams using GitHub for version control. It emphasizes simplicity and continuous delivery, making it an ideal choice for projects with frequent releases and rapid iteration cycles. Here's a breakdown of its key features:\n", - "\n", - "**Branching Model:**\n", - "\n", - " - Main Branch: GitHub Flow revolves around a single main branch (often named \"main\" or \"master\"), representing the development branch that leads to production-ready code.\n", - " - Feature Branches: Developers create feature branches off the main branch for each new feature or bug fix.\n", - "\n", - "\n", - "**Workflow:**\n", - "\n", - "- Create a Branch: Developers create a new branch for each feature or bug fix.\n", - "- Make Changes: Developers create a new branch, with the latest changes from upstream/main (or origin/main, if you are not working on a fork) for each feature or bug fix.\n", - "- Open Pull Request: Once changes are complete, a pull request (PR) is opened to merge the feature branch into the main branch.\n", - "- Review and Merge: Team members review the code changes, provide feedback, and merge the PR into the main branch once approved.\n", - "\n", - "**Continuous Deployment**:\n", - "\n", - "- Continuous Integration: GitHub Flow encourages the use of continuous integration tools to automatically test changes before merging.\n", - "- Continuous Deployment: Merged changes are automatically deployed to production, ensuring a fast and reliable release cycle.\n", - "\n", - "GitHub Flow's simplicity and flexibility make it a popular choice for teams of all sizes, particularly those embracing agile development practices.\n", - "\n", - "### GitFlow:\n", - "\n", - "GitFlow, developed by Vincent Driessen, provides a more organized approach to branching and release management. It excels in larger projects with longer release cycles and strict versioning requirements. Here's how GitFlow differs from GitHub Flow:\n", - "\n", - "**Branching Model**:\n", - "\n", - "- Main Branches: GitFlow defines two main branches – \"master\" for stable releases and \"develop\" for ongoing development.\n", - "- Feature Branches: Developers create feature branches off the \"develop\" branch for each new feature.\n", - "\n", - "**Workflow**:\n", - "\n", - "- Feature Development: Developers work on feature branches, merging them into the \"develop\" branch once complete.\n", - "- Release Branches: When it's time for a new release, a release branch is created from the \"develop\" branch for final testing and bug fixing.\n", - "- Hotfix Branches: If critical issues arise in production, hotfix branches are created from the \"master\" branch to address them directly.\n", - "\n", - "**Versioning**:\n", - "\n", - "- GitFlow employs a strict versioning scheme, with each release assigned a unique version number based on semantic versioning principles.\n", - "\n", - "### Which Workflow to Choose?\n", - "\n", - "Choosing between GitHub Flow and GitFlow depends on your team's specific needs and project requirements:\n", - "\n", - "- **GitHub Flow**: Ideal for teams focused on continuous delivery, rapid iteration, and simplicity. This is most used in a bunch of projects.\n", - "- **GitFlow**: Suited for larger projects with longer release cycles, strict versioning, and a more structured approach to development.\n", - "\n", - "While both workflows have their merits, it's essential to assess your team's workflow preferences, project size, and release cycle frequency before making a decision.\n", - "\n", - "### Recommendation:\n", - "\n", - "For a deeper dive into their advantages and implementation details, consider referring to the following blog post: [click here](https://www.harness.io/blog/github-flow-vs-git-flow-whats-the-difference).\n", - "\n", - "OSL recommends the GitHub flow for development." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Python Linters Overview\n", - "\n", - "Here's a breakdown of popular Python linters and their functionalities:\n", - "\n", - "1. **ruff**: A high-performance Python linter and code formatter engineered for efficiency. It amalgamates the functionalities of multiple linters into a unified tool, encompassing features such as style checking, static type validation, and dead code detection. This holistic approach obviates the necessity of managing disparate linters, thus streamlining the development workflow. Notably, ruff excels in speed, rendering it well-suited for handling extensive codebases or integration into Continuous Integration/Continuous Deployment (CI/CD) pipelines. Moreover, its extensive configuration options empower users to customize its behavior according to the specific coding standards of their projects, ensuring adherence to desired styles and quality guidelines.\n", - "\n", - "2. **black**: An influential Python code formatter that ensures code uniformity by automatically applying a standardized style across the codebase.\n", - "\n", - "3. **flake8**: Integrates linting, style validation, and complexity analysis functionalities into a unified package. Widely utilized for enforcing PEP 8 coding standards and identifying common programming errors.\n", - "\n", - "4. **mypy**: A static type checker for Python that identifies type errors and enhances code maintainability through type annotations.\n", - "\n", - "5. **pydocstyle**: Ensures adherence to Python docstring conventions outlined in PEP 257, thereby enhancing code readability and documentation quality.\n", - "\n", - "6. **isort**: A utility for organizing and sorting import statements within Python code, maintaining a consistent import style and mitigating import-related issues.\n", - "\n", - "7. **vulture**: Identifies redundant code in Python projects by detecting unused variables, functions, classes, and modules.\n", - "\n", - "8. **mccabe**: Computes the McCabe cyclomatic complexity of functions and methods, highlighting intricate code segments for potential enhancements in readability and maintainability.\n", - "\n", - "9. **bandit**: A security-centric linter that identifies security vulnerabilities and insecure coding practices in Python codebases.\n", - "\n", - "These linters seamlessly integrate into development workflows, furnishing developers with real-time feedback and upholding code quality throughout the development lifecycle." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Documentation\n", - "\n", - "### Why Documentation is Essential\n", - "\n", - "Documentation is essential in software development for various reasons. Firstly, it enhances clarity and understanding by detailing the purpose, functionality, and usage of the software, aiding developers, users, and stakeholders in comprehending the system's workings. Additionally, it streamlines the onboarding process for new team members by furnishing them with a comprehensive overview of the project, including its architecture and coding standards. Furthermore, well-crafted documentation promotes maintainability and scalability by elucidating the project's structure, design decisions, and coding conventions, empowering developers to implement changes, rectify bugs, and incorporate new features without introducing errors.\n", - "\n", - "Moreover, documentation serves as a valuable resource for support and troubleshooting, furnishing users with troubleshooting guides, FAQs, and usage examples to swiftly resolve issues. It also contributes to the project's sustainability by encapsulating critical knowledge about its design, implementation, and maintenance, thereby preserving institutional knowledge and facilitating future enhancements or migrations.\n", - "\n", - "## Options for Creating Documentation\n", - "\n", - "Here are some popular tools for creating documentation for your project:\n", - "\n", - "1. **Sphinx**: Sphinx stands out as a robust documentation generator tool extensively employed within Python ecosystems. Supporting various markup formats such as reStructuredText and Markdown, Sphinx enables the generation of documentation in diverse output formats like HTML, PDF, and ePub.\n", - "\n", - "2. **MkDocs**: MkDocs emerges as a user-friendly documentation tool that simplifies the generation of static websites through Markdown files. Its straightforward setup process and flexibility in customization, utilizing Jinja2 templates, empower users to swiftly produce polished and professional documentation. Be it for small-scale projects or extensive documentation requirements, MkDocs offers an intuitive solution for crafting well-structured and visually appealing documentation.\n", - "\n", - "3. **Quarto**: Quarto represents a modern documentation tool tailored for data science and computational projects. Leveraging the amalgamation of Markdown, LaTeX, and Jupyter Notebooks, Quarto facilitates the creation of interactive and reproducible documentation, catering to the specific needs of these domains.\n", - "\n", - "## Recommended Resources for Improving Documentation Skills\n", - "\n", - "To enhance your documentation writing skills, consider exploring the following resources:\n", - "\n", - "1. **Diataxis**: [Diataxis](https://diataxis.fr/) offers comprehensive documentation writing guides, tutorials, and best practices for technical writers and developers. It covers various topics, including structuring documentation, writing clear and concise content, and using documentation tools effectively.\n", - "\n", - "2. **Write the Docs Slack Community**: Join the [Write the Docs Slack Community](https://www.writethedocs.org/slack/) to connect with other documentation enthusiasts, share ideas, and seek advice on writing documentation. The community is a valuable resource for learning from experienced writers, participating in discussions, and staying updated on the latest trends in documentation practices.\n", - "\n", - "By investing time and effort in creating high-quality documentation, you can significantly improve the usability, maintainability, and overall success of your software projects.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Continuous Integration (CI)\n", - "\n", - "Continuous Integration (CI) is a crucial practice in modern software development, enabling teams to deliver high-quality code efficiently. CI involves automating the process of testing and integrating code changes into a shared repository, typically multiple times a day. This approach helps teams to identify and resolve issues early in the development cycle, maintain a consistently deployable codebase, and streamline the overall development workflow.\n", - "\n", - "Here are some top options for CI/CD platforms:\n", - "\n", - "1. **GitHub Actions**: GitHub Actions is an integrated CI/CD solution provided within the GitHub platform. It allows developers to define workflows using YAML syntax directly in their GitHub repositories. With support for various triggers such as pushes, pull requests, and scheduled events, GitHub Actions enables flexible automation tailored to project needs.\n", - "\n", - "2. **Azure Pipelines**: Azure Pipelines, part of the Microsoft Azure suite, offers a cloud-based CI/CD service for building, testing, and deploying applications across different platforms. It provides extensive flexibility through YAML configuration or a graphical editor, facilitating seamless integration with Azure services and third-party tools.\n", - "\n", - "3. **CircleCI**: CircleCI is a popular cloud-based CI/CD platform known for its simplicity and scalability. It supports integration with version control systems like GitHub and Bitbucket, allowing teams to define build and deployment pipelines using YAML configuration files. CircleCI offers a wide range of pre-configured and customizable job types to meet diverse project requirements.\n", - "\n", - "GitHub Actions has emerged as one of the most popular options for automating workflows in software development pipelines. Its seamless integration with GitHub repositories, extensive marketplace of pre-built actions, and flexibility in creating custom workflows have contributed to its widespread adoption by developers and organizations alike.\n", - "\n", - "When choosing a CI/CD platform, consider factors such as integration capabilities, scalability, ease of use, and pricing. Evaluating these options based on your specific project requirements and existing development ecosystem will help determine the best fit for your team's needs. It's often beneficial to experiment with different platforms to find the one that aligns most closely with your workflow and objectives." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Unit Tests and Testing Frameworks in Python\n", - "\n", - "Unit tests are an essential part of software development, allowing developers to verify that individual components of their code behave as expected. The `unittest` module in Python provides a framework for organizing and running unit tests. Here's why you might consider using `unittest`:\n", - "\n", - "1. **Standard Library Inclusion**: `unittest` is part of Python's standard library, which means it's readily available without needing to install additional packages. This makes it convenient for projects that prefer minimal dependencies.\n", - "\n", - "2. **Built-in Assertions**: `unittest` offers a set of built-in assertion methods for verifying expected outcomes, such as `assertEqual`, `assertTrue`, and `assertRaises`. These assertions make it easy to write expressive and readable test cases.\n", - "\n", - "3. **Test Discovery**: `unittest` supports automatic test discovery, allowing you to organize your tests into separate modules and directories while effortlessly running them as a cohesive test suite.\n", - "\n", - "4. **Integration with IDEs and CI Tools**: `unittest` integrates well with popular IDEs like PyCharm, VS Code, and CI/CD platforms, enabling seamless test execution and reporting within your development workflow.\n", - "\n", - "While `unittest` is a solid choice for writing unit tests in Python, there are alternative frameworks that offer additional features and flexibility:\n", - "\n", - "1. **Pytest**: Pytest is a popular third-party testing framework known for its simplicity and powerful features. It provides concise syntax, fixtures for reusable test setup, parameterized testing, and extensive plugin support. Pytest excels in making test code more readable and maintainable.\n", - "\n", - "2. **Hypothesis**: Hypothesis is a property-based testing library that complements traditional example-based testing. Instead of writing specific test cases, you specify general properties that your code should satisfy. Hypothesis then generates input data automatically to thoroughly test these properties, uncovering edge cases and potential bugs." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Python Project Initialization\n", - "\n", - "When starting a new Python project, it's beneficial to use project templates that include predefined directory structures, configuration files, and boilerplate code to jumpstart development. Tools like `cookiecutter` and project templates such as `scicookie` provide convenient starting points for various project types:\n", - "\n", - "- **Cookiecutter**: Cookiecutter is a command-line utility that generates projects from project templates. It prompts you for project-specific details and then creates a customized project structure based on the selected template. There are many community-contributed templates available for various types of Python projects, including web applications, libraries, and data analysis projects.\n", - "\n", - "- **SciCookie**: SciCookie is a project template focused on the scientific python community, but it can be used by any python project. It includes a structured directory layout, documentation templates, and example code snippets. SciCookie helps streamline the setup process for scientific Python projects and encourages best practices in testing and documentation.\n", - "\n", - "In summary, while `unittest` provides a robust framework for writing unit tests in Python, alternative frameworks like Pytest and Hypothesis offer additional features and flexibility. When starting a new project, leveraging project templates such as `scicookie` with tools like `cookiecutter` can accelerate setup and promote best practices in project organization and testing." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# What is a virtual enviroment and why is it important?\n", - "\n", - "A virtual environment is a self-contained directory that isolates the dependencies for a specific project, regardless of the programming language. It can house an interpreter (like Python) along with its associated libraries, but it can also manage dependencies for other languages and tools. This isolation ensures that the project's requirements don't conflict with those of other projects on the same system.\n", - "\n", - "When you create a virtual environment for each of your projects, it essentially creates a sandboxed environment where you can install packages and dependencies without affecting the global Python installation on your system. This isolation is crucial because different projects often require different versions of libraries or dependencies, and conflicts can arise if they share the same environment.\n", - "\n", - "Creating virtual environments is crucial for several reasons:\n", - "\n", - "- **Isolation:** Virtual environments allow you to isolate project dependencies, preventing conflicts between different projects that may require different versions of the same packages. This ensures that your projects remain stable and reproducible.\n", - "\n", - "- **Dependency Management:** By creating separate environments for each project, you can manage dependencies more effectively. You can install specific versions of packages for each project without affecting other projects or the system-wide installation.\n", - "\n", - "- **Experimentation:** Virtual environments provide a safe space for experimentation. You can try out new packages or versions without worrying about breaking existing projects or the system environment.\n", - "\n", - "- **Reproducibility:** Utilizing virtual environments not only streamlines collaboration but also enhances reproducibility in project workflows. By sharing environment configuration files such as environment.yml, collaborators ensure uniformity in dependencies and versions across all team members. This practice mitigates compatibility issues and fosters consistency, enabling seamless replication of results and facilitating smoother collaboration.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Conda/Mamba\n", - "Conda is a package manager, environment manager, and dependency solver that can install and manage packages and their dependencies. Mamba is a fast, drop-in replacement for Conda that aims to provide faster package management operations.\n", - "\n", - "An agnostic language package manager is a tool that handles dependencies for software projects using multiple programming languages. Unlike traditional package managers tied to one language (like npm for JavaScript), agnostic managers work across languages. They simplify development for projects using various languages, offering features like dependency management and version control in a unified way. This helps maintain consistency and flexibility, especially in projects with mixed-language environments or complex architectures.\n", - "\n", - "### Installation:\n", - "\n", - " For Windows users, Anaconda may be a suitable choice, providing a comprehensive Python distribution along with Conda. For Linux and macOS users, Miniconda or Miniforge offers a lighter and more streamlined approach to managing environments and packages. You can download Miniforge from [here](https://github.com/conda-forge/miniforge?tab=readme-ov-file#downloadhere), which includes Miniconda, Conda Forge configuration, and Mamba.\n", - "\n", - "**Creating a Conda Enviroment**\n", - "To create a new Conda enviroment named `myenv`, open a terminal or command promt and use the following commands:\n", - "\n", - "```bash\n", - "$ conda create --name myenv\n", - "```\n", - "\n", - "- On Windows:\n", - "```bash\n", - "$ activate myenv\n", - "```\n", - "\n", - "- On Unix/ MacOs:\n", - "```bash\n", - "$ source activate myenv\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Conda-Forge:\n", - "Is a community-driven collection of recipes, build infrastructure, and distributions for the Conda package manager. It provides a vast repository of pre-built packages for various programming languages, including Python, R, C, C++, Rust, Go, Fortran, and more. Conda-Forge aims to offer high-quality, up-to-date packages that are well-integrated with Conda environments.\n", - "\n", - "Conda is not limited to managing Python packages; it can handle packages for various programming languages. This capability makes Conda a versatile tool for software development, allowing users to manage complex dependencies and libraries across different programming ecosystems.\n", - "\n", - "### Anaconda:\n", - "\n", - "Anaconda is a popular Python distribution that bundles the Python interpreter, Conda package manager, and a comprehensive set of pre-installed packages for scientific computing, data analysis, and machine learning. It includes tools like Jupyter Notebook, Spyder IDE, and many essential libraries for scientific computing, making it a convenient choice for data scientists and researchers." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Virtualenv\n", - "Virtualenv is a tool to create isolated Python environments. It's lightweight and widely used in the Python community. Here's how to set up Virtualenv:\n", - "\n", - "Installation:\n", - "- Ensure you have Python installed on your system.\n", - "- Install Virtualenv using pip (Python's package installer):\n", - "\n", - "```bash\n", - "$ pip install virtualenv\n", - "```\n", - "\n", - "Create a new Virtualenv:\n", - "```bash\n", - "$ virtualenv myenv\n", - "```\n", - "\n", - "Activate the enviroment:\n", - "\n", - "- On Windows:\n", - "```bash\n", - "$ myenv\\Scripts\\activate\n", - "```\n", - "\n", - "- On Unix/MacOS:\n", - "```bash\n", - "$ source myenv/bin/activate\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Pipenv\n", - "Pipenv is a higher-level tool compared to Virtualenv. It aims to simplify and streamline the process of managing dependencies and environments for Python projects. Here's how to get started with Pipenv:\n", - "\n", - "**Installation:**\n", - "\n", - "Make sure you have Python installed on your system.\n", - "Install Pipenv using pip (Python's package installer):\n", - "\n", - "```bash\n", - "$ pip install pipenv\n", - "```\n", - "\n", - "**Creating a new environment and managing dependencies:**\n", - "\n", - "Navigate to your project directory in the terminal.\n", - "Use Pipenv to create a new virtual environment and generate a Pipfile, which will manage your project's dependencies:\n", - "\n", - "```bash\n", - "$ pipenv --python 3.x\n", - "```\n", - "Replace 3.x with your desired Python version.\n", - "\n", - "**Installing dependencies:**\n", - "\n", - "Use Pipenv to install packages for your project:\n", - "\n", - "```bash\n", - "$ pipenv install package-name\n", - "```\n", - "\n", - "This will install the package and automatically update your Pipfile and Pipfile.lock.\n", - "\n", - "**Activating the environment:**\n", - "\n", - "Pipenv automatically activates the virtual environment when you enter the project directory. You'll see the name of the virtual environment displayed in your terminal prompt.\n", - "\n", - "**Deactivating the environment:**\n", - "\n", - "To deactivate the virtual environment and return to your global Python environment, simply use the exit command or close the terminal window.\n", - "\n", - "**Benefits of Pip**\n", - "\n", - "- *Dependency management:* Pipenv simplifies dependency management by automatically creating and managing a Pipfile and Pipfile.lock for each project.\n", - "- *Isolation:* Pipenv creates isolated environments for each project, preventing conflicts between dependencies.\n", - "- *Streamlined workflow:* Pipenv combines package installation, environment management, and dependency resolution into a single tool, streamlining the development process." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Pixi\n", - "Pixi, developed by prefix.dev, is a dynamic cross-platform package manager and workflow tool inspired by the Conda and Pypi ecosystems. It caters to developers seeking a unified experience across multiple programming languages, akin to renowned tools like Cargo or Yarn. Pixi's adaptability and robust feature set make it a valuable asset in modern software development environments.\n", - "\n", - "Key highlights of Pixi include:\n", - "\n", - "- **Multi-language Compatibility:** Pixi adeptly manages packages written in various languages including Python, C++, R, and more, offering versatility in development workflows.\n", - "- **Platform Agnosticism:** It seamlessly operates on Linux, Windows, macOS, and diverse hardware architectures, ensuring consistent performance across different environments.\n", - "- **Up-to-date Dependency Locking:** Pixi's dependency locking mechanism guarantees the stability of projects by maintaining consistent package versions across different development setups.\n", - "- **Intuitive Command-line Interface:** With a clean and user-friendly interface, Pixi enhances developer productivity and ease of use, promoting efficient workflow management.\n", - "- **Flexible Installation Options:** Pixi accommodates both per-project and system-wide installations, allowing developers to tailor its usage according to specific project requirements.\n", - "\n", - "For comprehensive information, detailed installation guidelines, and practical examples, visit the official [Pixi website](https://pixi.sh/latest). Explore Pixi today to streamline your development process across multiple languages with ease.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Conclusion\n", - "\n", - "The proficiency in specific techniques and methodologies is crucial for anyone looking to make meaningful contributions to open-source projects. This article delineates the essential skills and practices required for effective participation, including but not limited to, adeptness with collaborative tools, version control systems, adherence to coding standards, and familiarity with contribution guidelines. Emphasizing the significance of tailored mentorship, accessible documentation, and active engagement within the community, the article serves as a comprehensive guide for enhancing the quality and impact of contributions. By adopting best practices in code review, project management, and fostering an inclusive dialogue, contributors can significantly elevate the collaborative dynamics and innovation within open-source endeavors. Thus, reading this article is instrumental for those aiming to navigate the complexities of open-source projects successfully and contribute to the advancement of collective scientific and technological objectives." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/pages/blog/first-time-contributors/index.qmd b/pages/blog/first-time-contributors/index.qmd new file mode 100644 index 000000000..89a936397 --- /dev/null +++ b/pages/blog/first-time-contributors/index.qmd @@ -0,0 +1,538 @@ +--- +title: "First Time Contributors" +slug: first-time-contributors +date: 2024-04-08 +authors: ["Daniela Iglesias Rocabado"] +tags: [open-source, contributors, git, osl] +categories: [contributors] +description: | + First Time Contributors" refers to individuals making their initial foray into contributing to open-source projects within scientific laboratories. These newcomers bring diverse skills and fresh perspectives, enriching the collaborative environment. Embracing inclusivity and providing guidance fosters their engagement, leading to innovative solutions in open science. +thumbnail: "/header.jpeg" +template: "blog-post.html" +--- +Diving into project development can be overwhelming for beginners. A first-timers guide is key to navigate this unfamiliar terrain. From understanding basics to mastering tools, it'll help you contribute effectively. Join us as we explore how to get started in a development project! + +### Avoiding Issues in Your First Contributions + +The world of open-source programming project development is a vibrant and collaborative space, but it can also be daunting for those venturing in for the first time. We've noticed that new contributors often face significant challenges when taking their first steps in this environment. That's why we've created this first-timers guide. + +Our aim is to provide a comprehensive resource that helps newcomers overcome initial barriers and start contributing effectively right from the get-go. By offering a guide that covers everything from the basics to best practices in open-source project development, we hope to streamline the onboarding process and foster a more inclusive and productive collaborative environment. + +This guide will not only be beneficial for those starting their journey in open-source projects but will also serve as the primary reference for all affiliated projects and projects under the OSL Incubator Program. By standardizing practices and promoting a common understanding of the processes involved, we aim to enhance the experience for all contributors and promote more efficient and high-quality development in our open-source projects. + +## Getting Started + +In this guide, we'll tackle the common challenges that newcomers encounter in open-source projects. These include navigating Git and establishing your development environment. We'll provide step-by-step instructions on Git fundamentals and setting up your virtual workspace seamlessly. + +Additionally, we will offer further advice for the contributor to start with a solid initial structure, thus ensuring that their project is well-organized from the outset. From the initial setup to active contribution in the project, this guide aims to provide contributors with the tools and knowledge needed to effectively contribute to open-source programming projects. + +# GIT + +## What is Git? +Git is a widely used distributed version control system in software development. It allows project collaborators to work collaboratively on the same set of files, recording changes, merging contributions, and maintaining a detailed history of all modifications made to the source code. + +## How to install Git? +To install Git, you can follow these steps: + +1. **Windows Operating System:** You can download the Git installer from the [official Git website](https://gitforwindows.org/). Once downloaded, run the installer and follow the installation wizard instructions. + +2. **macOS Operating System:** Git is usually pre-installed on macOS. However, if it's not installed or you want to update it, you can do so through the command line or using package management tools like Homebrew. + +3. **Linux Operating System:** In most Linux distributions, Git is available in the default package repositories. You can install it using the package manager specific to your distribution, such as apt for Ubuntu or yum for CentOS. + +For more detailed material on Git, we recommend using the Software Carpentry material, which is more focused on people who already have basic knowledge of Git but still face difficulties. You can access the material at the following link: [Software Carpentry - Git Novice](https://carpentries.github.io/workshop-template/install_instructions/#git-1) + +## First Steps to Collaborating on a Project + +### Understanding Repository Forking +When embarking on a collaborative project, the first step often involves forking a repository. Forking is a fundamental aspect of version control systems like Git, enabling individuals or teams to create their own copy of a project's repository. This copy acts as a sandbox where contributors can freely experiment, make changes, and propose improvements without affecting the original project. + +#### Steps to Forking a Repository +1. **Navigate to the Repository:** Visit the project's repository on the hosting platform, such as GitHub, GitLab, or Bitbucket. + +2. **Locate the Fork Button:** Look for the "Fork" button on the repository's page. This button is usually located in the top-right corner. Clicking it will initiate the forking process. + +3. **Choose Destination:** When prompted, select where you want to fork the repository. You can fork it to your personal account or to an organization you're a part of. + +4. **Wait for Forking to Complete:** The platform will create a copy of the repository in your account or organization. Depending on the size of the repository and the platform's load, this process may take a few seconds to complete. + +5. **Clone Your Forked Repository:** Once the forking process is finished, clone the forked repository to your local machine using Git. This will create a local copy of the repository that you can work on. + +#### Benefits of Forking a Repository +- **Independence:** Forking gives you complete control over your copy of the project. You can modify it as you see fit without impacting the original. + +- **Experimentation:** Forking provides a safe environment for experimenting with changes. You can try out new features, fix bugs, or test ideas without risking the stability of the main project. + +- **Collaboration:** Forking facilitates collaboration by enabling contributors to work on different aspects of the project simultaneously. Once you've made improvements or fixes in your fork, you can propose them to the original project through a pull request. + + +### What is a Pull Request / Merge Request and why is it important? + +A Pull Request (PR) is a proposed change that a collaborator makes to a code repository managed by a version control system such as Git. It's essentially a request for the changes made in one branch of a repository to be incorporated into another branch, usually the main branch. Pull Requests are essential in the collaborative workflow of software development, as they allow teams to review, discuss, and approve changes before they are merged into the codebase. This facilitates collaboration, improves code quality, and helps maintain a clear history of modifications made to the project. + +### Steps for creating a good Pull Request: + +1. **Create a feature branch:** Before starting to make changes to the code, create a new branch in the repository that clearly describes the purpose of the changes you plan to make. This helps keep the development organized and makes code review easier. + +2. **Make necessary changes:** Once you're on your feature branch, make the changes to the code as planned. Make sure to follow the project's coding conventions and write unit tests if necessary. + +3. **Update documentation if necessary:** If your changes affect existing functionality or introduce new features, it's important to update the corresponding documentation, such as code comments or user documentation. + +4. **Create the Pull Request:** Once you've completed your changes and are ready to request review, create a Pull Request. Provide a clear and concise description of the purpose of your changes, as well as any relevant context to facilitate review by your team members. + +5. **Request review:** After creating the Pull Request, assign relevant reviewers to examine your changes. This may include other developers on the team, technical leads, or anyone with expertise in the area affected by your modifications. + +6. **Respond to comments and make adjustments if necessary:** Once reviewers have provided feedback on your Pull Request, take the time to respond to their questions and make any necessary adjustments to your code. It's important to collaborate constructively during this process to ensure that the proposed changes are of the highest possible quality. + +By following these steps, you can effectively contribute to a project's development through Pull Requests that are easy to review, approve, and merge, ultimately leading to stronger code and more efficient teamwork. + + +## Remotes Repositories + +In version control systems like Git, a remote repository is essentially a copy of your local repository stored on a server somewhere else, often online. It acts as a central hub for collaboration and version control. + + +- Origin: This remote, typically created by default when cloning the repository, points to your fork on GitHub. +- Upstream: This remote points to the original (upstream) repository you forked from. It allows you to stay updated with the main project's development. + + +Creating the Upstream Remote + +```bash +git remote add upstream +``` + +Using the Upstream Remote: + +Fetching Updates: Regularly use `git fetch upstream` to download the latest changes from the upstream repository without merging them into your local branch. +Creating Feature Branches: When starting work on a new feature, it's recommended to base your branch on the latest upstream main branch: + +```bash +git checkout upstream/main +git checkout -b my-feature-branch +``` +This ensures your feature branch incorporates the most recent upstream developments. + +## Understanding Merge Commit vs. Rebase in Git + +In Git, managing branches is a fundamental aspect of collaboration and version control. When integrating changes from one branch to another, developers often encounter two primary methods: merge commit and rebase. Both approaches have their advantages and trade-offs, influencing how teams collaborate and maintain a clean project history. Let's delve into each method: + +### Merge Commit + +A merge commit, as the name suggests, involves creating a new commit to merge changes from one branch into another. Here's how it typically works: + +1. **Branch Divergence**: Suppose you have a feature branch (`feature`) and a main branch (`main` or `master`). As work progresses, both branches diverge, accumulating different commits. + +2. **Merge Process**: When it's time to integrate changes from `feature` into `main`, you execute a merge command. Git creates a new commit, known as a merge commit, to combine the histories of both branches. + +3. **Commit History**: The merge commit preserves the entire history of changes from both branches, making it clear when and how the integration occurred. + +4. **Parallel Development**: Merge commits allow parallel development, enabling team members to work independently without affecting each other's changes. + +### Rebase + +Rebasing is an alternative method for integrating changes, involving rewriting commit history to maintain a linear project history. Here's how it differs from merge commit: + +1. **Branch Adjustment**: Instead of creating a merge commit, rebasing adjusts the commit history of the feature branch (`feature`) to appear as if it originated from the tip of the main branch (`main` or `master`). + +2. **Commit Replay**: Git replays each commit from the feature branch onto the tip of the main branch, effectively transplanting the changes onto a different base. + +3. **Linear History**: By rewriting commit history, rebasing creates a linear sequence of commits, making the project history cleaner and easier to follow. + +4. **Conflict Resolution**: Rebasing can lead to conflicts if changes from the feature branch conflict with those on the main branch. These conflicts must be resolved manually during the rebase process. + + Recommendations and Best Practices + +While both merge commit and rebase have their merits, the choice often depends on the team's workflow and preferences. Here are some considerations: + +**Merge Commit**: + +* Suitable for preserving a detailed history of parallel development. +* Preferred when collaboration involves multiple contributors or when maintaining a clear record of individual contributions is essential. + +**Rebase**: + + - Promotes a cleaner, linear project history. + - Recommended for feature branches with short-lived changes or when maintaining a tidy commit history is a priority. + +It's worth noting that some organizations, such as the Open Source Initiative (OSI), recommend the usage of git rebase to maintain a clean and linear project history. You can configure Git to use rebase by default for pull operations with the command: + +```bash +$ git config --global pull.rebase true +``` + +OSL recommends the usage of git rebase + + +## Mergin a Pull Requests (PRs) +Pull requests (PRs) are essential for collaborative software development, enabling contributors to propose changes to a project's codebase. Once a PR is submitted, it undergoes a review process before being merged into the main codebase. Here are three common methods for merging PRs: + +#### 1. Merge Commit: +A merge commit combines changes from different branches in version control systems like Git. It records the integration of these changes, preserving their histories and keeping the project's development organized. + +Use when you want to keep a detailed history of changes from multiple branches. It preserves individual commit histories, making it suitable for tracking the development of feature branches and bug fixes separately. + + +#### 2. Squash and Merge: +"Squash and merge" condenses multiple commits into one before merging, simplifying the project's commit history. + +Employ when you have multiple small, related commits that you want to consolidate into a single, more meaningful commit. It's useful for cleaning up the commit history, especially before merging feature branches into the main branch. + +#### 3. Rebase and Merge: +"Rebase and merge" rewrites commit history to integrate changes from one branch into another, maintaining a cleaner and more linear history. + +Opt for this method when you want to maintain a clean and linear commit history by incorporating changes from one branch into another. It helps to avoid unnecessary merge commits, keeping the commit history straightforward and easier to follow. + + +Each method offers distinct advantages and considerations, influencing the project's commit history and overall workflow. + +In their development workflow, **Open Science Labs** recommend using squash and merge. + +### Pre-commit + +Pre-commit is a tool used in software development to automatically run various checks and tests on files before they are committed to a version control system, such as Git. These checks can include code formatting, linting, static analysis, and other quality assurance tasks. The goal is to catch potential issues early in the development process, ensuring that only high-quality code is committed to the repository. + +Here's how to install and use pre-commit: + +1. Installation: +You can install pre-commit using pip, the Python package manager. Open your terminal or command prompt and run: + +``` bash +$ pip install pre-commit +``` + +2. Configuration: +Once pre-commit is installed, you need to set up a configuration file named .pre-commit-config.yaml in the root directory of your project. This file specifies the hooks (checks) that pre-commit should run. + +Here's a basic example of a `.pre-commit-config.yaml` file: + +``` yaml +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.3.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml +``` + +3. Installation of Git Hooks: +After configuring `.pre-commit-config.yaml`, you need to install pre-commit hooks into your Git repository. Navigate to your project directory in the terminal and run: + +``` bash +$ pre-commit install +``` + +4. Running Pre-commit: +Once the pre-commit hooks are installed, you can run them manually using the following command: + +```bash +$ pre-commit run --all-files +``` +This command tells pre-commit to run all configured hooks on all files in the repository. It will check for issues according to the configuration specified in `.pre-commit-config.yaml` and provide feedback on any problems found. + + +The Git hooks are triggered each time the user initiates a git commit command. By default, they operate on the files that have been modified, but their behavior can be adjusted to encompass all files if configured accordingly in the .pre-commit-config.yaml file. + +## Navigating Git Workflows: A Dive into GitHub Flow and GitFlow + +In the ever-evolving world of software development, efficient collaboration and streamlined workflows are paramount. Git, the popular version control system, offers a plethora of options for managing code changes, each tailored to different team structures and project requirements. Two widely used workflows, GitHub Flow and GitFlow, stand out for their simplicity and effectiveness. Let's explore these options in detail. + +### GitHub Flow: + +GitHub Flow is a lightweight, branch-based workflow specifically designed for teams using GitHub for version control. It emphasizes simplicity and continuous delivery, making it an ideal choice for projects with frequent releases and rapid iteration cycles. Here's a breakdown of its key features: + +**Branching Model:** + + - Main Branch: GitHub Flow revolves around a single main branch (often named "main" or "master"), representing the development branch that leads to production-ready code. + - Feature Branches: Developers create feature branches off the main branch for each new feature or bug fix. + + +**Workflow:** + +- Create a Branch: Developers create a new branch for each feature or bug fix. +- Make Changes: Developers create a new branch, with the latest changes from upstream/main (or origin/main, if you are not working on a fork) for each feature or bug fix. +- Open Pull Request: Once changes are complete, a pull request (PR) is opened to merge the feature branch into the main branch. +- Review and Merge: Team members review the code changes, provide feedback, and merge the PR into the main branch once approved. + +**Continuous Deployment**: + +- Continuous Integration: GitHub Flow encourages the use of continuous integration tools to automatically test changes before merging. +- Continuous Deployment: Merged changes are automatically deployed to production, ensuring a fast and reliable release cycle. + +GitHub Flow's simplicity and flexibility make it a popular choice for teams of all sizes, particularly those embracing agile development practices. + +### GitFlow: + +GitFlow, developed by Vincent Driessen, provides a more organized approach to branching and release management. It excels in larger projects with longer release cycles and strict versioning requirements. Here's how GitFlow differs from GitHub Flow: + +**Branching Model**: + +- Main Branches: GitFlow defines two main branches – "master" for stable releases and "develop" for ongoing development. +- Feature Branches: Developers create feature branches off the "develop" branch for each new feature. + +**Workflow**: + +- Feature Development: Developers work on feature branches, merging them into the "develop" branch once complete. +- Release Branches: When it's time for a new release, a release branch is created from the "develop" branch for final testing and bug fixing. +- Hotfix Branches: If critical issues arise in production, hotfix branches are created from the "master" branch to address them directly. + +**Versioning**: + +- GitFlow employs a strict versioning scheme, with each release assigned a unique version number based on semantic versioning principles. + +### Which Workflow to Choose? + +Choosing between GitHub Flow and GitFlow depends on your team's specific needs and project requirements: + +- **GitHub Flow**: Ideal for teams focused on continuous delivery, rapid iteration, and simplicity. This is most used in a bunch of projects. +- **GitFlow**: Suited for larger projects with longer release cycles, strict versioning, and a more structured approach to development. + +While both workflows have their merits, it's essential to assess your team's workflow preferences, project size, and release cycle frequency before making a decision. + +### Recommendation: + +For a deeper dive into their advantages and implementation details, consider referring to the following blog post: [click here](https://www.harness.io/blog/github-flow-vs-git-flow-whats-the-difference). + +OSL recommends the GitHub flow for development. + +## Python Linters Overview + +Here's a breakdown of popular Python linters and their functionalities: + +1. **ruff**: A high-performance Python linter and code formatter engineered for efficiency. It amalgamates the functionalities of multiple linters into a unified tool, encompassing features such as style checking, static type validation, and dead code detection. This holistic approach obviates the necessity of managing disparate linters, thus streamlining the development workflow. Notably, ruff excels in speed, rendering it well-suited for handling extensive codebases or integration into Continuous Integration/Continuous Deployment (CI/CD) pipelines. Moreover, its extensive configuration options empower users to customize its behavior according to the specific coding standards of their projects, ensuring adherence to desired styles and quality guidelines. + +2. **black**: An influential Python code formatter that ensures code uniformity by automatically applying a standardized style across the codebase. + +3. **flake8**: Integrates linting, style validation, and complexity analysis functionalities into a unified package. Widely utilized for enforcing PEP 8 coding standards and identifying common programming errors. + +4. **mypy**: A static type checker for Python that identifies type errors and enhances code maintainability through type annotations. + +5. **pydocstyle**: Ensures adherence to Python docstring conventions outlined in PEP 257, thereby enhancing code readability and documentation quality. + +6. **isort**: A utility for organizing and sorting import statements within Python code, maintaining a consistent import style and mitigating import-related issues. + +7. **vulture**: Identifies redundant code in Python projects by detecting unused variables, functions, classes, and modules. + +8. **mccabe**: Computes the McCabe cyclomatic complexity of functions and methods, highlighting intricate code segments for potential enhancements in readability and maintainability. + +9. **bandit**: A security-centric linter that identifies security vulnerabilities and insecure coding practices in Python codebases. + +These linters seamlessly integrate into development workflows, furnishing developers with real-time feedback and upholding code quality throughout the development lifecycle. + +## Documentation + +### Why Documentation is Essential + +Documentation is essential in software development for various reasons. Firstly, it enhances clarity and understanding by detailing the purpose, functionality, and usage of the software, aiding developers, users, and stakeholders in comprehending the system's workings. Additionally, it streamlines the onboarding process for new team members by furnishing them with a comprehensive overview of the project, including its architecture and coding standards. Furthermore, well-crafted documentation promotes maintainability and scalability by elucidating the project's structure, design decisions, and coding conventions, empowering developers to implement changes, rectify bugs, and incorporate new features without introducing errors. + +Moreover, documentation serves as a valuable resource for support and troubleshooting, furnishing users with troubleshooting guides, FAQs, and usage examples to swiftly resolve issues. It also contributes to the project's sustainability by encapsulating critical knowledge about its design, implementation, and maintenance, thereby preserving institutional knowledge and facilitating future enhancements or migrations. + +## Options for Creating Documentation + +Here are some popular tools for creating documentation for your project: + +1. **Sphinx**: Sphinx stands out as a robust documentation generator tool extensively employed within Python ecosystems. Supporting various markup formats such as reStructuredText and Markdown, Sphinx enables the generation of documentation in diverse output formats like HTML, PDF, and ePub. + +2. **MkDocs**: MkDocs emerges as a user-friendly documentation tool that simplifies the generation of static websites through Markdown files. Its straightforward setup process and flexibility in customization, utilizing Jinja2 templates, empower users to swiftly produce polished and professional documentation. Be it for small-scale projects or extensive documentation requirements, MkDocs offers an intuitive solution for crafting well-structured and visually appealing documentation. + +3. **Quarto**: Quarto represents a modern documentation tool tailored for data science and computational projects. Leveraging the amalgamation of Markdown, LaTeX, and Jupyter Notebooks, Quarto facilitates the creation of interactive and reproducible documentation, catering to the specific needs of these domains. + +## Recommended Resources for Improving Documentation Skills + +To enhance your documentation writing skills, consider exploring the following resources: + +1. **Diataxis**: [Diataxis](https://diataxis.fr/) offers comprehensive documentation writing guides, tutorials, and best practices for technical writers and developers. It covers various topics, including structuring documentation, writing clear and concise content, and using documentation tools effectively. + +2. **Write the Docs Slack Community**: Join the [Write the Docs Slack Community](https://www.writethedocs.org/slack/) to connect with other documentation enthusiasts, share ideas, and seek advice on writing documentation. The community is a valuable resource for learning from experienced writers, participating in discussions, and staying updated on the latest trends in documentation practices. + +By investing time and effort in creating high-quality documentation, you can significantly improve the usability, maintainability, and overall success of your software projects. + + +## Continuous Integration (CI) + +Continuous Integration (CI) is a crucial practice in modern software development, enabling teams to deliver high-quality code efficiently. CI involves automating the process of testing and integrating code changes into a shared repository, typically multiple times a day. This approach helps teams to identify and resolve issues early in the development cycle, maintain a consistently deployable codebase, and streamline the overall development workflow. + +Here are some top options for CI/CD platforms: + +1. **GitHub Actions**: GitHub Actions is an integrated CI/CD solution provided within the GitHub platform. It allows developers to define workflows using YAML syntax directly in their GitHub repositories. With support for various triggers such as pushes, pull requests, and scheduled events, GitHub Actions enables flexible automation tailored to project needs. + +2. **Azure Pipelines**: Azure Pipelines, part of the Microsoft Azure suite, offers a cloud-based CI/CD service for building, testing, and deploying applications across different platforms. It provides extensive flexibility through YAML configuration or a graphical editor, facilitating seamless integration with Azure services and third-party tools. + +3. **CircleCI**: CircleCI is a popular cloud-based CI/CD platform known for its simplicity and scalability. It supports integration with version control systems like GitHub and Bitbucket, allowing teams to define build and deployment pipelines using YAML configuration files. CircleCI offers a wide range of pre-configured and customizable job types to meet diverse project requirements. + +GitHub Actions has emerged as one of the most popular options for automating workflows in software development pipelines. Its seamless integration with GitHub repositories, extensive marketplace of pre-built actions, and flexibility in creating custom workflows have contributed to its widespread adoption by developers and organizations alike. + +When choosing a CI/CD platform, consider factors such as integration capabilities, scalability, ease of use, and pricing. Evaluating these options based on your specific project requirements and existing development ecosystem will help determine the best fit for your team's needs. It's often beneficial to experiment with different platforms to find the one that aligns most closely with your workflow and objectives. + +## Unit Tests and Testing Frameworks in Python + +Unit tests are an essential part of software development, allowing developers to verify that individual components of their code behave as expected. The `unittest` module in Python provides a framework for organizing and running unit tests. Here's why you might consider using `unittest`: + +1. **Standard Library Inclusion**: `unittest` is part of Python's standard library, which means it's readily available without needing to install additional packages. This makes it convenient for projects that prefer minimal dependencies. + +2. **Built-in Assertions**: `unittest` offers a set of built-in assertion methods for verifying expected outcomes, such as `assertEqual`, `assertTrue`, and `assertRaises`. These assertions make it easy to write expressive and readable test cases. + +3. **Test Discovery**: `unittest` supports automatic test discovery, allowing you to organize your tests into separate modules and directories while effortlessly running them as a cohesive test suite. + +4. **Integration with IDEs and CI Tools**: `unittest` integrates well with popular IDEs like PyCharm, VS Code, and CI/CD platforms, enabling seamless test execution and reporting within your development workflow. + +While `unittest` is a solid choice for writing unit tests in Python, there are alternative frameworks that offer additional features and flexibility: + +1. **Pytest**: Pytest is a popular third-party testing framework known for its simplicity and powerful features. It provides concise syntax, fixtures for reusable test setup, parameterized testing, and extensive plugin support. Pytest excels in making test code more readable and maintainable. + +2. **Hypothesis**: Hypothesis is a property-based testing library that complements traditional example-based testing. Instead of writing specific test cases, you specify general properties that your code should satisfy. Hypothesis then generates input data automatically to thoroughly test these properties, uncovering edge cases and potential bugs. + +## Python Project Initialization + +When starting a new Python project, it's beneficial to use project templates that include predefined directory structures, configuration files, and boilerplate code to jumpstart development. Tools like `cookiecutter` and project templates such as `scicookie` provide convenient starting points for various project types: + +- **Cookiecutter**: Cookiecutter is a command-line utility that generates projects from project templates. It prompts you for project-specific details and then creates a customized project structure based on the selected template. There are many community-contributed templates available for various types of Python projects, including web applications, libraries, and data analysis projects. + +- **SciCookie**: SciCookie is a project template focused on the scientific python community, but it can be used by any python project. It includes a structured directory layout, documentation templates, and example code snippets. SciCookie helps streamline the setup process for scientific Python projects and encourages best practices in testing and documentation. + +In summary, while `unittest` provides a robust framework for writing unit tests in Python, alternative frameworks like Pytest and Hypothesis offer additional features and flexibility. When starting a new project, leveraging project templates such as `scicookie` with tools like `cookiecutter` can accelerate setup and promote best practices in project organization and testing. + +# What is a virtual enviroment and why is it important? + +A virtual environment is a self-contained directory that isolates the dependencies for a specific project, regardless of the programming language. It can house an interpreter (like Python) along with its associated libraries, but it can also manage dependencies for other languages and tools. This isolation ensures that the project's requirements don't conflict with those of other projects on the same system. + +When you create a virtual environment for each of your projects, it essentially creates a sandboxed environment where you can install packages and dependencies without affecting the global Python installation on your system. This isolation is crucial because different projects often require different versions of libraries or dependencies, and conflicts can arise if they share the same environment. + +Creating virtual environments is crucial for several reasons: + +- **Isolation:** Virtual environments allow you to isolate project dependencies, preventing conflicts between different projects that may require different versions of the same packages. This ensures that your projects remain stable and reproducible. + +- **Dependency Management:** By creating separate environments for each project, you can manage dependencies more effectively. You can install specific versions of packages for each project without affecting other projects or the system-wide installation. + +- **Experimentation:** Virtual environments provide a safe space for experimentation. You can try out new packages or versions without worrying about breaking existing projects or the system environment. + +- **Reproducibility:** Utilizing virtual environments not only streamlines collaboration but also enhances reproducibility in project workflows. By sharing environment configuration files such as environment.yml, collaborators ensure uniformity in dependencies and versions across all team members. This practice mitigates compatibility issues and fosters consistency, enabling seamless replication of results and facilitating smoother collaboration. + + +## Conda/Mamba +Conda is a package manager, environment manager, and dependency solver that can install and manage packages and their dependencies. Mamba is a fast, drop-in replacement for Conda that aims to provide faster package management operations. + +An agnostic language package manager is a tool that handles dependencies for software projects using multiple programming languages. Unlike traditional package managers tied to one language (like npm for JavaScript), agnostic managers work across languages. They simplify development for projects using various languages, offering features like dependency management and version control in a unified way. This helps maintain consistency and flexibility, especially in projects with mixed-language environments or complex architectures. + +### Installation: + + For Windows users, Anaconda may be a suitable choice, providing a comprehensive Python distribution along with Conda. For Linux and macOS users, Miniconda or Miniforge offers a lighter and more streamlined approach to managing environments and packages. You can download Miniforge from [here](https://github.com/conda-forge/miniforge?tab=readme-ov-file#downloadhere), which includes Miniconda, Conda Forge configuration, and Mamba. + +**Creating a Conda Enviroment** +To create a new Conda enviroment named `myenv`, open a terminal or command promt and use the following commands: + +```bash +$ conda create --name myenv +``` + +- On Windows: +```bash +$ activate myenv +``` + +- On Unix/ MacOs: +```bash +$ source activate myenv +``` + +### Conda-Forge: +Is a community-driven collection of recipes, build infrastructure, and distributions for the Conda package manager. It provides a vast repository of pre-built packages for various programming languages, including Python, R, C, C++, Rust, Go, Fortran, and more. Conda-Forge aims to offer high-quality, up-to-date packages that are well-integrated with Conda environments. + +Conda is not limited to managing Python packages; it can handle packages for various programming languages. This capability makes Conda a versatile tool for software development, allowing users to manage complex dependencies and libraries across different programming ecosystems. + +### Anaconda: + +Anaconda is a popular Python distribution that bundles the Python interpreter, Conda package manager, and a comprehensive set of pre-installed packages for scientific computing, data analysis, and machine learning. It includes tools like Jupyter Notebook, Spyder IDE, and many essential libraries for scientific computing, making it a convenient choice for data scientists and researchers. + +## Virtualenv +Virtualenv is a tool to create isolated Python environments. It's lightweight and widely used in the Python community. Here's how to set up Virtualenv: + +Installation: +- Ensure you have Python installed on your system. +- Install Virtualenv using pip (Python's package installer): + +```bash +$ pip install virtualenv +``` + +Create a new Virtualenv: +```bash +$ virtualenv myenv +``` + +Activate the enviroment: + +- On Windows: +```bash +$ myenv\Scripts\activate +``` + +- On Unix/MacOS: +```bash +$ source myenv/bin/activate +``` + +## Pipenv +Pipenv is a higher-level tool compared to Virtualenv. It aims to simplify and streamline the process of managing dependencies and environments for Python projects. Here's how to get started with Pipenv: + +**Installation:** + +Make sure you have Python installed on your system. +Install Pipenv using pip (Python's package installer): + +```bash +$ pip install pipenv +``` + +**Creating a new environment and managing dependencies:** + +Navigate to your project directory in the terminal. +Use Pipenv to create a new virtual environment and generate a Pipfile, which will manage your project's dependencies: + +```bash +$ pipenv --python 3.x +``` +Replace 3.x with your desired Python version. + +**Installing dependencies:** + +Use Pipenv to install packages for your project: + +```bash +$ pipenv install package-name +``` + +This will install the package and automatically update your Pipfile and Pipfile.lock. + +**Activating the environment:** + +Pipenv automatically activates the virtual environment when you enter the project directory. You'll see the name of the virtual environment displayed in your terminal prompt. + +**Deactivating the environment:** + +To deactivate the virtual environment and return to your global Python environment, simply use the exit command or close the terminal window. + +**Benefits of Pip** + +- *Dependency management:* Pipenv simplifies dependency management by automatically creating and managing a Pipfile and Pipfile.lock for each project. +- *Isolation:* Pipenv creates isolated environments for each project, preventing conflicts between dependencies. +- *Streamlined workflow:* Pipenv combines package installation, environment management, and dependency resolution into a single tool, streamlining the development process. + +## Pixi +Pixi, developed by prefix.dev, is a dynamic cross-platform package manager and workflow tool inspired by the Conda and Pypi ecosystems. It caters to developers seeking a unified experience across multiple programming languages, akin to renowned tools like Cargo or Yarn. Pixi's adaptability and robust feature set make it a valuable asset in modern software development environments. + +Key highlights of Pixi include: + +- **Multi-language Compatibility:** Pixi adeptly manages packages written in various languages including Python, C++, R, and more, offering versatility in development workflows. +- **Platform Agnosticism:** It seamlessly operates on Linux, Windows, macOS, and diverse hardware architectures, ensuring consistent performance across different environments. +- **Up-to-date Dependency Locking:** Pixi's dependency locking mechanism guarantees the stability of projects by maintaining consistent package versions across different development setups. +- **Intuitive Command-line Interface:** With a clean and user-friendly interface, Pixi enhances developer productivity and ease of use, promoting efficient workflow management. +- **Flexible Installation Options:** Pixi accommodates both per-project and system-wide installations, allowing developers to tailor its usage according to specific project requirements. + +For comprehensive information, detailed installation guidelines, and practical examples, visit the official [Pixi website](https://pixi.sh/latest). Explore Pixi today to streamline your development process across multiple languages with ease. + + +## Conclusion + +The proficiency in specific techniques and methodologies is crucial for anyone looking to make meaningful contributions to open-source projects. This article delineates the essential skills and practices required for effective participation, including but not limited to, adeptness with collaborative tools, version control systems, adherence to coding standards, and familiarity with contribution guidelines. Emphasizing the significance of tailored mentorship, accessible documentation, and active engagement within the community, the article serves as a comprehensive guide for enhancing the quality and impact of contributions. By adopting best practices in code review, project management, and fostering an inclusive dialogue, contributors can significantly elevate the collaborative dynamics and innovation within open-source endeavors. Thus, reading this article is instrumental for those aiming to navigate the complexities of open-source projects successfully and contribute to the advancement of collective scientific and technological objectives. diff --git a/pages/blog/fqlearn-ternary-plot/index.qmd b/pages/blog/fqlearn-ternary-plot/index.qmd new file mode 100644 index 000000000..6e602da16 --- /dev/null +++ b/pages/blog/fqlearn-ternary-plot/index.qmd @@ -0,0 +1,514 @@ +--- +title: + "Plotting ternary phase diagrams for solving thermodynamics problems using + fqlearn" +slug: fqlearn-ternary-plot +date: 2024-05-29 +authors: ["Faith Njamiu Hunja"] +tags: [open science, thermodynamics, visualization, ternary phase diagram] +categories: [science, python, visualization] +description: | + An article on how to use the open source fqlearn library to plot three + phase diagrams used in thermodynamics, also known as ternary plots. + +thumbnail: "/header.png" +template: "blog-post.html" +--- + + + +An article on how to use the open source Fqlearn library to plot three phase +diagrams used in thermodynamics, also known as ternary plots. + + + +## Introduction + +During the Open Science Labs Q1 2024 internship, I worked on the Fqlearn +project. [Fqlearn](https://github.com/osl-pocs/fqlearn) is an open source python +library, currently in development, which aims to facilitate the teaching of mass +transfer and thermodynamics. + +My main task involved developing methods to use a three phase diagram to solve +thermodynamics problems graphically. For this purpose, I wrote code for the +[`ThreeComponent.py`](https://github.com/osl-pocs/fqlearn/blob/main/src/fqlearn/ThreeComponent.py) +class, as well as corresponding +[tests](https://github.com/osl-pocs/fqlearn/blob/main/tests/test_three_component.py) +for the class. + +### Ternary phase diagram + +A ternary plot, ternary graph, triangle plot, simplex plot, or Gibbs triangle is +a barycentric plot on three variables which sum to a constant. It graphically +depicts the ratios of the three variables as positions in an equilateral +triangle. It is used in physical chemistry, petrology, mineralogy, metallurgy, +and other physical sciences to show the compositions of systems composed of +three species. Ternary plots are tools for analyzing compositional data in the +three-dimensional case [[1](https://en.wikipedia.org/wiki/Ternary_plot)]. + +![A ternary plot example, plotted using Fqlearn](plot.png) + +In a ternary plot, the values of the three variables a, b, and c must sum to +some constant, K. Usually, this constant is represented as 1.0 or 100%. Because +a + b + c = K for all substances being graphed, any one variable is not +independent of the others, so only two variables must be known to find a +sample's point on the graph: for instance, c must be equal to K − a − b. Because +the three numerical values cannot vary independently—there are only two degrees +of freedom—it is possible to graph the combinations of all three variables in +only two dimensions. + +The advantage of using a ternary plot for depicting chemical compositions is +that three variables can be conveniently plotted in a two-dimensional graph. +Ternary plots can also be used to create phase diagrams by outlining the +composition regions on the plot where different phases exist +[[1](https://en.wikipedia.org/wiki/Ternary_plot)]. + +## Methods + +To begin with, we import the libraries required for plotting ternary phase +diagrams. We used +[`python-ternary`](https://github.com/marcharper/python-ternary), a plotting +library that uses matplotlib to make ternary plots. Using this library, many +features could be added to the fqlearn library for various purposes, as +described in this article. + +```python +import matplotlib as plt +import numpy as np +import ternary +from scipy.interpolate import CubicSpline +``` + +Then we define our class, `ThreeComponent`, in which we create some functions, a +few of which will be explained in this article. To see how these functions work, +we create some variables, which are lists containing an unordered list of +tuples, each containing 3 values, representing the x, y and z values in the form +`[(x,y,z)]`. + +```python +right_eq_line = [(0.02, 0.02, 0.96), (0.025, 0.06, 0.915), (0.03, 0.1, 0.87), + (0.035, 0.16, 0.805), (0.04, 0.2, 0.76), (0.045, 0.25, 0.705), + (0.05, 0.3, 0.65), (0.07, 0.36, 0.57), (0.09, 0.4, 0.51), + (0.14, 0.48, 0.38), (0.33, 0.49, 0.18)] +left_eq_line = [(0.97, 0.01, 0.02), (0.95, 0.03, 0.02), (0.91, 0.06, 0.03), + (0.88, 0.09, 0.03), (0.83, 0.13, 0.04), (0.79, 0.17, 0.04), + (0.745, 0.2, 0.055), (0.68, 0.26, 0.06), (0.62, 0.3, 0.08), + (0.49, 0.4, 0.11), (0.33, 0.49, 0.18)] + +points = left_eq_line + right_eq_line +``` + +We also set the scale of our plot to 100 in the `__init__ function`. This is the +value that each tuple must sum up to. + +```python +self.scale = 100 +``` + +To start using the Three Component class, we can use the following code: + +```python +from fqlearn import ThreeComponent +model = ThreeComponent() +``` + +We can then call the functions as needed. We define a function `sort_points` +that sorts the values it receives as an argument. The points are added using the +`add_point` function, which ensures that the argument is not an empty list, +removes duplicate tuples, and multiplies each point in the tuple by the scale. + +```python +def add_point(self, points): + # Check if points is an empty list + if not points: + raise ValueError( + "The 'points' list cannot be empty. Please provide valid points." + ) + + # Remove duplicate points + points_to_plot = list(set(points)) + + # Multiply each point by the scale + points_to_plot = [ + (x * self.scale, y * self.scale, z * self.scale) + for x, y, z in points_to_plot + ] + + # Add the points to the plot + self.tax.scatter(points_to_plot, linewidth=1.0, marker="o", color="red") + return points_to_plot +``` + +The `sort_points` function sorts the tuples using the x value in each tuple, +then adds them to a new list. This allows us to have the tuples sorted in +ascending order along the x-axis. The function also ensures that all points in +the tuple(s) in the sorted list add up to the scale. + +```python +def sort_points(self, points): + points_to_plot = self.add_point(points) + # Sort the points in ascending order + xyz = [(x, y, z) for x, y, z in points_to_plot] + sorted_points = sorted(xyz, key=lambda m: m[0]) + + # New list to store sorted points + new_sorted_points = [] + + # Check if the points are in a list of lists or a single list + if isinstance( + sorted_points[0], (int, float) + ): # Check if the first element of points is a number + assert sorted_points[0] + sorted_points[1] + sorted_points[2] == self.scale + new_sorted_points.append(sorted_points) + else: + # If the points are in a list of lists + for sorted_point in sorted_points: + assert sorted_point[0] + sorted_point[1] + sorted_point[2] == self.scale + new_sorted_points.append(sorted_point) + + # Add the points to the plot + self.tax.scatter(new_sorted_points, linewidth=1.0, marker="o", color="red") + return new_sorted_points +``` + +If we print the returned values, we get the following output, where each tuple +is arranged in ascending order according to the x values: + +```console +[(2.0, 2.0, 96.0), (2.5, 6.0, 91.5), (3.0, 10.0, 87.0), (3.5000000000000004, 16.0, 80.5), (4.0, 20.0, 76.0), (4.5, 25.0, 70.5), (5.0, 30.0, 65.0), (7.000000000000001, 36.0, 56.99999999999999), (9.0, 40.0, 51.0), (14.000000000000002, 48.0, 38.0), (33.0, 49.0, 18.0), (49.0, 40.0, 11.0), (62.0, 30.0, 8.0), (68.0, 26.0, 6.0), (74.5, 20.0, 5.5), (79.0, 17.0, 4.0), (83.0, 13.0, 4.0), (88.0, 9.0, 3.0), (91.0, 6.0, 3.0), (95.0, 3.0, 2.0), (97.0, 1.0, 2.0)] +``` + +We can then call the `plot` function, to plot the ternary phase diagram in order +to visualize the plotted points. + +```python +def plot(self): + self.tax.clear_matplotlib_ticks() + self.tax.get_axes().axis("off") + self.tax.legend() + ternary.plt.show() +``` + +We obtain the ternary plot shown below: + +![Adding points to the ternary plot](add_point.png) + +We define the `composition_line` function which plots equilibrium lines joining +the tuples corresponding to the two compositions in each index in the list of +tuples. This function first multiplies each point in each tuple by the scale, +then sorts the list of the left composition in ascending order, and the list of +the right composition in descending order. For each tuple, the x, y and z values +are extracted, then joined using equilibrium lines. + +```python +def composition_line(self, left_eq_line, right_eq_line): + # Multiply each point by the scale + new_left_eq_line = [ + (x * self.scale, y * self.scale, z * self.scale) for x, y, z in left_eq_line + ] + new_right_eq_line = [ + (x * self.scale, y * self.scale, z * self.scale) + for x, y, z in right_eq_line + ] + + # Sort the left points in ascending order + xyz = [(x, y, z) for x, y, z in new_left_eq_line] + sorted_left_eq_line = sorted(xyz, key=lambda m: m[0]) + # Sort the right points in descending order + xyz = [(x, y, z) for x, y, z in new_right_eq_line] + sorted_right_eq_line = sorted(xyz, key=lambda m: m[0], reverse=True) + + for i in range(len(left_eq_line)): + # Ensure all points add up to the scale + pointA = sorted_left_eq_line[i] + assert sum(pointA) == self.scale + pointB = sorted_right_eq_line[i] + assert sum(pointB) == self.scale + + # Extract x and y coordinates of each point + xA, yA, zA = pointA + xB, yB, zB = pointB + + # Add the two points to the plot + self.tax.scatter([(xA, yA, zA), (xB, yB, zB)], marker="s", color="red") + # Plot a line connecting the two points + self.tax.plot( + [(xA, yA, zA), (xB, yB, zB)], + linewidth=1.0, + color="blue", + ) +``` + +We obtain the following ternary plot: + +![Equilibrium line joining two compositions](solute_points.png) + +We can calculate the slope of the equilibrium lines plotted above using the +function `eq_slope`. We loop through each tuple of each composition in each +index, extract the x and y values, and find the slope, by dividing the change in +y by the change in x for each index. The function returns the average slope. + +```python +def eq_slope(self, right_eq_line, left_eq_line): + # Multiply each point by the scale + right_eq_line = [ + (x * self.scale, y * self.scale, z * self.scale) + for x, y, z in right_eq_line + ] + left_eq_line = [ + (x * self.scale, y * self.scale, z * self.scale) for x, y, z in left_eq_line + ] + + # Sort the right points in ascending order + xyz = [(x, y, z) for x, y, z in right_eq_line] + right_eq = sorted(xyz, key=lambda m: m[0]) + # Sort the left points in descending order + xyz = [(x, y, z) for x, y, z in left_eq_line] + left_eq = sorted(xyz, key=lambda m: m[0], reverse=True) + + slopes = [] + + for i in range(len(right_eq_line)): + pointA = right_eq[i] + assert sum(pointA) == self.scale + pointB = left_eq[i] + assert sum(pointB) == self.scale + + # Extract x and y coordinates of each point + xA, yA, _ = pointA + xB, yB, _ = pointB + + # Calculate the slope of the line joining the points + if xA - xB != 0: # Check for vertical line + slope = (yA - yB) / (xA - xB) + slopes.append(slope) + else: + # For vertical lines, return None for slope + slopes.append(0) + print("Slope = ", slopes) + + # Calculate average of the slopes + avg_slope = sum(slopes) / len(slopes) + print("Average slope = ", avg_slope) + return avg_slope +``` + +We obtain the following output: + +```console +Slope = [-0.010526315789473684, -0.032432432432432434, -0.045454545454545456, -0.08284023668639054, -0.08860759493670886, -0.10738255033557047, -0.14388489208633093, -0.16393442622950818, -0.18867924528301888, -0.22857142857142856, 0] +Average slope = -0.09930124252776436 +``` + +We can also plot a ternary phase diagram with an equilibrium line joining all +the plotted points, like in a graph, by calling the `add_eq_line` function. + +```python +def add_eq_line(self, right_eq_line, left_eq_line): + # Add the points + self.right_eq_line = self.sort_points(right_eq_line) + self.left_eq_line = self.sort_points(left_eq_line) + eq_line = self.right_eq_line + self.left_eq_line + + # Remove duplicate points + eq_line_plot = list(set(eq_line)) + + # Sort the points in ascending order + xyz = [(x, y, z) for x, y, z in eq_line_plot] + sorted_eq = sorted(xyz, key=lambda m: m[0]) + + self.tax.plot(sorted_eq, linewidth=1.0, color="blue", label="Equilibrium line") +``` + +We get the ternary plot below: + +![Joining points with an equilibrium line](eq_line.png) + +However, the equilibrium line plotted above is not smooth. To generate a smooth +line, we use the `interpolate_points` function. + +We first sort the points using the `sort_points` function. Then we extract the x +and y values from the sorted points, ignoring the z values which will not be +used. Variable `x` becomes a list of all x values and variable `y` becomes a +list of all y values. After that, we perform cubic spline interpolation on the x +and y values using the CubicSpline function, imported from the scipy.interpolate +module of SciPy's library +[[2](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.CubicSpline.html)]. +`bc_type="natural"` specifies the natural boundary conditions, meaning the +second derivative of the spline at the boundaries will be set to zero. `x_cubic` +is set to a linearly spaced array of values ranging from 0 to 100 with 100 +number of points, which is the value of `self.scale`, and `y_cubic` contains the +corresponding y values interpolated using the cubic spline function `f`. Then we +filter out any points outside the specified range of 0 to 100 for both `x_cubic` +and `y_cubic`, and use the `np.column_stack` function to combine `x_cubic` and +`y_cubic` into a single 2D array, which is returned by the function as +`interpolated_points`. + +```python +def interpolate_points(self, points): + sorted_points = self.sort_points(points) + + # Cubic spline interpolation + x = [x for x, y, _ in sorted_points] + y = [y for x, y, _ in sorted_points] + + f = CubicSpline(x, y, bc_type="natural") + x_cubic = np.linspace(0, self.scale, self.scale) + y_cubic = f(x_cubic) + + # Remove negative points + interpolated_points = [ + [i, j] + for i, j in np.column_stack((x_cubic, y_cubic)) + if 0 <= i <= self.scale and 0 <= j <= self.scale + ] + + return interpolated_points +``` + +Printing the output of the function, we obtain the output below, showing the +interpolated points: + +```console +[[2.0202020202020203, 2.1691472640742586], [3.0303030303030303, 10.329514857514697], [4.040404040404041, 20.340775738329878], [5.050505050505051, 30.41604117022922], [6.0606060606060606, 34.9725244736696], [7.070707070707071, 36.0788150823154], [8.080808080808081, 37.866519430111914], [9.090909090909092, 40.207639450509895], [10.101010101010102, 42.33305618527275], [11.111111111111112, 44.15164951408806], [12.121212121212121, 45.70152886497335], [13.131313131313131, 47.02080366594621], [14.141414141414142, 48.147569717383064], [15.151515151515152, 49.11261947676193], [16.161616161616163, 49.927423194367556], [17.171717171717173, 50.600292249170636], [18.181818181818183, 51.13953802014185], [19.191919191919194, 51.55347188625187], [20.202020202020204, 51.850405226471395], [21.212121212121215, 52.038649419771076], [22.222222222222225, 52.12651584512162], [23.232323232323235, 52.122315881493684], [24.242424242424242, 52.03436090785797], [25.252525252525253, 51.87096230318516], [26.262626262626263, 51.640431446445916], [27.272727272727273, 51.35107971661092], [28.282828282828284, 51.011218492650855], [29.292929292929294, 50.62915915353641], [30.303030303030305, 50.213213078238255], [31.313131313131315, 49.771691645727074], [32.323232323232325, 49.31290623497355], [33.333333333333336, 48.84510804535748], [34.343434343434346, 48.37284930204433], [35.35353535353536, 47.89489752249601], [36.36363636363637, 47.409516570717145], [37.37373737373738, 46.91497031071236], [38.38383838383839, 46.40952260648627], [39.3939393939394, 45.891437322043494], [40.40404040404041, 45.358978321388655], [41.41414141414142, 44.81040946852637], [42.42424242424243, 44.24399462746127], [43.43434343434344, 43.65799766219796], [44.44444444444445, 43.050682436741056], [45.45454545454546, 42.4203128150952], [46.46464646464647, 41.765152661265006], [47.47474747474748, 41.083465839255076], [48.484848484848484, 40.373516213070054], [49.494949494949495, 39.63371670174959], [50.505050505050505, 38.86607499990182], [51.515151515151516, 38.076288591118626], [52.525252525252526, 37.270223020764256], [53.535353535353536, 36.45374383420295], [54.54545454545455, 35.63271657679892], [55.55555555555556, 34.81300679391643], [56.56565656565657, 34.000480030919704], [57.57575757575758, 33.201001833172974], [58.58585858585859, 32.42043774604049], [59.5959595959596, 31.664653314886465], [60.60606060606061, 30.93951408507516], [61.61616161616162, 30.250885601970786], [62.62626262626263, 29.60336029919871], [63.63636363636364, 28.983912044764498], [64.64646464646465, 28.366648419185424], [65.65656565656566, 27.72538388514391], [66.66666666666667, 27.0339329053224], [67.67676767676768, 26.26610994240331], [68.68686868686869, 25.39917799115036], [69.6969696969697, 24.44860975371087], [70.70707070707071, 23.453665293050822], [71.71717171717172, 22.45396405508463], [72.72727272727273, 21.489125485726685], [73.73737373737374, 20.59876903089141], [74.74747474747475, 19.822230508859366], [75.75757575757576, 19.162761793258028], [76.76767676767678, 18.552564414178864], [77.77777777777779, 17.915539652280298], [78.7878787878788, 17.175588788220747], [79.7979797979798, 16.27016258697005], [80.80808080808082, 15.240129979240486], [81.81818181818183, 14.174027140890017], [82.82828282828284, 13.160644754568583], [83.83838383838385, 12.27416074496895], [84.84848484848486, 11.4905826393484], [85.85858585858587, 10.745488901010873], [86.86868686868688, 9.974332435176901], [87.87878787878789, 9.112566147067007], [88.8888888888889, 8.11684009659671], [89.89898989898991, 7.065697936762742], [90.90909090909092, 6.081168542871885], [91.91919191919193, 5.257512388433542], [92.92929292929294, 4.543021889369125], [93.93939393939395, 3.8382841183777483], [94.94949494949496, 3.043859233178794], [95.95959595959597, 2.0885249765853326], [96.96969696969697, 1.0322241687602192]] +``` + +Next, we define a function, `div_half`, to divide an equilibrium line in half, +and thus dividing the interpolated points in the left side from those in the +right side. Before that, we define some helper functions, `derivative` and +`min_diff`. + +The `derivative` function extracts the x and y values, then calculates the n-th +discrete difference between these values using NumPy's np.diff +[[3](https://numpy.org/doc/stable/reference/generated/numpy.diff.html)]. The +first value of dydx is given by dydx[i] = (y[i+1] - y[i])/(x[i+1] - x[i]) along +the given axis and higher differences are calculated by using np.diff +recursively. + +```python +def derivative(self, points): + points_to_derive = self.interpolate_points(points) + + # Extract x and y values + x = [x for x, _ in points_to_derive] + y = [y for _, y in points_to_derive] + + # Calculate derivative + dydx = np.diff([y]) / np.diff([x]) + + return dydx +``` + +We can print out dydx to see the output: + +```console +[[ 8.07876392e+00 9.91114827e+00 9.97451278e+00 4.51091847e+00 + 1.09522770e+00 1.76982730e+00 2.31770882e+00 2.10416257e+00 + 1.80040740e+00 1.53438056e+00 1.30608205e+00 1.11549839e+00 + 9.55399262e-01 8.06655680e-01 6.66140364e-01 5.33853313e-01 + 4.09794527e-01 2.93964007e-01 1.86361751e-01 8.69877611e-02 + -4.15796399e-03 -8.70754239e-02 -1.61764619e-01 -2.28225548e-01 + -2.86458213e-01 -3.36462612e-01 -3.78238746e-01 -4.11786615e-01 + -4.37106218e-01 -4.54197557e-01 -4.63120208e-01 -4.67536156e-01 + -4.73172262e-01 -4.80527142e-01 -4.89600797e-01 -5.00393227e-01 + -5.12904432e-01 -5.27134411e-01 -5.43083164e-01 -5.60750693e-01 + -5.80136996e-01 -6.01242073e-01 -6.24065925e-01 -6.48608552e-01 + -6.74869954e-01 -7.02850130e-01 -7.32401516e-01 -7.59965285e-01 + -7.81888545e-01 -7.98004915e-01 -8.08314395e-01 -8.12816985e-01 + -8.11512685e-01 -8.04401495e-01 -7.91483416e-01 -7.72758446e-01 + -7.48226587e-01 -7.17887838e-01 -6.81742198e-01 -6.41050050e-01 + -6.13253772e-01 -6.11090989e-01 -6.34851889e-01 -6.84536470e-01 + -7.60144733e-01 -8.58262632e-01 -9.41062555e-01 -9.84995016e-01 + -9.89704226e-01 -9.55190184e-01 -8.81452890e-01 -7.68773137e-01 + -6.52874028e-01 -6.04095405e-01 -6.30654514e-01 -7.32551355e-01 + -8.96371939e-01 -1.01973228e+00 -1.05544181e+00 -1.00324856e+00 + -8.77619170e-01 -7.75742325e-01 -7.37642801e-01 -7.63444901e-01 + -8.53148625e-01 -9.85768790e-01 -1.04063074e+00 -9.74684100e-01 + -8.15419593e-01 -7.07345594e-01 -6.97690393e-01 -7.86480636e-01 + -9.45780914e-01 -1.04573780e+00]] +``` + +Then we use the `min_diff` function to find the index of the point that is at +the centre of the equilibrium line, which divides it into 2 halves. Starting +with an index value of zero and a minimum difference given by the difference +between the absolute of the first `dydx` value and the average slope, we iterate +over the indices of `dydx` to find the index of the `dydx` value which is +closest to the average slope `avg_slope`. + +```python +def min_diff(self, right_eq_line, left_eq_line): + self.points = right_eq_line + left_eq_line + dydx = self.derivative(self.points) + avg_slope = self.eq_slope(right_eq_line, left_eq_line) + + min_index = 0 + # Initialize min_diff_value with the first element difference + min_diff_value = abs(dydx[0][0] - avg_slope) + + # Iterate over indices of dydx + for index in range(0, dydx.size): + diff = abs(dydx[0][index] - avg_slope) + if diff < min_diff_value: + min_diff_value = diff + min_index = index + + return min_index +``` + +We then define the `div_half` function that divides the equilibrium line +precisely in half. We use the interpolated points from the `interpolate_points` +function, which ensures that the point of division is very precise. Then we use +the `min_diff` function to obtain the index of the point dividing the +equilibrium line in half. Using this index, we can halve the interpolated +points, and plot two equilibrium lines on the right and left side of the ternary +plot using these points. + +```python +def div_half(self, right_eq_line, left_eq_line): + # Add the points + self.points = right_eq_line + left_eq_line + interpolated_points = self.interpolate_points(self.points) + + # Use index to separate right and left side + index = self.min_diff(right_eq_line, left_eq_line) + self.interpolated_right_side = interpolated_points[index:] + self.interpolated_left_side = interpolated_points[: index + 1] + + # Plot the curve + self.tax.plot( + self.interpolated_right_side, + linewidth=1.0, + color="blue", + label="Right interpolated curve", + ) + self.tax.plot( + self.interpolated_left_side, + linewidth=1.0, + color="orange", + label="Left interpolated curve", + ) +``` + +We obtain the following ternary plot: + +![Dividing the interpolated points in half](div_half.png) + +## Acknowledgements + +I would like to thank the [Open Science Labs](https://opensciencelabs.org/) and +The Graph Network for giving me the opportunity to learn and gain experience in +open source through this internship. I also thank +[Ever Vino](https://github.com/EverVino) for his guidance and mentorship +throughout this internship program. If you would like to connect with me, you +can find me on [LinkedIn](https://www.linkedin.com/in/faithhunja), or check out +my [personal website](https://faithhunja.github.io/). + +## References + +[1] Ternary plots - Wikipedia: + +[2] scipy.interpolate.CubicSpline - SciPy v1.13.1 Manual: + + +[3] numpy.diff - NumPy v1.26 Manual: + diff --git a/pages/blog/fqlearn-visualizacion-datos/index.qmd b/pages/blog/fqlearn-visualizacion-datos/index.qmd new file mode 100644 index 000000000..1ac96093d --- /dev/null +++ b/pages/blog/fqlearn-visualizacion-datos/index.qmd @@ -0,0 +1,450 @@ +--- +title: + "Explorando fqlearn: Potenciando el Análisis y Diseño de Procesos + Termodinámicos" +slug: fqlearn-visualizacion-datos +date: 2023-12-14 +authors: ["José María"] +tags: [quimica, fisica, datos, visualizacion, docencia] +categories: [ciencia, python, docencia, datos] +description: | + En este artículo se describe la utilidad que ofrece una visualización sencilla + de cálculos termodinámicos, útil para el manejo de docentes y el aprendizaje + por parte de los alumnos interesados en el tema. Usando las funciones disponibles + en las clases de McCabe-Thiele, y SteamTable. +thumbnail: "/header.png" +template: "blog-post.html" +--- + + + +En este artículo se describe la utilidad que ofrece una visualización sencilla +de cálculos termodinámicos, útil para el manejo de docentes y el aprendizaje por +parte de los alumnos interesados en el tema. Usando las funciones disponibles en +las clases de McCabe-Thiele, y SteamTable. + + + +# Explorando fqlearn: Potenciando el Análisis y Diseño de Procesos Termodinámicos + +En el apasionante mundo de la ingeniería química, la biblioteca `fqlearn` emerge +como una herramienta valiosa, ofreciendo capacidades significativas para el +análisis y diseño de procesos. Diseñada para mejorar la visualización de datos y +servir como una herramienta educativa, `fqlearn` se posiciona como un recurso +versátil para ingenieros y científicos en la industria química. + +## Implementación de la Clase McCabe-Thiele para Análisis Gráfico de Destilación Binaria en Python + +La clase `McCabeThiele` dentro de `fqlearn` brinda funcionalidades +especializadas para llevar a cabo cálculos de equilibrio líquido-vapor +utilizando el método de McCabe-Thiele. Este método es esencial en el diseño de +columnas de destilación, permitiendo determinar el número óptimo de etapas +necesarias para separar componentes en una mezcla. + +La clase McCabeThiele es una implementación en Python del método de +McCabe-Thiele, una técnica gráfica fundamental en el diseño de columnas de +destilación para procesos químicos. Este método proporciona una manera visual de +analizar y dimensionar sistemas de destilación binaria. A continuación, se +presenta una descripción detallada de la clase y sus principales componentes. + +# Ejemplo de uso de McCabeThiele en fqlearn + +```python +# Importar la clase McCabeThiele de la biblioteca fqlearn +from fqlearn.McCabeThiele import McCabeThiele + + +# Crear una instancia del modelo McCabeThiele +model = McCabeThiele() + +# Establecer los compuestos para el análisis (metanol y agua) +model.set_data(compound_a="methanol", compound_b="water") + +# Establecer las composiciones del destilado (xD) y del líquido (xW) +model.set_compositions(xD=0.94, xW=0.05) + +# Establecer los parámetros de alimentación (relación de calor q y composición xF) +model.set_feed(q=0.5, xF=0.5) + +# Resolver el sistema y calcular el número de etapas necesarias +model.solve() + +# Imprimir información relevante sobre el reflujo mínimo y la composición líquida de salida +model.describe() +``` + +## Función describe + +La función 'describe' imprime en pantalla información sobre cada etapa en el +proceso de McCabe-Thiele. + +- El reflujo mínimo es de: 0.7480780119884876 +- La composición líquida de salida: 0.05 + +### Composición de entrada y salida en cada etapa: + +- Etapa 1: Entrada = 0.9400, Salida = 0.9400 +- Etapa 2: Entrada = 0.8783, Salida = 0.9400 +- Etapa 3: Entrada = 0.8783, Salida = 0.9074 +- Etapa 4: Entrada = 0.7933, Salida = 0.9074 +- Etapa 5: Entrada = 0.7933, Salida = 0.8624 +- Etapa 6: Entrada = 0.6840, Salida = 0.8624 +- Etapa 7: Entrada = 0.6840, Salida = 0.8046 +- Etapa 8: Entrada = 0.5611, Salida = 0.8046 +- Etapa 9: Entrada = 0.5611, Salida = 0.7396 +- Etapa 10: Entrada = 0.4316, Salida = 0.7396 + +Número total de etapas: 9 + +```python +# Generar un gráfico que muestra las curvas de equilibrio, la línea de alimentación y las etapas del proceso +model.plot() +``` + +## Función plot + +La función plot de McCabe-Thiele le muestra al usuario la gráfica de destilación +binaria, con todas las etapas y demás información que el usuario pueda solicitar + +![ejemplo de mcabethiele](mccabethiele.png) + +**Ejes:** Los ejes X e Y representan la composición molar x y y respectivamente, +variando de 0 a 1. + +**Líneas:** + +- _Equilibrio (azul):_ Esta línea se extiende diagonalmente a través del gráfico + y representa el equilibrio entre las fases líquida y vapor de la mezcla. + +- _Intersección (roja):_ Esta línea marca un punto específico de intersección + entre las curvas ROP y SOP. + +- _ROP (naranja):_ Representa la línea de operación de rectificación, que es la + sección de la columna de destilación por encima del plato de alimentación. + +- _SOP (verde):_ Representa la línea de operación de agotamiento, que es la + sección de la columna de destilación por debajo del plato de alimentación. + +- _Pasos (púrpura):_ Estos indican los platos teóricos necesarios para la + separación. Cada paso representa un plato teórico en la columna de + destilación. + +Este gráfico es útil para determinar el número de platos teóricos necesarios en +una columna de destilación para separar una mezcla de metanol-agua. Los platos +teóricos son una medida de la eficiencia de la columna de destilación. Cuantos +más platos teóricos tenga una columna, más eficiente será la separación. + +**Importancia de la Destilación Binaria de McCabe-Thiele:** + +La destilación binaria de McCabe-Thiele es un método crucial en ingeniería +química y procesos de separación, utilizado para analizar y diseñar columnas de +destilación. A continuación, se detallan algunas de las razones clave por las +cuales la destilación binaria de McCabe-Thiele es importante: + +**Diseño Eficiente de Columnas de Destilación:** + +- Proporciona un enfoque sistemático y gráfico para el diseño de columnas de + destilación binarias, permitiendo determinar el número mínimo de etapas + requeridas para una separación específica. +- Facilita la comprensión de la eficiencia de una columna de destilación y la + relación entre el número de etapas teóricas y reales. + +**Optimización de Procesos de Separación:** + +- Permite optimizar los procesos de separación de mezclas binarias, como la + purificación de componentes en la industria química y petroquímica. +- Ayuda en la selección adecuada de condiciones operativas para lograr la + separación deseada de componentes. + +**Análisis Detallado de Comportamientos de Mezclas:** + +- Proporciona una representación visual y detallada de la composición de las + mezclas en cada etapa de la columna de destilación, lo que facilita el + análisis del comportamiento de los componentes. +- Permite identificar el reflujo mínimo necesario para una separación específica + y evaluar la eficiencia del proceso. + +**Entendimiento de Interacciones de Componentes:** + +- Ayuda a comprender las interacciones entre los componentes de la mezcla y cómo + influyen en el proceso de destilación. +- Permite evaluar la viabilidad y eficiencia de la separación de componentes + específicos en una mezcla binaria. + +**Herramienta Educativa y de Investigación:** + +- Se utiliza como herramienta educativa para estudiantes en ingeniería química y + campos relacionados para comprender los principios fundamentales de la + destilación. +- Es una herramienta valiosa para la investigación y desarrollo de procesos de + destilación más eficientes y sostenibles. + +**Análisis de Sistemas de Ingeniería de Procesos:** + +- Facilita el análisis de sistemas de ingeniería de procesos que involucran la + destilación de mezclas binarias, contribuyendo a la mejora continua y la + optimización de operaciones. + +## Implementación de la Clase SteamTable para una consulta eficáz de las tablas de vapor + +La clase SteamTable ofrece capacidades para gestionar y representar datos +termodinámicos asociados con las fases de vapor y líquido, centrándose +especialmente en tablas vapor. + +Esta función es valiosa en aplicaciones donde se requiere conocer propiedades +termodinámicas precisas para una temperatura específica, por ejemplo, en el +diseño y análisis de sistemas termodinámicos, procesos de ingeniería, o +cualquier escenario donde la temperatura es un parámetro crítico. Proporciona +una herramienta flexible y útil para obtener datos detallados en puntos +específicos de interés en la tabla termodinámica de vapor. + +# Ejemplo de uso de SteamTable en fqlearn + +```python +from fqlearn.SteamTable import SteamTable + +# Crea una instancia llamada sistema con SteamTable, y carga las tablas en la base de datos +sistema = SteamTable() + +# Imprime al usuario las tablas pre-cargadas en la base de datos +sistema.data() + +# Le muestra al usuario el gráfico solicitado +sistema.plot('PV') +sistema.plot('PT') +sistema.plot('TV') +sistema.plot3d() + +# Le da al usuario información disponible en las tablas con la temperatura proporcionada +sistema.point(t = 100) +``` + +## Función data + +La función 'data' tiene la capacidad de imprimir las tablas de vapor que están +cargadas en la base de datos, mostrándolas con incrementos de 20 en 20. Este +enfoque permite al usuario visualizar de manera rápida y eficiente la tabla, +facilitando así la realización de consultas de forma ágil. + +| t | p | rhoL | rhoV | hL | hV | delta_h | sL | sV | delta_s | vL | vV | +| ----- | --------- | ------ | --------- | ------ | ------ | ------- | -------- | ------ | ------- | ------- | ------- | +| 16.0 | 0.0018188 | 998.9 | 0.013 645 | 67.17 | 2530.2 | 2463.0 | 0.238 97 | 8.757 | 8.518 | 1.0011 | 73286.0 | +| 36.0 | 0.0059479 | 993.64 | 0.041 790 | 150.81 | 2566.3 | 2415.5 | 0.518 67 | 8.3321 | 7.8135 | 1.0064 | 23929.0 | +| 72.0 | 0.034 | 976.58 | 0.215 07 | 301.45 | 2629.5 | 2328.1 | 0.979 49 | 7.7246 | 6.7451 | 1.02398 | 4649.6 | +| 92.0 | 0.075684 | 963.94 | 0.454 91 | 385.46 | 2662.8 | 2277.3 | 1.2160 | 7.4526 | 6.2367 | 1.03741 | 2198.2 | +| 128.0 | 0.2545 | 936.52 | 1.4149 | 537.85 | 2717.3 | 2179.5 | 1.6135 | 7.0465 | 5.433 | 1.06778 | 706.75 | +| 148.0 | 0.45118 | 918.87 | 2.422 | 623.56 | 2743.5 | 2119.9 | 1.8214 | 6.8552 | 5.0337 | 1.0883 | 412.88 | +| 184.0 | 1.0985 | 882.69 | 5.6279 | 780.75 | 2780.6 | 1999.8 | 2.1779 | 6.5525 | 4.3746 | 1.1329 | 177.69 | +| 204.0 | 1.6893 | 859.95 | 8.5186 | 870.35 | 2794.3 | 1923.9 | 2.3683 | 6.4004 | 4.0322 | 1.16286 | 117.39 | +| 240.0 | 3.3469 | 813.37 | 16.749 | 1037.6 | 2803.0 | 1765.4 | 2.702 | 6.1423 | 3.4403 | 1.22946 | 59.705 | +| 260.0 | 4.6923 | 783.63 | 23.712 | 1135.0 | 2796.6 | 1661.6 | 2.8849 | 6.0016 | 3.1167 | 1.27612 | 42.173 | +| 296.0 | 8.1143 | 720.23 | 43.21 | 1322.8 | 2757.0 | 1434.2 | 3.2174 | 5.7373 | 2.5199 | 1.38845 | 23.143 | +| 316.0 | 10.699 | 676.81 | 60.361 | 1437.8 | 2712.3 | 1274.5 | 3.4097 | 5.5729 | 2.1632 | 1.47751 | 16.567 | +| 352.0 | 16.939 | 566.46 | 118.68 | 1687.5 | 2549.6 | 862.1 | 3.8039 | 5.1829 | 1.379 | 1.76536 | 8.4257 | +| 372.0 | 21.554 | 422.26 | 226.84 | 1935.3 | 2275.5 | 340.3 | 4.1785 | 4.7059 | 0.5274 | 2.3682 | 4.4084 | + +## Función plot + +### Diagrama Presión-Volumen (P-V): + +- **Concepto:** Muestra la relación entre la presión y el volumen de un sistema + termodinámico. +- **Eje X:** Volumen. +- **Eje Y:** Presión. +- **Características:** En un diagrama P-V, las curvas representan los procesos + termodinámicos del sistema. Puede mostrar procesos adiabáticos, isotérmicos, + isobáricos, etc. La pendiente de las curvas indica propiedades como la + compresibilidad del fluido. + +![diagrama pv](pvdiagram.png) + +### Diagrama Presión-Temperatura (P-T): + +- **Concepto:** Muestra la relación entre la presión y la temperatura de un + sistema termodinámico. +- **Eje X:** Temperatura. +- **Eje Y:** Presión. +- **Características:** Este diagrama revela cómo cambia la presión con la + temperatura bajo diversas condiciones. Puede mostrar regiones de fases (por + ejemplo, líquido, vapor, o ambos) y proporciona información sobre las + transiciones de fase. + +![diagrama pt](ptdiagram.png) + +### Diagrama Temperatura-Volumen (T-V): + +- **Concepto:** Muestra la relación entre la temperatura y el volumen de un + sistema termodinámico. +- **Eje X:** Volumen. +- **Eje Y:** Temperatura. +- **Características:** Este diagrama ayuda a visualizar cómo la temperatura se + relaciona con el volumen en diferentes procesos. Puede mostrar curvas de + saturación, isóbaras, entre otras. + +![diagrama tv](tvdiagram.png) + +Estos diagramas son herramientas esenciales en termodinámica y se utilizan +comúnmente para representar y analizar los ciclos termodinámicos, como el ciclo +de Carnot en máquinas térmicas. Además, en el caso de sustancias puras, los +diagramas P-V, P-T y T-V son útiles para entender las propiedades y +comportamientos de la sustancia en diferentes estados termodinámicos. Por +ejemplo, el diagrama P-V de una sustancia pura puede revelar información sobre +la expansión y la compresión durante un proceso termodinámico. + +## Función plot3d + +### Diagrama Tridimensional PVT: + +- **Ejes:** + + - **Eje X:** Volumen (V) + - **Eje Y:** Presión (P) + - **Eje Z:** Temperatura (T) + +- **Características:** + + - Este diagrama tridimensional proporciona una representación visual de cómo + la presión, el volumen y la temperatura interactúan en un sistema + termodinámico. + + - Permite observar las superficies de fases y regiones críticas en función de + los cambios en las variables P, V y T. + + - Se utiliza para estudiar propiedades como la expansión y contracción de + sustancias, cambios de fase y transiciones críticas. + + - Puede revelar información sobre la estabilidad de las fases y proporcionar + una comprensión detallada del comportamiento termodinámico de un fluido. + +- **Aplicaciones:** + + - Útil en la industria química y de petróleo para comprender el comportamiento + de sustancias en condiciones de presión y temperatura variables. + + - Permite visualizar las áreas de vapor, líquido y fase crítica en un solo + diagrama. + + - Ayuda en el diseño y optimización de procesos termodinámicos complejos. + +- **Importancia:** + + - Facilita la comprensión de las propiedades físicas de sustancias y su + respuesta a cambios en las condiciones ambientales. + + - Ayuda en la predicción de comportamientos críticos, como la formación de + fases y puntos críticos. + +Este tipo de diagrama es una herramienta valiosa para los ingenieros y +científicos que trabajan en el estudio y diseño de sistemas termodinámicos, +especialmente en áreas como la química, la ingeniería de procesos y la industria +del petróleo y gas. + +![diagrama 3d](3ddiagram.png) + +## Función point + +La función 'point' permite al usuario buscar valores en la tabla de vapor +introduciendo la temperatura deseada, esta regresa el valor correspondiende de +presión, densidad, entalpía, entropía y volumen + +### Datos del punto buscado + +- Temperatura: 100ºC +- Presión: 0.10142 MPa +- Densidad líquido (rhoL): 958.35 kg/m^3 +- Densidad vapor (rhoV): 0.59817 kg/m^3 +- Entalpía líquido (hL): 419.17 KJ/kg +- Entalpía vapor (hV): 2675.6 KJ/kg +- Cambio de entalpía (delta_h): 2256.4 KJ/kg +- Entropía líquido (sL): 1.3072 KJ/(kg·K) +- Entropía vapor (sV): 7.3541 KJ/(kg·K) +- Cambio de entropía (delta_s): 6.0469 KJ/(kg·K) +- Volumen líquido (vL): 1.04346 cm^3/g +- Volumen vapor (vV): 1671.8 cm^3/g + +**Importancia de las Tablas de Vapor en Termodinámica:** + +Las tablas de vapor son herramientas fundamentales en termodinámica para el +estudio y análisis de sustancias en estados gaseosos y líquidos, proporcionando +información crucial sobre las propiedades termodinámicas de las sustancias. A +continuación, se detallan algunas de las razones clave por las cuales las tablas +de vapor son esenciales: + +**Propiedades Termodinámicas Precisas:** + +- Las tablas de vapor contienen datos precisos y experimentales sobre + propiedades termodinámicas, como la presión, temperatura, entalpía, entropía, + volumen específico y otras. + +- Estos datos permiten realizar cálculos precisos en procesos termodinámicos y + determinar el comportamiento de las sustancias en diferentes condiciones. + +**Diseño y Análisis de Ciclos Termodinámicos:** + +- Son esenciales en el diseño y análisis de ciclos termodinámicos, como el ciclo + Rankine en plantas de energía, el ciclo de refrigeración por compresión de + vapor, y otros procesos de conversión de energía. + +- Facilitan la identificación de puntos críticos, condiciones de saturación, y + la comprensión de las fases involucradas en estos ciclos. + +**Predicción de Comportamientos en Procesos Térmicos:** + +- Permiten predecir cómo una sustancia se comportará bajo diversas condiciones + de temperatura y presión, facilitando la planificación y operación eficiente + de sistemas termodinámicos. + +- Ayudan en la identificación de transiciones de fase, como la vaporización y la + condensación, y proporcionan datos sobre las condiciones críticas. + +**Determinación de Propiedades en Ingeniería y Ciencias Aplicadas:** + +- En ingeniería química, mecánica y otras disciplinas, las tablas de vapor son + utilizadas para calcular y analizar procesos, desde la producción de energía + hasta el diseño de sistemas de climatización. + +- Facilitan la determinación de propiedades clave necesarias en el diseño y + operación de equipos y maquinaria. + +**Análisis de Problemas de Transferencia de Calor y Masa:** + +- Son esenciales para el análisis de problemas relacionados con la transferencia + de calor y masa, proporcionando datos para calcular tasas de evaporación, + coeficientes de transferencia de calor, y más. + +- Ayudan en la comprensión de fenómenos como la condensación y evaporación en + intercambiadores de calor. + +**Entendimiento de Comportamientos de Sustancias Puras:** + +- Para sustancias puras, las tablas de vapor son fundamentales para entender + cómo las propiedades termodinámicas varían en diferentes estados, lo que es + esencial para la investigación y el desarrollo de nuevos materiales y + procesos. + +En resumen, las tablas de vapor son una herramienta invaluable en la +termodinámica aplicada, proporcionando datos cruciales que permiten comprender, +diseñar y optimizar una amplia variedad de sistemas y procesos en ingeniería y +ciencia. + +# Conclusiones + +En este artículo, hemos explorado la utilidad y aplicaciones prácticas de las +funciones disponibles en las clases de McCabe-Thiele y SteamTable de fqlearn. La +capacidad de visualizar de manera sencilla cálculos termodinámicos demuestra ser +invaluable, especialmente para docentes que buscan simplificar la enseñanza y +para alumnos que desean comprender de manera efectiva los conceptos +termodinámicos. La combinación de estas herramientas en fqlearn proporciona una +plataforma integral que facilita tanto la instrucción como el aprendizaje en el +fascinante campo de la termodinámica. + +## Repositorio en GitHub + +No olvides que puedes consultar fqlearn en +[Github](https://github.com/osl-pocs/fqlearn) + +## Contácto + +El autor de este artículo se encuentra disponible en +[LinkedIn](https://www.linkedin.com/in/josé-maría-garcía-márquez-556a75199/), y +en [Github](https://github.com/JoseMariaGarciaMarquez). diff --git a/pages/blog/google-summer-of-code-week-4th-and-7th-midterm-evaluation/index.qmd b/pages/blog/google-summer-of-code-week-4th-and-7th-midterm-evaluation/index.qmd new file mode 100644 index 000000000..13b073b8c --- /dev/null +++ b/pages/blog/google-summer-of-code-week-4th-and-7th-midterm-evaluation/index.qmd @@ -0,0 +1,87 @@ +--- +title: "Google Summer of Code- Week 4th & 7th Midterm Evaluation" +slug: google-summer-of-code-week-4th-and-7th-midterm-evaluation +date: 2023-07-25 +authors: ["Ankit Kumar"] +tags: [google summer of code, gsoc, open-source open-sciencelab] +categories: [open-source, gsoc] +description: | + In this article, I will share the progress for Week 4th week to 7th week for my + contribution to Open-science labs as a part of Google Summer of Code 2023. +thumbnail: "/GSoC-Vertical.png" +--- + +## Google Summer of Code- Week 4th & 7th Midterm Evaluation + +In this article, I will share the progress for Week 4th week to 7th week for my +contribution to Open-science labs as a part of Google Summer of Code 2023. + + + +As my Google Summer of Code journey continued, I found myself faced with an +exciting yet daunting task: implementing a whole new build-system as an option +for templates in the esteemed Open-Science Lab. This endeavor demanded +meticulous planning, unwavering dedication, and the exploration of various +build-systems, including Maturin, Hatchling, Scikit-build, and `pybuild11`. + +In this period, I started working on to add support for `Maturin` build-system. + +### Maturin + +[**Maturin**]() was the first build-system I explored. Its unique approach of +building Python bindings for Rust libraries intrigued me, and I wondered if it +could provide a novel solution to the lab's needs. The seamless blending of +Python and Rust offered the potential for unparalleled performance and memory +efficiency in research projects. However, I faced a steep learning curve to +master the intricacies of Rust and its integration with Python. Overcoming these +challenges was a significant achievement, and I managed to create a functional +prototype that demonstrated Maturin's potential to revolutionize the +Open-Science Lab's workflow. My contribution to this issue is +[here](https://github.com/osl-incubator/scicookie/pull/152) + +After merging this pull request, I started to add support for `Hatchling` +build-system. + +### Hatchling + +[**Hatchling**]() known for its user-friendly nature, was my next target. It +promised to simplify the build and deployment processes, which could be +particularly beneficial for newcomers to the lab and projects with +straightforward requirements. Integrating Hatchling into the lab's ecosystem +required thorough documentation and integration tests to ensure its smooth +adoption. Overcoming initial hurdles, I was elated to see the positive response +from the lab's community as they began adopting Hatchling for their projects. My +contribution to this issue is +[here](https://github.com/osl-incubator/scicookie/pull/144) + +After completetion of this issue, I jumped to a task to add support for +`Scikit-Build-Core`. + +### Scikit-build-core + +[**Scikit-build-core**]() a cross-platform build-system, offered a robust option +for integrating CPython extensions. While challenging to implement, I recognized +its potential to support projects with complex native code dependencies. My +experience with Scikit-build exposed me to advanced build and packaging +concepts, and I was thrilled to see it complementing the existing build-systems +in the lab, catering to a broader range of projects. My contribution to this +issue is [here](https://github.com/osl-incubator/scicookie/pull/161) + +### Conclusions + +In conclusion, my Google Summer of Code experience with implementing new +build-systems for the Open-Science Lab was a transformative journey. Overcoming +hurdles with Maturin, embracing user-friendliness with Hatchling, exploring the +potential of Scikit-build.I realized the importance of innovation and +adaptability in the world of open-source development. This experience has not +only enriched my technical skills but also instilled in me a passion for +contributing to projects that drive positive change in the world of scientific +research. As I look to the future, I am excited to continue this journey, +collaborating with the open-source community to create solutions that empower +researchers and advance the boundaries of knowledge. + +You can read my previous blog [here](https://medium.com/@ayeankit) + +If want to connect with me on LinkedIn +[here](https://www.linkedin.com/in/ayeankit/). Github +[here](https://github.com/ayeankit). diff --git a/pages/blog/internship-call-2-2024/index.qmd b/pages/blog/internship-call-2-2024/index.qmd new file mode 100644 index 000000000..cd50974ba --- /dev/null +++ b/pages/blog/internship-call-2-2024/index.qmd @@ -0,0 +1,212 @@ +--- +title: "Call for Interns 2024-02" +slug: internship-call-2-2024 +date: 2024-04-20 +authors: + - Ever Vino +tags: + - community + - internship + - OpenScienceLabs +categories: + - internship + - community + - Technological Collaboration + - Open Source + - Mentors + - Technology Students +description: > + The Open Science Labs (OSL) has announced its Internship and + Learning Program for the second cycle of 2024, in collaboration with The GRAPH + Network. +thumbnail: "/header.jpg" +template: "blog-post.html" +--- + + + + + +This program presents valuable opportunities for both mentors and student/collaborators to engage and grow, despite being an unpaid initiative. + +## Summary of steps to got into internship program + +- Choose a project of your interest. Please read the project ideas document and see if yout meet the prerequisites or the stack required (normally the mentor rejects applicants if they do not meet the stack required) +- Contact to the project mentor through email with the following info. + - Curriculum Vitae + - The project name + - Project idea name + - Motivation for wanting to collaborate on that specific project. +- The mentor will assign you a minor issue to solve. (This step is required to verify if the applicant is able to solve the project tasks.) +- After solving the issue The mentor will ask you to fill a form via google forms. +- The selected applicant will be contacted via email on May 20. + +Candidates should have a basic understanding of Git and how to contribute. For guidance, we recommend reading this [blog post](https://opensciencelabs.org/blog/first-time-contributors/) designed for first-time contributors. Additionally, to stay informed about announcements related to the Internship Program, candidates are encouraged to join the [OSL Discord](https://opensciencelabs.org/discord). + +Please keep in mind about the timeline, and if you have any more questions do not hesitate to contact us at team@opensciencelabs.org. + +## Timeline + +The following is the timeline for the OSL Internship Program Cycle 2024-02: + +- **April 22**: Call for Interns opens. +- **May 13**: Deadline for Interns applications. +- **May 20**: Announcement of approved Interns and start of the bonding period +- **May 27**: Official Start Date of Internship Period; an alternative for projects not +selected by GSoC to run under the OSL Internship Program with The Graph Network support. +- **July 8**: Mid-term Evaluation. +- **August 26**: Final Evaluation. +- **September 3**: Official End Date; Certification process begins. + +## Info about participating Projects +--- + +Below is the list of projects participating in the current internship cycle. Each entry provides key details to assist candidates in understanding the scope and requirements of the projects. + +### ArtBox + +- **Description:** ArtBox is a tool set for handling multimedia files with a bunch of useful functions. +- **Category:** Multimedia Processing. +- **Organization/Project Webpage URL:** [https://osl-incubator.github.io/artbox/](https://osl-incubator.github.io/artbox/) +- **Contact:** Ivan Ogasawara [ivan.ogasawara@gmail.com](mailto:ivan.ogasawara@gmail.com) +- **Project Ideas URL:** https://github.com/osl-incubator/artbox/wiki/OSL-Internship-%E2%80%90-2024-%E2%80%90-2nd-Cycle + + +### ArxLang/ASTx + +- **Description:** ASTx is an agnostic expression structure for AST. It is agnostic because it is not specific to any language, neither to the ArxLang project, although its main focus is to provide all needed feature for ArxLang. +- **Categories:** AST, Compiler +- **Organization/Project Webpage URL:** [https://astx.arxlang.org/](https://astx.arxlang.org/) +- **Contact:** Ivan Ogasawara [ivan.ogasawara@gmail.com](mailto:ivan.ogasawara@gmail.com) +- **Project Ideas URL:** + +### Envers + +- **Description:** Envers is a command-line tool (CLI) designed to manage and version environment variables for different deployment stages such as staging, development, and production. It provides a secure and organized way to handle environment-specific configurations. +- **Categories:** DevOps, Environment Management +- **Organization/Project Webpage URL:** [https://osl-incubator.github.io/envers/](https://osl-incubator.github.io/envers/) +- **Contact:** Ivan Ogasawara [ivan.ogasawara@gmail.com](mailto:ivan.ogasawara@gmail.com) +- **Project Ideas URL:** + +### fqlearn + +- **Description:** This Project aims to facilitate the teaching of unit operations and thermodynamics. +- **Categories:** Mathematical Modeling, Educational +- **Organization/Project Webpage URL:** [https://osl-pocs.github.io/fqlearn/](https://osl-pocs.github.io/fqlearn/) +- **Contact:** John Ever Vino Duran [evervino00@gmail.com](mailto:evervino00@gmail.com) +- **Project Idea 1 URL**: +- **Project Idea 2 URL**: + +### pymedx + +- **Description**: This Project aims to fetch scientific papers metadata +- **Categories:** scrapping, educational, python, api +- **Organization/Project Webpage URL**: +- **Contact**: John Ever Vino Duran (evervino00@gmail.com) +- **Project Ideas 1 URL**: +- **Project Ideas 2 URL**: +- **Project Ideas 3 URL**: + +### Makim + +- **Description:** Makim (or makim) is based on make and focus on improve the way to define targets and dependencies. Instead of using the Makefile format, it uses yaml format. +- **Categories:** DevOps, Automation +- **Organization/Project Webpage URL:** [https://osl-incubator.github.io/makim/](https://osl-incubator.github.io/makim/) +- **Contact:** Ivan Ogasawara [ivan.ogasawara@gmail.com](mailto:ivan.ogasawara@gmail.com) +- **Project Ideas URL:** + + +### noWorkflow + +- **Description:** The noWorkflow project aims at allowing scientists to benefit from provenance data analysis even when they don't use a workflow system. It transparently collects provenance from Python scripts and notebooks and provide tools to support the analysis and management of the provenance. +- **Categories:** Provenance, Software Engineering +- **Organization/Project Webpage URL:** [https://gems-uff.github.io/noworkflow/](https://gems-uff.github.io/noworkflow/) +- **Contact:** João Felipe Nicolaci Pimentel [joaofelipenp@gmail.com](mailto:joaofelipenp@gmail.com) +- **Project Ideas URL (only project idea 2 available):** [https://gist.github.com/JoaoFelipe/ce4cb232deb2c71d4f39afc5cbeefe2b](https://gist.github.com/JoaoFelipe/ce4cb232deb2c71d4f39afc5cbeefe2b) + +### SciCookie + +- **Description:** SciCookie is a template developed by [OpenScienceLabs](https://opensciencelabs.org/) that creates projects from project templates. +- **Category:** Project Templates, Scientific Software +- **Organization/Project Webpage URL:** [https://osl-incubator.github.io/scicookie](https://osl-incubator.github.io/scicookie) +- **Contact:** Ivan Ogasawara [ivan.ogasawara@gmail.com](mailto:ivan.ogasawara@gmail.com) +- **Project Ideas URL:** + +### Sugar + +- **Description:** Sugar aims to organize your stack of containers, gathering some useful scripts and keeping this information centralized in a configuration file. So the command line would be very simple. +- **Categories:** DevOps, Container Management +- **Organization/Project Webpage URL:** [https://osl-incubator.github.io/sugar/e](https://osl-incubator.github.io/sugar/) +- **Contact:** Ivan Ogasawara [ivan.ogasawara@gmail.com](mailto:ivan.ogasawara@gmail.com) +- **Project Ideas URL:** + +### ES-Journals + +- **Description**: An ElasticSearch instance for serving scientific journals metadata. Currently, it has support for biorXiv and medrXiv. +- **Categories:** Servers, api +- **Organization/Project Webpage URL**: +- **Contact**: Ivan Ogasawara (ivan.ogasawara@gmail.com) +- **Project Ideas URL**: + +### Growth-Forge + +- **Description**: GrowthForge is a simplified feedback exchange platform designed to facilitate periodic feedback between individuals within specific projects. It aims to streamline communication and insights sharing, enhancing project collaboration and personal development. +- **Categories:** web development, backend, frontend, python +- **Organization/Project Webpage URL**: +- **Contact**: Ivan Ogasawara (ivan.ogasawara@gmail.com) +- **Project Ideas URL**: + + +### Useful Materials and Courses +--- + +- **First Time Contributors:** In this [blog post](https://opensciencelabs.org/blog/first-time-contributors/) you will find useful information about how to do your first contribution in projects. +- **Software Carpentry Lessons:** Offering tutorials on Git, Bash, Python, R, and more, these lessons are invaluable for building a strong foundation in software development. Access the lessons at Software Carpentry. +- **Udacity CS212 - Design of Computer Programs:** This course, taught by Peter Norvig, delves into advanced programming topics and is an excellent way to deepen your understanding of computer programs. Enroll in the course at Udacity CS212. +- **The GRAPH Network Courses:** Explore a range of courses offered by The GRAPH Network, tailored to various aspects of data analysis. Find the courses at The GRAPH Network Courses. +These resources provide a great opportunity to prepare effectively for the Internship Program and to develop a broad skill set in software development and data analysis. diff --git a/pages/blog/language-server-protocol-lsp-how-editors-speak-code/index.qmd b/pages/blog/language-server-protocol-lsp-how-editors-speak-code/index.qmd new file mode 100644 index 000000000..d5771b0fe --- /dev/null +++ b/pages/blog/language-server-protocol-lsp-how-editors-speak-code/index.qmd @@ -0,0 +1,107 @@ +--- +title: "Language Server Protocol (LSP): How Editors Speak Code" +slug: "language-server-protocol-lsp-how-editors-speak-code" +date: 2025-08-15 +authors: ["Ansh Arora"] +tags: ["Makim", "Automation", "Developer Experience"] +categories: ["Devops", "Dev Tools"] +description: | + The Language Server Protocol (LSP) powers modern code editors like VS Code by + enabling real-time autocompletion, hover info, diagnostics, and more. +thumbnail: "/header.png" +template: "blog-post.html" +--- + +# Language Server Protocol (LSP): How Editors Speak Code + +When you open a code file in VS Code and get real-time suggestions, hover +tooltips, or error squiggles, have you ever wondered **how** your editor +understands the language you’re writing in? + +This magic isn’t hardcoded per-language into the editor. Instead, it’s often +powered by something called the **Language Server Protocol (LSP)**. + +Let’s dive into what LSP is, why it exists, and how it powers modern development +environments. + +## What is the Language Server Protocol? + +The **Language Server Protocol (LSP)** is a standardized way for development +tools (like VS Code, Vim, or Emacs) to communicate with language-specific +services (called language servers). + +Instead of writing new editor plugins for every language and every editor, **LSP +decouples the logic**: + +- The **editor (client)** handles the UI and editor behavior. +- The **language server** handles parsing, validation, completions, and other + language-specific logic. + +They talk to each other via a common JSON-RPC protocol over standard +input/output, TCP, or WebSockets. + +## Why was LSP created? + +Before LSP, supporting multiple languages across editors was a mess: + +- Each editor needed custom plugins. +- Each language had to build and maintain these plugins. + +This was inefficient and hard to maintain. + +**Microsoft introduced LSP in 2016**, alongside VS Code, to fix this +fragmentation. Now, language authors can focus on building a single LSP server, +and editors can plug into it easily. + +![LSP Languages and Editors](https://code.visualstudio.com/assets/api/language-extensions/language-server-extension-guide/lsp-languages-editors.png) + +## Core Features of LSP + +Here are some features LSP enables out-of-the-box: + +- Autocompletion +- Go to Definition +- Hover Information +- Diagnostics (errors/warnings) +- Formatting +- Find References +- Rename Symbol +- Signature Help + +These features work **consistently across any editor** that supports! + +## How Does It Work? + +Here's a simplified lifecycle of how an editor (client) talks to a language +server: + +1. **Editor launches the language server.** +2. **Sends an `initialize` request** to begin communication. +3. As you edit: + + - Sends `textDocument/didOpen`, `didChange`, or `didSave`. + - Receives back diagnostics or suggestions. + +4. On hover, completion, or definition jumps: + + - Sends `textDocument/hover`, `completion`, or `definition` requests. + - Displays server responses in the UI. + +All of this happens over a well-defined set of JSON-RPC messages. + +### Anatomy of a Language Server + +A language server is just a **program** that: + +- Parses the user’s code (possibly building an AST or symbol table). +- Responds to LSP method calls. +- Tracks open files and their versions. + +## Final Thoughts + +The Language Server Protocol has quietly become the **backbone of modern +developer tooling**. Whether you’re building an IDE, a DSL, or a configuration +tool, LSP lets you ship a polished editing experience with far less effort. + +If you're working on your own language, plugin, or platform, building an LSP +server is one of the smartest investments you can make. diff --git a/pages/blog/makim-efficient-workflows-with-makims-working-directory/index.ipynb b/pages/blog/makim-efficient-workflows-with-makims-working-directory/index.ipynb deleted file mode 100644 index dc6caf35d..000000000 --- a/pages/blog/makim-efficient-workflows-with-makims-working-directory/index.ipynb +++ /dev/null @@ -1,324 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "id": "d4447f2e", - "metadata": {}, - "source": [ - "---\n", - "title: \"Efficent Workflows with Makim's Working Directory\"\n", - "slug: \"makim-efficient-workflows-with-makims-working-directory\"\n", - "date: 2023-12-10\n", - "authors: [\"Abhijeet Saroha\"]\n", - "tags: [\"makim\", \"automation\", \"working-directory\", \"devops\", \"open-source\"]\n", - "categories: [\"devops\", \"automation\", \"python\"]\n", - "description: |\n", - " In this blog post, we'll explore the Working Directory feature, understand \n", - " its syntax, and witness its power in action through real-world examples. Whether\n", - " you're a Makim veteran or a newcomer, this feature is designed to make your\n", - " command-line experience more flexible and tailored to your project's needs.\n", - " Let's dive into the details and unleash the potential of Makim's Working Directory\n", - " feature!\n", - "thumbnail: \"/header.jpg\"\n", - "template: \"blog-post.html\"\n", - "---" - ] - }, - { - "cell_type": "markdown", - "id": "39d4fdb7-8441-4060-8048-3fc389f37cbd", - "metadata": {}, - "source": [ - "# **Efficent Workflows with Makim's Working Directory**" - ] - }, - { - "cell_type": "markdown", - "id": "eca10ec0-50fe-4ef2-a80f-75ab15beb60c", - "metadata": {}, - "source": [ - "Welcome to the world of [Makim](https://github.com/osl-incubator/makim), your go-to command-line companion for project automation. In our ongoing effort to enhance Makim's capabilities, we've introduced an exciting new feature—the Working Directory. This feature brings a new level of control and flexibility to your project workflows.\n", - "\n", - "In this blog post, we'll explore the Working Directory feature, understand its syntax, and witness its power in action through real-world examples. Whether you're a Makim veteran or a newcomer, this feature is designed to make your command-line experience more flexible and tailored to your project's needs. Let's dive into the details and unleash the potential of Makim's Working Directory feature!" - ] - }, - { - "cell_type": "markdown", - "id": "a445f2d9-e743-4b24-9b47-2f00455898de", - "metadata": {}, - "source": [ - "## Unveiling the attribute: working-directory" - ] - }, - { - "cell_type": "markdown", - "id": "1f75b9ef-ce06-4003-ac8f-308b853a27f5", - "metadata": {}, - "source": [ - "In the bustling realm of project management, one of the key challenges is orchestrating a seamless workflow while ensuring command executions are precisely where they need to be. This is where Makim's **Working Directory** steps into the spotlight, offering a robust solution for organizing, customizing, and optimizing your project commands." - ] - }, - { - "cell_type": "markdown", - "id": "1a214ca5-810a-4ef9-84d6-93b3c37e1882", - "metadata": {}, - "source": [ - "## Syntax and Scopes" - ] - }, - { - "cell_type": "markdown", - "id": "56c3a561-48b0-4047-b155-db00bf11eb07", - "metadata": {}, - "source": [ - "The Working Directory feature in Makim operates across three distinct scopes: Global, Group, and Target.\n", - "1. **Global Scope**\n", - " \n", - " At the global scope, setting the working directory impacts all targets and groups within the Makim configuration. It provides a top-level directive to establish a standardized execution environment.\n", - "```yaml\n", - "version: 1.0\n", - "working-directory: /path/to/global/directory\n", - "\n", - "# ... other configuration ...\n", - "```\n", - "\n", - "2. **Group Scope**\n", - " \n", - " Moving a level deeper, the group scope allows you to tailor the working directory for all targets within a specific group.\n", - "```yaml\n", - "version: 1.0\n", - "\n", - "groups:\n", - " my-group:\n", - " working-directory: /path/to/group/directory\n", - " targets:\n", - " target-1:\n", - " run: |\n", - " # This target operates within the /path/to/group/directory\n", - "```\n", - "3. **Target Scope**\n", - " \n", - " For fine-grained control over individual targets, the working directory can be specified at the target scope.\n", - "```yaml\n", - "version: 1.0\n", - "\n", - "groups:\n", - " my-group:\n", - " targets:\n", - " my-target:\n", - " working-directory: /path/to/target/directory\n", - " run: |\n", - " # This target operates within the /path/to/target/directory\n", - "```\n", - "\n", - "The flexibility provided by these scopes ensures that your commands are executed in precisely the right context, maintaining a clean and organized project structure." - ] - }, - { - "cell_type": "markdown", - "id": "05c11ec2-7749-4db0-832b-0738d63c22c0", - "metadata": {}, - "source": [ - "## Why is it helpful?" - ] - }, - { - "cell_type": "markdown", - "id": "ae7bdee0-f8e5-4844-9dbd-1dafb0622b4d", - "metadata": {}, - "source": [ - "Embracing the Working Directory feature in Makim brings forth a multitude of advantages, enhancing the overall project management experience. Let's delve into why this feature is a game-changer for your Makim configurations:\n", - "1. **Isolation of Commands:**\n", - " Users can isolate commands within specific directories, avoiding potential conflicts and ensuring that commands run in the expected environment.\n", - "\n", - "2. **Flexibility in Configuration:**\n", - " Different targets or groups may require different execution environments. The working-directory attribute provides the flexibility to customize these environments, tailoring them to the unique needs of each segment of your project.\n", - "\n", - "3. **Ease of Use:**\n", - " Users can easily understand and manage the execution context of commands within the Makim configuration. This makes the configuration more readable and maintainable, especially when dealing with complex project structures.\n", - "\n", - "4. **Support for Absolute and Relative Paths:**\n", - " The feature supports both absolute and relative paths, allowing users to specify directories based on their requirements. This flexibility ensures compatibility with diverse project structures and simplifies the configuration process." - ] - }, - { - "cell_type": "markdown", - "id": "35312475-67f7-4108-a328-7235b032cc8c", - "metadata": {}, - "source": [ - "## Real-Life Example" - ] - }, - { - "cell_type": "markdown", - "id": "e5dbbc1a-59d1-4028-9575-d21f2e2e12ef", - "metadata": {}, - "source": [ - "Consider a scenario where a development team is working on a project that involves multiple programming languages and technologies. The project structure looks like this:" - ] - }, - { - "cell_type": "markdown", - "id": "edeab9cc-5fa2-44ac-b1fb-5570c5f5753c", - "metadata": {}, - "source": [ - "```\n", - "multi_language_project/\n", - "│\n", - "├── backend/\n", - "│ ├── python/\n", - "│ │ └── src/\n", - "│ └── java/\n", - "│ └── src/\n", - "│\n", - "└── frontend/\n", - " ├── react/\n", - " │ └── src/\n", - " └── vue/\n", - " └── src/\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "5dcf2f8e-d41c-4684-b305-16c382af2216", - "metadata": {}, - "source": [ - "The project consists of a backend with components implemented in both Python and Java, and a frontend with components using React and Vue.js. To efficiently manage and run tasks for each language or framework, the Working Directory feature proves invaluable.\n", - "\n", - "Let's create a Makim configuration file *(.makim.yaml)* that showcases the flexibility of the Working Directory feature in managing tasks for different languages." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "726bcf5a-7cc5-4448-af62-0564e2cbe1a1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting .makim.yaml\n" - ] - } - ], - "source": [ - "%%writefile .makim.yaml\n", - "version: 1.0\n", - "working-directory: \"/tmp/multi_language_project\"\n", - "groups:\n", - " backend_python:\n", - " working-directory: \"backend/python\"\n", - " targets:\n", - " test:\n", - " run: |\n", - " echo \"Running Python backend tests...\"\n", - " # Add commands to run Python backend tests\n", - " lint:\n", - " run: |\n", - " echo \"Linting Python code...\"\n", - " # Add commands for linting Python code\n", - "\n", - " backend_java:\n", - " working-directory: \"backend/java\"\n", - " targets:\n", - " test:\n", - " working-directory: \"src\"\n", - " run: |\n", - " echo \"Running Java backend tests...\"\n", - " # Add commands to run Java backend tests\n", - " build:\n", - " run: |\n", - " echo \"Building Java artifacts...\"\n", - " # Add commands for building Java artifacts\n", - "\n", - " frontend_react:\n", - " working-directory: \"frontend/react\"\n", - " targets:\n", - " test:\n", - " run: |\n", - " echo \"Running React frontend tests...\"\n", - " # Add commands to run React frontend tests\n", - " build:\n", - " run: |\n", - " echo \"Building React frontend...\"\n", - " # Add commands for building React frontend\n", - "\n", - " frontend_vue:\n", - " working-directory: \"frontend/vue\"\n", - " targets:\n", - " test:\n", - " run: |\n", - " echo \"Running Vue.js frontend tests...\"\n", - " # Add commands to run Vue.js frontend tests\n", - " build:\n", - " working-directory: \"src\"\n", - " run: |\n", - " echo \"Building Vue.js frontend...\"\n", - " # Add commands for building Vue.js frontend" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "af7432d1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Linting Python code...\n", - "Running Java backend tests...\n", - "Running React frontend tests...\n", - "Building Vue.js frontend...\n" - ] - } - ], - "source": [ - "!makim --makim-file ./.makim.yaml backend_python.lint\n", - "!makim --makim-file ./.makim.yaml backend_java.test\n", - "!makim --makim-file ./.makim.yaml frontend_react.test\n", - "!makim --makim-file ./.makim.yaml frontend_vue.build" - ] - }, - { - "cell_type": "markdown", - "id": "184db134", - "metadata": {}, - "source": [ - "## Conclusion\n", - "\n", - "In conclusion, Makim's working-directory feature empowers users with a flexible and efficient approach to project management. Throughout this blog, we explored how this feature, applied at the global, group, and target scopes, provides unparalleled customization and control over the execution environment.\n", - "\n", - "By isolating commands, offering flexibility in configuration, and ensuring ease of use, Makim's working-directory feature becomes an invaluable asset in your toolkit. It not only streamlines the execution of commands but also enhances the overall organization and maintainability of your projects.\n", - "\n", - "Harness the power of working directories in Makim to elevate your project management game. As you integrate this tool into your workflow, you'll discover a newfound simplicity and clarity in your command execution. Enjoy the benefits of an organized and optimized project environment, courtesy of Makim's innovative features.\n", - "\n", - "Start optimizing your projects with Makim today!" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pages/blog/makim-efficient-workflows-with-makims-working-directory/index.qmd b/pages/blog/makim-efficient-workflows-with-makims-working-directory/index.qmd new file mode 100644 index 000000000..71a7bdd47 --- /dev/null +++ b/pages/blog/makim-efficient-workflows-with-makims-working-directory/index.qmd @@ -0,0 +1,193 @@ +--- +title: "Efficent Workflows with Makim's Working Directory" +slug: "makim-efficient-workflows-with-makims-working-directory" +date: 2023-12-10 +authors: ["Abhijeet Saroha"] +tags: ["makim", "automation", "working-directory", "devops", "open-source"] +categories: ["devops", "automation", "python"] +description: | + In this blog post, we'll explore the Working Directory feature, understand + its syntax, and witness its power in action through real-world examples. Whether + you're a Makim veteran or a newcomer, this feature is designed to make your + command-line experience more flexible and tailored to your project's needs. + Let's dive into the details and unleash the potential of Makim's Working Directory + feature! +thumbnail: "/header.jpg" +template: "blog-post.html" +--- +# **Efficent Workflows with Makim's Working Directory** + +Welcome to the world of [Makim](https://github.com/osl-incubator/makim), your go-to command-line companion for project automation. In our ongoing effort to enhance Makim's capabilities, we've introduced an exciting new feature—the Working Directory. This feature brings a new level of control and flexibility to your project workflows. + +In this blog post, we'll explore the Working Directory feature, understand its syntax, and witness its power in action through real-world examples. Whether you're a Makim veteran or a newcomer, this feature is designed to make your command-line experience more flexible and tailored to your project's needs. Let's dive into the details and unleash the potential of Makim's Working Directory feature! + +## Unveiling the attribute: working-directory + +In the bustling realm of project management, one of the key challenges is orchestrating a seamless workflow while ensuring command executions are precisely where they need to be. This is where Makim's **Working Directory** steps into the spotlight, offering a robust solution for organizing, customizing, and optimizing your project commands. + +## Syntax and Scopes + +The Working Directory feature in Makim operates across three distinct scopes: Global, Group, and Target. +1. **Global Scope** + + At the global scope, setting the working directory impacts all targets and groups within the Makim configuration. It provides a top-level directive to establish a standardized execution environment. +```yaml +version: 1.0 +working-directory: /path/to/global/directory + +# ... other configuration ... +``` + +2. **Group Scope** + + Moving a level deeper, the group scope allows you to tailor the working directory for all targets within a specific group. +```yaml +version: 1.0 + +groups: + my-group: + working-directory: /path/to/group/directory + targets: + target-1: + run: | + # This target operates within the /path/to/group/directory +``` +3. **Target Scope** + + For fine-grained control over individual targets, the working directory can be specified at the target scope. +```yaml +version: 1.0 + +groups: + my-group: + targets: + my-target: + working-directory: /path/to/target/directory + run: | + # This target operates within the /path/to/target/directory +``` + +The flexibility provided by these scopes ensures that your commands are executed in precisely the right context, maintaining a clean and organized project structure. + +## Why is it helpful? + +Embracing the Working Directory feature in Makim brings forth a multitude of advantages, enhancing the overall project management experience. Let's delve into why this feature is a game-changer for your Makim configurations: +1. **Isolation of Commands:** + Users can isolate commands within specific directories, avoiding potential conflicts and ensuring that commands run in the expected environment. + +2. **Flexibility in Configuration:** + Different targets or groups may require different execution environments. The working-directory attribute provides the flexibility to customize these environments, tailoring them to the unique needs of each segment of your project. + +3. **Ease of Use:** + Users can easily understand and manage the execution context of commands within the Makim configuration. This makes the configuration more readable and maintainable, especially when dealing with complex project structures. + +4. **Support for Absolute and Relative Paths:** + The feature supports both absolute and relative paths, allowing users to specify directories based on their requirements. This flexibility ensures compatibility with diverse project structures and simplifies the configuration process. + +## Real-Life Example + +Consider a scenario where a development team is working on a project that involves multiple programming languages and technologies. The project structure looks like this: + +``` +multi_language_project/ +│ +├── backend/ +│ ├── python/ +│ │ └── src/ +│ └── java/ +│ └── src/ +│ +└── frontend/ + ├── react/ + │ └── src/ + └── vue/ + └── src/ +``` + +The project consists of a backend with components implemented in both Python and Java, and a frontend with components using React and Vue.js. To efficiently manage and run tasks for each language or framework, the Working Directory feature proves invaluable. + +Let's create a Makim configuration file *(.makim.yaml)* that showcases the flexibility of the Working Directory feature in managing tasks for different languages. + + +```python +%%writefile .makim.yaml +version: 1.0 +working-directory: "/tmp/multi_language_project" +groups: + backend_python: + working-directory: "backend/python" + targets: + test: + run: | + echo "Running Python backend tests..." + # Add commands to run Python backend tests + lint: + run: | + echo "Linting Python code..." + # Add commands for linting Python code + + backend_java: + working-directory: "backend/java" + targets: + test: + working-directory: "src" + run: | + echo "Running Java backend tests..." + # Add commands to run Java backend tests + build: + run: | + echo "Building Java artifacts..." + # Add commands for building Java artifacts + + frontend_react: + working-directory: "frontend/react" + targets: + test: + run: | + echo "Running React frontend tests..." + # Add commands to run React frontend tests + build: + run: | + echo "Building React frontend..." + # Add commands for building React frontend + + frontend_vue: + working-directory: "frontend/vue" + targets: + test: + run: | + echo "Running Vue.js frontend tests..." + # Add commands to run Vue.js frontend tests + build: + working-directory: "src" + run: | + echo "Building Vue.js frontend..." + # Add commands for building Vue.js frontend +``` + + Overwriting .makim.yaml + + + +```python +!makim --makim-file ./.makim.yaml backend_python.lint +!makim --makim-file ./.makim.yaml backend_java.test +!makim --makim-file ./.makim.yaml frontend_react.test +!makim --makim-file ./.makim.yaml frontend_vue.build +``` + + Linting Python code... + Running Java backend tests... + Running React frontend tests... + Building Vue.js frontend... + + +## Conclusion + +In conclusion, Makim's working-directory feature empowers users with a flexible and efficient approach to project management. Throughout this blog, we explored how this feature, applied at the global, group, and target scopes, provides unparalleled customization and control over the execution environment. + +By isolating commands, offering flexibility in configuration, and ensuring ease of use, Makim's working-directory feature becomes an invaluable asset in your toolkit. It not only streamlines the execution of commands but also enhances the overall organization and maintainability of your projects. + +Harness the power of working directories in Makim to elevate your project management game. As you integrate this tool into your workflow, you'll discover a newfound simplicity and clarity in your command execution. Enjoy the benefits of an organized and optimized project environment, courtesy of Makim's innovative features. + +Start optimizing your projects with Makim today! diff --git a/pages/blog/my-google-summer-of-code-journey/index.qmd b/pages/blog/my-google-summer-of-code-journey/index.qmd new file mode 100644 index 000000000..b2c146054 --- /dev/null +++ b/pages/blog/my-google-summer-of-code-journey/index.qmd @@ -0,0 +1,238 @@ +--- +title: "My Google Summer of Code Journey" +slug: "my-google-summer-of-code-journey" +date: 2024-08-10 +authors: ["Kishan Ved"] +tags: ["Google Summer of Code", "GSoC", "Summer internship"] +categories: ["Google Summer of Code", "GSoC"] +description: "This blog post summarizes my open source experience, right from my very first open source contribution to getting selected for GSoC and completing my Google Summer of Code project!" +thumbnail: "/header.png" +template: "blog-post.html" +--- + +# About Me +I am [Kishan Ved](https://github.com/Kishan-Ved), an undergraduate student at the Indian Institute of Technology Gandhinagar (IIT Gandhinagar), India, in the department of Computer Science and Engineering. I spent the summer after my second year doing my Google Summer of Code 2024 project with the NumFOCUS organization (which serves as an umbrella organization for open source projects), which had Open Science Labs as a sub organization. The project I worked on is: [PyDataStructs](https://github.com/codezonediitj/pydatastructs). + +# About PyDataStructs + +[PyDataStructs](https://github.com/codezonediitj/pydatastructs) aims to be a Python package for various data structures in computer science. It also contains several algorithms including their parallel implementations. Everything is just a few calls away, and users don't need to write any code from scratch. The APIs provided are consistent with each other, clean and easy to use. All implementations are well tested, 99% lines of code have already been tested by developers. PyDataStructs is the first well-designed library/package which covers most of the data structures and algorithms, with different backends like Python and C++. + +## My first contribution to open source +I began contributing to open source in December 2023, when I connected to a senior student at my university who had also done GSoC in 2023, with PyDataStructs. I was interested in the project that she did and hence I started exploring the codebase. + +### Finding a good first issue +I went to the issues tab on the repository's github page and searched for good first issues. I noticed an issue that seemed doable: [Implement Introsort](https://github.com/codezonediitj/pydatastructs/issues/545). This involved implementing the sorting algorithm: Introsort in the Python backend. I had learned this as a part of my academic cousework, and hence, I was able to code this. + +### My first Pull Request +I started reading the README file and set up the repository locally and created the conda environment. Then I got to work. In a day, I was able to successfully create a PR (pull request) and open it on GitHub: [Implemented Introsort](https://github.com/codezonediitj/pydatastructs/pull/549). This PR was reviewed and changes were requested. Once I had done all of them and all the CI checks passed, it was merged. And that's my first open souce contribution! + +## Pre-GSoC Phase + +Next, I connected to the maintainer, [Gagandeep Singh](https://github.com/czgdp1807), and expressed my interest for contributing. He suggested that I contribute to more organizations, like: [LFortran](https://github.com/lfortran/lfortran) and [LPython](https://github.com/lcompilers/lpython) to maximize my chances for GSoC. These organizations also had Gagandeep Sir as the core maintainer and developer. + +### The amazing open source community +I found the open source community amazing, everyone was ready to help me with every doubt I had, patiently guide me with differnt git commands to resolve conflicts and review my PRs and give valueable advice. I made new friends, who were almost of the same age as me, who had been contributing since quite a while. They made things easier for me and helped me contribute. + +### More open source contributions +I spent the next 3 months contributing to these organizations, and I had 10 merged PRs in the LFortran repository and 5 merged PRs in the LPython repository. Here's a link to these: + +- [LFortran contributions](https://github.com/lfortran/lfortran/pulls?q=is%3Apr+author%3AKishan-Ved+is%3Amerged) + +- [LPython contributions](https://github.com/lcompilers/lpython/pulls?q=is%3Apr+author%3AKishan-Ved+is%3Amerged) + +These made me well versed with various git commands, the skill of handling a large and dynamic codebase and communicating effectively with the community to incorporate suggestions and solve issues. + +### Drafting my GSoC proposal +Next, it was time to submit proposals for GSoC projects. By then, I had been contributing to open source for nearly 3 months. I requested a meeting with Gagandeep Sir to help me decide my proposal. He kindly agreed and we set up an online meeting, where we discussed potential GSoC ideas and the timeline that was best suited for me. + +I submitted 2 proposals, to LPython (Python Software Foundation) and PyDataStructs (NumFOCUS): + +- [NumFOCUS - Open Science Labs: PyDataStructs: Add C++ Backend for all trees](https://drive.google.com/file/d/1khpreH4pVJSj26VGJOf2v_XDCrOlo8qP/view?usp=sharing) + +- [Python Software Foundation - LPython: Implement modules from the Python Standard Library](https://drive.google.com/file/d/1to69uW9rb9vAE6pJCooamQo-Q1f_BYBt/view?usp=sharing) + +The proposals should have personal information, details about previous open source contributions made to the organization's repository, and detailed information about the project that you wish to take up, with a structured plan about your goals. In addition to this, it must contain an expected timeline that you will follow. It's a good practice to set a large number of small goals and plan tasks before starting the project. + +### Continuing open source contributions +It's a good practice to remain active in the organizations by contributing even after submitting GSoC proposals, this makes the developers believe that you are genuinely interested in the project and enthusiastic to contribute. This amplifies chances for getting selected for Google Summer of Code! + +## GSoC Phase +I got my acceptance letter at 11:32 pm IST on May 1, 2024. It was 2 minutes past the result declaration time and I was at the edge of my seat. I was overjoyed to get the mail saying I was accepted as a Google Summer of Code contributor for NumFOCUS! Here's my project: **[Open Science Labs: PyDataStructs: Add a C++ Backend for tree data structures and their algorithms](https://summerofcode.withgoogle.com/programs/2024/projects/2nrxEFTg)** + +### My GSoC project goals + +My project involved adding a C++ backend for all tree data structures in **[PyDataStructs](https://github.com/codezonediitj/pydatastructs)**, a Python package for advanced data structures and algorithms. The user has an option to select either the Python backend or the C++ backend. + + +```python +tree = RedBlackTree(backend=Backend.CPP) +``` + + +For any data structure, the Python backend is developed first, and once completely tested and ready, its C++ backend is developed. Both the backends share full functionality and are completely compatible. The C++ backend is extremely fast, it executes codes 8-10 times faster. This enhances the computation speed, making it extremely valuable for scientific computing and high-performance applications. + +### The community bonding period +I'll be honest, it was difficult at the start. I found the project a little more difficult than I had anticipated, and realized that it demanded a little more that my current skillset. My mentor, Gagandeep Sir, adviced me to read the documentation and first learn about Python-C API, which was something totally new for me. I spent the first week doing this, and I could barely write any code. However, I soon realized the importance of reading the documentation and how smoothly things went following that. Within a week, I became well versed with all the new technology and started coding up my first PR. + +### Coding phase + +I started working from the community bonding period itself, and this gave me a good headstart and allowed me to complete the project in 12 weeks. Here's an outline of all the work I did: + +#### Pre GSoC Work + +| PR Description | +|------------------| +| **[Added Introsort algorithm](https://github.com/codezonediitj/pydatastructs/pull/549)** | +| **[Fixed version related bugs](https://github.com/codezonediitj/pydatastructs/pull/553)** | + +#### Community Bonding Period + +| PR Description | +|------------------| +| **[C++ backend for Node, TreeNode, ArrayForTrees, BinaryTree and BinarySearchTree and all tree traversals implemented](https://github.com/codezonediitj/pydatastructs/pull/556)** | + +#### Coding Phase 1 + +| PR Description | +|------------------| +| **[C++ backend for Self Balancing Binary Tree](https://github.com/codezonediitj/pydatastructs/pull/559)** | +| **[C++ backend for Red Black Trees](https://github.com/codezonediitj/pydatastructs/pull/560)** | +| **[C++ backend for Binary Indexed Trees](https://github.com/codezonediitj/pydatastructs/pull/561)** | +| **[C++ backend for Splay Trees](https://github.com/codezonediitj/pydatastructs/pull/562)** | + +#### Coding Phase 2 + +| PR Description | +|------------------| +| **[C++ backend for AVL Trees](https://github.com/codezonediitj/pydatastructs/pull/564)** | +| **[C++ backend for Cartesian Trees](https://github.com/codezonediitj/pydatastructs/pull/567)** | +| **[C++ backend for Treap](https://github.com/codezonediitj/pydatastructs/pull/568)** | +| **[C++ backend for all trees in `binary_trees.py` file complete](https://github.com/codezonediitj/pydatastructs/pull/569)** | +| **[Updated Documentation](https://github.com/codezonediitj/pydatastructs/pull/570)** | + +--- + + +### Contribution Stats: + +Lines added: **+4,721** (#2 contributor in terms of lines added) + +Commits made: 12 + +Total merged Pull Requests : 12 + +Here's a complete list of all my **[merged PRs](https://github.com/codezonediitj/pydatastructs/pulls?q=is%3Apr+is%3Amerged+author%3AKishan-Ved)** + +### Speed results + +
+ Click to see a benchmark code + + To run this code, you need PyDataStructs installed with the following imports: + + ```python + import timeit, functools, os, pytest + from pydatastructs.trees.binary_trees import (BinarySearchTree, RedBlackTree) + from pydatastructs.utils.misc_util import Backend + ``` + + ```python + def test_BinarySearchTree(**kwargs): + cpp = Backend.CPP + repeat = 1 + number = 1 + + size = int(os.environ.get("PYDATASTRUCTS_BENCHMARK_SIZE", "1000")) + size = kwargs.get("size", size) + + BST = BinarySearchTree + b1 = BST(backend=Backend.PYTHON) + b2 = BST(backend=Backend.CPP) + + def f(backend, tree): + for node in range(-1000,1000): + tree.insert(node, node) + def g(backend, tree): + for node in range(-1000, 1000): + tree.search(node) + def h(backend, tree): + for node in range(2000): + tree.delete(node) + + kwds_dict_PY = {"backend": Backend.PYTHON, "tree":b1} + kwds_dict_CPP = {"backend": Backend.CPP, "tree":b2} + + timer_python = timeit.Timer(functools.partial(f, **kwds_dict_PY)) + python_insert = min(timer_python.repeat(repeat, number)) + + timer_cpp = timeit.Timer(functools.partial(f, **kwds_dict_CPP)) + cpp_insert = min(timer_cpp.repeat(repeat, number)) + assert cpp_insert < python_insert + + timer_python = timeit.Timer(functools.partial(g, **kwds_dict_PY)) + python_search = min(timer_python.repeat(repeat, number)) + + timer_cpp = timeit.Timer(functools.partial(g, **kwds_dict_CPP)) + cpp_search = min(timer_cpp.repeat(repeat, number)) + assert cpp_search < python_search + + timer_python = timeit.Timer(functools.partial(h, **kwds_dict_PY)) + python_delete = min(timer_python.repeat(repeat, number)) + + timer_cpp = timeit.Timer(functools.partial(h, **kwds_dict_CPP)) + cpp_delete = min(timer_cpp.repeat(repeat, number)) + assert cpp_delete < python_delete + + print("Python Time:") + print("insert(): ",python_insert,"s") + print("search(): ",python_search,"s") + print("delete(): ",python_delete,"s") + python_total = python_insert+python_search+python_delete + print("Total Python time: ", python_total,"s\n") + + print("C++ Time:") + print("insert(): ",cpp_insert,"s") + print("search(): ",cpp_search,"s") + print("delete(): ",cpp_delete,"s") + cpp_total = cpp_insert+cpp_search+cpp_delete + print("Total C++ time: ", cpp_total,"s\n") + + print("C++ backend is",round(python_total/cpp_total),"times faster!") + + test_BinarySearchTree() + ``` +
+ +Time taken for methods of Binary Search Tree class to execute in different backends: +

+ Centered Image +

+ +The picture clearly indicates the utility of the C++ backend. It makes code execution much faster. This is helpful for high-performance computing. + + +### Weekly reports + +My Google Summer of Code blogs are available on my website: **[https://kishanved.tech/blog/](https://kishanved.tech/blog/)** + +### Final Report + +My final report for Google Summer of Code 2024 is available here: **[Google Summer of Code 2024 Report](https://gist.github.com/Kishan-Ved/ebe0a971220d67517ae815e4f92d2459)** + +### Future work + +My project is complete, the C++ backend for all trees is fully functional. Some (non-critical) issues have been opened, these need to be addressed. For upcoming plans (and major goals), refer **[PyDataStructs Wiki](https://github.com/codezonediitj/pydatastructs/wiki/Google-Summer-of-Code-Project-Ideas)** on GitHub. + +### Learnings + +**Tech**: Mastered the art of linking a Python code to a C++ backend by using the Python-C API to improve speeds greatly. Polished my C++ and Python coding skills. + +**GitHub**: Learned various new commands, resolution of conflicts and merging branches for collaborative work. + +**Perseverance**: GSoC taught me to read the documentation, be calm and perseverant. It's difficult at the start but smoother ahead! + +--- + +And that's my entire open source story, from my first contribution to getting selected for Google Summer of Code! I hope this helps you to navigate through the amazing world of open source software! + +Thanks to my mentor **[Gagandeep Singh](https://github.com/czgdp1807)** for his support and guidance. Thanks to **[Ivan Ogasawara](https://github.com/xmnlab)** and the team at Open Science Labs and NumFOCUS. \ No newline at end of file diff --git a/pages/blog/my-opensciencelabs-internship-journey/index.qmd b/pages/blog/my-opensciencelabs-internship-journey/index.qmd new file mode 100644 index 000000000..45572245b --- /dev/null +++ b/pages/blog/my-opensciencelabs-internship-journey/index.qmd @@ -0,0 +1,82 @@ +--- +title: "My Open Science Labs Internship Journey" +slug: "my-opensciencelabs-internship-journey" +date: 2025-01-30 +authors: ["Mfonobong Uyah"] +tags: ["OpenScienceLabs", "OSL", "internship"] +categories: ["Internship", "Backend"] +description: "This blog post summarizes my internship in backend development at Open Science Labs with Ivan Ogasawara as my mentor and supervisor!" +thumbnail: "/header.png" +template: "blog-post.html" +--- + +# About Me + +I am [Mfonobong Uyah](https://www.linkedin.com/in/mfonobong-uyah-26901620a/), a graduate of Electrical and Electronics Engineering (EEE) from the Federal University of Technology, Akure (FUTA). In June 2024, I was privileged to be accepted for an internship at Open Science Labs. This program was mentored by [Ivan Ogasawara](https://github.com/xmnlab), the founder of Open Science Labs and a seasoned professional with over 10 years of experience in open-source and 22 years of experience in software development. + +# The Focus of My Internship Program + +My internship at Open Science Labs was designed to be comprehensive. As such, I was introduced to:
+• software development project work
+• coding challenges
+• project/code reviews
+• open-source events
+• weekly open study group sessions
+• Relevant online courses
+• personal study sessions
+ +# My First Contribution to Open Source + +My first open-source contribution was a bug fix for an issue pointed out by the linter in the Growth Forge repository. Here is a link to the PR: https://github.com/osl-incubator/growth-forge/pull/9 + +As a pre-requirement for participating in the OSL internship, potential candidates are to make, at least, one simple contribution. My mentor directed me to the open ‘linter’ issue on the Growth Forge repo, informing me that it was simple enough to start with. I took a look at the issue and was able to run pre-commit hooks to fix the linter problem. + +# About My Internship + +During my internship program, I worked on some of the most amazing incubated and affiliated projects under the OSL umbrella. Here’s a list of the repositories I engaged with and some of my activities. + +## Project Work +[Growth Forge:](https://github.com/osl-incubator/growth-forge) +My contribution to the Growth Forge repository involved creating test codes for the project’s views and models and running the respective tests, as well as recording the results. + +[OSL Website:](https://www.opensciencelabs.org) +For the OSL website repository, I engaged in backend updates of several pages, including the home, team, events, and blogs pages. I also added a few new pages such as the career page, and worked to implement a website-wide search feature. + +[Artbox:](https://github.com/osl-incubator/artbox) +I utilized the Artbox text-to-speech feature to generate audio summaries of the monthly newsletter. These audios were applied to the OSL podcast. + +Aside from coding activities and project work, I also contributed by identifying and raising issues, particularly in the Artbox repository. + +## Contribution Stats +Two statistics that define my internship contribution are: 21 merged pull requests and Python being the dominant programming language. + +## OSL and Advent of Code Challenges +I attempted several challenges from the OSL Challenge repo including the train, padlock-secret, and money-calculation challenges. I also participated in solving Advent of Code 2024 challenges themed around finding a missing Santa using sketchy direction clues. + +## Course Completions +Continuous learning is at the core of the OSL internship program. As such, I undertook several online courses, to enhance my understanding of coding concepts and learn new things altogether. Some of the courses I undertook during my internship were: Version Control with Git (available on [Software Carpentry](https://swcarpentry.github.io/git-novice/)), Docker Training Course for the Absolute Beginner (available on [KodeKloud](https://learn.kodekloud.com/courses/docker-training-course-for-the-absolute-beginner)), and Python for Everyone (available on [Runestone Academy](https://runestone.academy/ns/books/published/py4e-int/index.html)). + +## Open Source Events +Finding and participating in open-source events was another highlight of my internship journey. In October, I applied and received a scholarship to participate in the maiden edition of the [pyOpenSci Fall Festival](https://www.pyopensci.org/events/pyopensci-2024-fall-festival.html). This event featured interactive sessions on writing modular, clean code, building a Python package, and creating reproducible reports and presentations with Quarto and Great Tables. + +## Open Study Calls +My mentor, Ivan Ogasawara, invited me to join the LiteRev-sponsored Open Study Calls every Thursday. Here, I came across several new concepts/tools such as Retrieval-Augumented Generation (RAG), JupyterLabs, etc. + +After participating in more than 20 Open Study Calls, I built a culture of learning, was exposed to expert suggestions and reviews, and came across relevant courses and resources on coding and software development in general. + +## Impact of the Open Source and Open Science Community # + +Support from the open-source and open-science community proved handy throughout my internship at Open Science Labs. The OSL Discord server was a place where I could throw questions and get prompt and helpful responses from a number of experts. This significantly lightened the burden of finding my footing as a beginner in software development. + +## Lessons Learnt # + +There are numerous lessons to be learnt from an impactful internship like the one I had at Open Science Labs (OSL). I’ll share them with you. + +• Be Open to Corrections: +Seek corrections. At the beginning, it might be difficult or overwhelming. However, with time, you will be grateful for it. Having someone to correct you will only make your learning process easy as you can quickly move on from mistakes and avoid them in the future. + +• Be Upfront about Challenges: +The Open Science Labs internship is designed to be safe, warm, and welcoming. If you happen to have challenges during the program, it is okay to speak up about it. In fact, you should speak up about it. This will help your mentor/program coordinator make necessary adjustments, enabling your continued participation and progress. + +• Put Your Mind In the Work: +If you like your work, it becomes easier. One notable way to build this liking is to put your mind to it. Your focus will easily translate into an interest, improving the quality of your participation and your output in general. \ No newline at end of file diff --git a/pages/blog/newsletter-first-edition/index.qmd b/pages/blog/newsletter-first-edition/index.qmd new file mode 100644 index 000000000..fd5f723a7 --- /dev/null +++ b/pages/blog/newsletter-first-edition/index.qmd @@ -0,0 +1,142 @@ +--- +title: "Newsletter First Edition" +slug: "newsletter-first-edition" +date: 2024-10-19 +authors: ["Mfonobong Uyah"] +tags: ["Newsletter", "OSL", "First Edition"] +categories: ["Newsletter"] +description: | + The OSL newsletter is launching soon! Our first edition has been repurposed + for this blog post, but you can subscribe to receive future releases directly + in your inbox. +thumbnail: "/header.jpg" +template: "blog-post.html" +--- + +**Highlights:** + +- **OSL Grant News** +- **pyOpenSci’s Upcoming Fall Festival Event** +- **OSL Projects Development Report** +- **Open Study Group** + +## News: OSL Receives PSF Grant for MAKIM and ASTx Projects + +We're thrilled to announce that the Python Software Foundation (PSF) has granted +funding to two of our key projects: **MAKIM** and **ASTx**. This support will +help accelerate development and enhance the capabilities of these tools. Read on +to learn more about these projects and the impact of the PSF grant. + +### About the Python Software Foundation + +Founded in March 2001, the Python Software Foundation (PSF) is a nonprofit +organization dedicated to advancing and promoting the Python programming +language. The PSF supports a wide range of open-source Python projects, +fostering a vibrant and inclusive community. + +## Are You a Pythonista? Join the pyOpenSci Fall Festival 2024 + +Mark your calendars! Our partner is hosting a one-of-a-kind event. The +**pyOpenSci Fall Festival 2024** is an inaugural online meeting of Python, Open +Science, and Open Source enthusiasts set to take place from October 28 to +November 1, 2024. + +The event promises to feature insightful talks, essential hands-on workshops, +and office hours with numerous industry experts exchanging ideas and sharing +experiences. +[Go here](https://www.pyopensci.org/events/pyopensci-2024-fall-festival.html) to +learn more. + +## OSL Projects Development Report + +### MAKIM Improvements + +**MAKIM** is a YAML-based task automation tool inspired by Make. It offers +structured definitions for tasks and dependencies, supporting conditionals, +arguments, grouping, variables, Jinja2 templating, and environment file +integration. + +Makim team has made several recent updates to the project, including the +addition of new features supported by the PSF grant. + +- Added support for checking the .makim.yaml structure with a schema definition. +- Added support for matrix variables for tasks. +- Changed from dependencies support to hooks with pre and post run support. +- Fixed text problems and issues in the continuous integration jobs. +- Introduced support for interactive arguments, allowing for more dynamic user + input. +- Refactored the attribute "shell" to "backend," improving code clarity. +- Updated the configuration for MyPy to ensure better type-checking practices. + +Read more about MAKIM +[here](https://dev.to/opensciencelabs/streamlining-project-automation-with-makim-21nc). + +### ASTx Improvements + +**ASTx** is a language-agnostic expression structure designed primarily for the +ArxLang project. However, it can be utilized by any programming language or +parser to create high-level representations of Abstract Syntax Trees (AST). + +ArxLang team has made several developmental improvements to ASTx, including the +addition of new features supported by the PSF grant: + +- Added a new import statement feature for improved module management. +- Implemented runtime type checking using Typeguard. +- Enhanced type safety and reliability. +- Improved the development configuration structure, and dependencies. +- Added a transpiler from astx to python +- Added support to complex32 and complex64 +- Added support to float16, and float64 +- Added support to uint8, uint16, unit32, uint64, uint128 + +If you would like to read more on ASTx, +[go here](https://opensciencelabs.org/blog/console-based-representation-in-astx/). + +### Sugar and SciCookie Both Have New Updates + +**Sugar** and **SciCookie** projects are also receiving updates. The latest PRs on the Sugar repository include: + +- Added support for checking the .sugar.yaml structure with a schema definition. +- A fix for the Jinja2 template. +- A refactor of the interface for plugins/extensions that moves the main command + to the compose group. + +On the SciCookie project, SciCookie team has added some feature and +improvements: + +- Improved tests and infrastructure. +- Added support to pixi with pyproject. +- Added support to circleci. + +## What’s Next? How to Get Started Learning About OSL Projects and Activities + +- **Tour Our Website:** Explore our mission, vision, contribution guidelines, + and more on the [OSL website](https://www.opensciencelabs.org). +- **Become a Member:** Join our + [OSL Discord server](https://www.opensciencelabs.org/discord) to connect with + like-minded individuals, contribute to discussions, and collaborate to project + under OSL umbrella. Whether you have a technical background or are a new + enthusiast, everyone is welcome! + +- **Stay Connected:** Follow us on + [LinkedIn](https://www.linkedin.com/company/opensciencelabs) and + [X](https://twitter.com/opensciencelabs) to get updates about published + articles and events before they hit your email. + +- **Explore Our Projects and Ideas:** Visit our + [YouTube channel](https://www.youtube.com/@opensciencelabs/videos). With 12 + insightful videos already available and many more rolling out soon, you can + learn how to install and use our most popular tools, as well as gain knowledge + on programming languages, coding best practices, and past events. + +### Open Study Group + +Join our Open Study Group! Everyone is welcome to participate in our dedicated +one-hour sessions designed to support your personal studies. Use this online +meeting space to focus on your work, ask questions, and share updates about your +progress. Whether you're tackling a new project, learning a new skill, or simply +seeking a quiet time to study, our study group provides a supportive and +collaborative environment to help you achieve your goals. Come connect with +fellow learners and make the most of your study time together! + +Ask for more information on our [Discord](https://www.opensciencelabs.org/discord). diff --git a/pages/blog/newsletter-second-edition/index.qmd b/pages/blog/newsletter-second-edition/index.qmd new file mode 100644 index 000000000..d09d597d2 --- /dev/null +++ b/pages/blog/newsletter-second-edition/index.qmd @@ -0,0 +1,98 @@ +--- +title: "Newsletter Second Edition" +slug: "newsletter-second-edition" +date: 2024-12-11 +authors: ["Mfonobong Uyah"] +tags: ["Newsletter", "OSL", "Second Edition"] +categories: ["Newsletter"] +description: | + The second edition of the OSL newsletter is here. Catch up on the latest updates from within and outside our organisation. Don't forget to share this resource with your network. +thumbnail: "/header.jpg" +template: "blog-post.html" +--- + +**Highlights:** + +- **Review of pyOpenSci Fall Festival 2024** +- **ArxLang/ASTx talk at PyConLadies2024** +- **Did You Know?** +- **What’s New in Software Development?** +- **More on MAKIM and ArxLang/ASTx Developments** +- **Open Study Group** + +## Review: pyOpenSci Fall Festival 2024 in A Few Sentences + +Our partner, pyOpenSci, just concluded the maiden edition of its Fall Festival. The highly anticipated event ran from October 28 to November 1, 2024, exposing participants to coding best practices, exciting sample projects, new tools and products, and games like Roast My Repo. The entire event was hosted on Spatial Chat. + +If you were unable to attend this event, we got you covered. Here’s a brief summary of each day’s activities: + +- Day 1: Keynote talks from Eric Ma, Melissa Medoca, and Rowan Cockett. These experts spoke on Open Science in Relation to biomedicine and LLMs, the impact of Open Source on Open Science, and how Markdown catalyses scientific computations through MySTMd. + +- Day 2: A three-part session on formatting code, modularising code, and finally, testing code. + +- Day 3: Insightful package creation tutorial using a template designed and owned by the pyOpenSci community. + +- Day 4: Tips and tricks to effectively sharing a code. How to publish your package on TestPyPI, and add a DOI to your GitHub repo using Zenodo. + +- Day 5: Reproducible reports with Quarto (interactive Python and R in your browser). Speech by George Stagg and demo by James Balamuta. + +## News: ArxLang/ASTx talk at PyConLadies2024 + +Ana Krelling, an impeccable contributor to the OSL community, gave a talk tiled “ASTx: Empowering Language Processing Through Custom Abstract Syntax Trees” at the recently concluded PyConLadies 2024 event. + +You can watch Ana's insightful session [here](https://www.youtube.com/watch?v=azVNQTFmuhA). + +## Did You Know? + +You can convert a Jupyter notebook to a script in the terminal +Once you have the terminal open, you can use it to run Jupyter notebooks as scripts by running: + +```jupyter nbconvert --to script .ipynb``` + +This converts the notebook into a Python script, which can then be executed directly from the terminal. You can also replace 'script' with any of the following; 'asciidoc', 'custom', 'html', 'latex', 'markdown', 'notebook', 'pdf', 'python', 'qtpdf', 'qtpng', 'rst', 'slides', or 'webpdf'. + +## What’s New in Software Development + +**Linux Kernel 6.12 Has Been Released.** +The new update is intended to receive Long-Term Support (LTS), up until 2026. It reportedly comes packed with features such as real-time computing with PREEMPT_RT, hardware enhancements for AMD, Intel, and NVIDIA, and network improvements for DMTCP, IPv6 IOAM6, and PTP Timestamps. + +Other pecks of the Linux Kernel 6.12 are driver updates, thermal core testing, file-backed mount support, guest PMU support, ARM permission overlay support, android guest support, and more. You can read more on this [here](https://www.developer-tech.com/news/linux-kernel-6-12-real-time-capabilities-hardware-boosts-and-more/). + +## Updates: Recent Implementations from the Makim and ArxLang/ASTx teams + +### Makim team + +The updates introduce support for SSH-based command execution, along with a fix for the validate_config function name. It also includes the addition of initial infrastructure for SSH-related tests and a fix for the xonsh version. On the continuous integration (CI) side, the linter job was fixed, and a matrix strategy was added to improve testing coverage. + +### ArxLang/ASTx team + +The update adds the IfExpr and WhileExpr classes, while also fixing the argument type in the FunctionCall class. Additionally, the output for the ForRangeLoopExpr was improved, and support for boolean operators was added to the transpiler. The linter issues were also addressed and fixed. + +## What’s Next? How to Get Started Learning About OSL Projects and Activities + +- **Tour Our Website:** Explore our mission, vision, contribution guidelines, + and more on the [OSL website](https://www.opensciencelabs.org). +- **Become a Member:** Join our + [OSL Discord server](https://www.opensciencelabs.org/discord) to connect with + like-minded individuals, contribute to discussions, and collaborate to project + under OSL umbrella. Whether you have a technical background or are a new + enthusiast, everyone is welcome! + +- **Stay Connected:** Follow us on + [LinkedIn](https://www.linkedin.com/company/opensciencelabs) and + [X](https://twitter.com/opensciencelabs) to get updates about published + articles and events before they hit your email. + +- **Explore Our Projects and Ideas:** Visit our + [YouTube channel](https://www.youtube.com/@opensciencelabs/videos). With 12 + insightful videos already available and many more rolling out soon, you can + learn how to install and use our most popular tools, as well as gain knowledge + on programming languages, coding best practices, and past events. + +### Open Study Group + +Join our Open Study Group! Everyone is welcome to participate in our dedicated one-hour sessions designed to support your personal studies. Use this online meeting space to focus on your work, ask questions, and share updates about your progress. Whether you're tackling a new project, learning a new skill, or simply seeking a quiet time to study, our study group provides a supportive and collaborative environment to help you achieve your goals. Come connect with fellow learners and make the most of your study time together! + +This study group is sponsored by [LiteRev](https://literev.unige.ch/)! + +Ask for more information on our [Discord](https://www.opensciencelabs.org/discord). diff --git a/pages/blog/open-science-labs-participating-google-summer-of-code-2025-mentoring-organization/index.qmd b/pages/blog/open-science-labs-participating-google-summer-of-code-2025-mentoring-organization/index.qmd new file mode 100644 index 000000000..3b934fde0 --- /dev/null +++ b/pages/blog/open-science-labs-participating-google-summer-of-code-2025-mentoring-organization/index.qmd @@ -0,0 +1,40 @@ +--- +title: "Open Science Labs is participating in the Google Summer of Code 2025 as a Mentoring Organization!" +slug: open-science-labs-participating-google-summer-of-code-2025-mentoring-organization +date: 2025-03-01 +authors: ["Open Science Labs Team"] +tags: [google summer of code, gsoc, open-source, open-science] +categories: [open-source, gsoc] +description: | + Open Science Labs (OSL) is participating as a mentoring organization in Google Summer of Code (GSoC) 2025 for the first time. Students and newcomers can join open-source projects, connect with mentors, and apply via the GSoC website. Visit the official page of OSL or Discord for details. +thumbnail: "/gsoc_osl.png" +--- + +## Open Science Labs is participating in the Google Summer of Code 2025 as a Mentoring Organization! + +![Google Summer of Code 2025 with Open Science Labs](gsoc_osl.png) + +We are thrilled to announce that Open Science Labs (OSL) has been selected as one of the 185 mentoring organizations for Google Summer of Code (GSoC) 2025. This is an exceptional opportunity for our community, our projects and sub-organizations, and new contributors to engage in open source software development. As an independent organization participating for the first time in GSoC, we are thrilled to welcome students and newcomers to collaborate on meaningful projects under the guidance of experienced mentors. + +### Participating Projects and Sub-Organizations + +You can check it in the [Open Science Labs Official page](https://opensciencelabs.org/opportunities/gsoc/project-ideas/), where each organization has its own set of project ideas and mentors. Feel free to reach out to the mentors directly for inquiries or join our [Discord server](https://opensciencelabs.org/discord) for discussions. + +### How to Participate + +#### For Students and Contributors + +If you are a student or newcomer eager to contribute to open source projects, here is how you can get involved: + +1. Explore Project Ideas: Check out the list [here](https://opensciencelabs.org/opportunities/gsoc/project-ideas/). +2. Introduce Yourself: Reach out to the organization or project mentors you are interested in working with. +3. Review Guidelines: Read our Contributor Guide and the official GSoC [Student Instructions](https://summerofcode.withgoogle.com/). +4. Apply: Submit your application through the GSoC website once the application period opens. + +For the latest project ideas and detailed submission guidelines, check out our official page or other communication channels. + +### Get Involved! + +- **For inquiries**: Email us at . +- **Join the conversation**: Connect with us on [Discord](https://opensciencelabs.org/discord). +- **Learn more about GSoC**: Visit the official [GSoC website](https://summerofcode.withgoogle.com/). diff --git a/pages/blog/osl-2-0/index.qmd b/pages/blog/osl-2-0/index.qmd new file mode 100644 index 000000000..6d39510fb --- /dev/null +++ b/pages/blog/osl-2-0/index.qmd @@ -0,0 +1,159 @@ +--- +title: "OSL 2.0" +slug: osl-2-0 +date: 2026-01-19 +authors: ["Ivan Ogasawara"] +tags: [osl] +categories: [osl] +description: | + OSL 2.0 is a new chapter—and an open invitation. If you believe in Growth, Collaboration, and Impact, there's a place for you here: to learn in public, contribute to real projects, and grow into a leader who helps others do the same. Whether you're a student, a researcher, a maintainer, or simply someone who cares about open source and open science, we'd love to meet you. Read our mission, join the conversation, and take your first step with us—one small contribution can become a life-changing journey. +thumbnail: "/header.png" +--- + +## OSL 2.0 + +Open Science Labs (OSL) was born in 2015, when I was working at a transportation +engineering lab. The idea was simple: help researchers bring open science into +their daily work. + +After a few months, life took me in other directions, and OSL went on pause. + +In 2018, a few friends were looking for their first jobs in tech. I decided to +reactivate OSL (back then called _Open Data Science Labs_) to support them with +mentoring and internships, and to connect them to open source projects in +research settings. + +Through this initiative, my friends **Sandro Loch** and **Elton Santana** +started contributing to a scientific project I had previously worked on: +**Alerta Dengue**, led by professor **Flavio Coelho**. A few months later, both +of them were hired. + +That was the real beginning of OSL. It matched what moves me most: helping +people grow while helping open source projects move forward. + +--- + +### Growing together + +From there, OSL started many kinds of activities: mentoring, open source +incubation, internships, translations, webinars, and more. + +We received support from wonderful people and friends like **Francisco Palm**, +**Mariangela Petrizzo**, **Ever Vino**, **Eunice Rodas**, **Luis Gago**, +**Agustina Pesce**, **Sandro Loch**, and many others. + +New interns joined too, such as **Luã Bida**, **Anavelyz Perez**, and **Yurely +Camacho**, who helped keep many internal projects moving. Later, Anavelyz also +helped coordinate our activities related to **Google Summer of Code (GSoC)**. + +And speaking of GSoC, we are deeply grateful to **Gagandeep Singh** for his +guidance and support over the years. + +--- + +### Our partners along the way + +OSL also received direct and indirect support from several partners, including: +**Alerta Dengue**, **The GRAPH Network**, **The GRAPH Courses**, **pyOpenSci**, +**LiteRev**, and **IGDORE**. + +A big thank you to **Olivia Keiser**, **Sara Botero**, **Flavio Coelho**, +**Aziza**, **Erol Orel**, and many others for the opportunities and trust. + +We are also very grateful to the **Python Software Foundation**, which supported +us with three grants to help maintain three affiliated projects: **SciCookie**, +**Makim**, and **ASTx**. + +These grants allowed us to hire amazing former interns like **Anavelyz**, +**Yurely**, **Abhijeet**, and **Ana Paula**. + +--- + +### The game changer: Google Summer of Code + +A big turning point for our community was joining the **Google Summer of Code** +program. + +In the first two years, we joined GSoC under the umbrella of **NumFOCUS**, who +opened the doors for us. Later, in 2025, OSL was accepted as an official **GSoC +Mentoring Organization**. + +GSoC put us “on the map” for newcomers, which has always been one of our main +audiences. Many new people started reaching out to participate in GSoC with OSL. +This was beautiful—but also challenging. + +We were not fully prepared for such a large number of candidates. It was hard to +manage, and we learned a lot in the process. + +GSoC 2026 has not yet been announced, and we are already receiving messages from +new candidates. This is one of the reasons we felt the need to pause, look at +ourselves, and clarify our mission, motivation, and purpose. + +We want to make these clear and public so that people join us because they +connect with our values, not only because of GSoC. Otherwise, many might feel +disappointed, since only a few can be selected each year. + +You can read more about our mission, motivation, and purpose [here](/about). +Below is a short summary. + +--- + +### Our core: Growth, Collaboration, Impact + +The three core values that represent our community are: + +- **Growth** +- **Collaboration** +- **Impact** + +We are building a community where anyone who shares these values has a space to: + +- Learn and grow +- Contribute to real projects +- Create a positive impact in the world + +We truly believe that open source is a powerful way to connect people who want +experience with maintainers who need contributors. + +Today, only a small fraction of students manage to work in the field they +studied after finishing university. That's the gap OSL is trying to help fill. + +--- + +### We don't want followers, we want leaders + +The focus of OSL is not to create followers, but to help people become +**leaders**. + +We want people in our community who feel our projects as **their** projects, +with the same love and passion that we put into them. + +Of course, each person grows at their own pace. It can take time to understand +the architecture, roadmap, and purpose of a project. It can also take time +before you feel confident to contribute independently or receive an invitation +to become a maintainer. + +That's normal. Don't give up. + +Keep moving forward. Ask for feedback. Use every step to grow. + +As **Jim Kwik** says in his _Super Brain_ course: + +> “Practice doesn't make perfect. Practice makes progress.” + +--- + +### Thank you 💚 + +Before closing, I want to say a special thank you to some amazing collaborators +who have helped us more actively in different projects and initiatives: **Sandro +Loch**, **Ever Vino**, **Felipe Paes**, **Satarupa Deb**, **Yuvi Mittal**, +**Aniket Kumar**, **Abhijeet**, and all our interns, mentors, former steering +council members, and partners. + +Thank you all for being part of our lives and our journey. Together, we are +building a welcoming place where people can grow, contribute, and make a real +difference. + +OSL 2.0 is not just a new phase of a project. It is a new chapter in a community +of people who care. diff --git a/pages/blog/packaging-a-vs-code-extension-using-pnpm-and-vsce/index.qmd b/pages/blog/packaging-a-vs-code-extension-using-pnpm-and-vsce/index.qmd new file mode 100644 index 000000000..9933b6d43 --- /dev/null +++ b/pages/blog/packaging-a-vs-code-extension-using-pnpm-and-vsce/index.qmd @@ -0,0 +1,103 @@ +--- +title: "Packaging a VS Code Extension Using pnpm and VSCE" +slug: "packaging-a-vs-code-extension-using-pnpm-and-vsce" +date: 2025-08-31 +authors: ["Ansh Arora"] +tags: ["Makim", "Automation", "VSCode", "pnpm", "esbuild"] +categories: ["Packaging", "Node", "Extensions"] +description: | + A step-by-step guide to packaging and publishing VS Code extensions with pnpm and vsce, + covering how to avoid dependency resolution issues. +thumbnail: "/header.png" +template: "blog-post.html" +--- + +# Packaging a VS Code Extension Using pnpm and VSCE + +VS Code’s `vsce` tool doesn't play nicely with `pnpm` out of the box; here’s a +proven workaround using bundling and the `--no-dependencies` flag to get things +running smoothly. + +--- + +## Why pnpm + vsce can be problematic + +`vsce` relies on `npm list --production --parseable --depth=99999`, which fails +under pnpm's symlink-based dependency management, often throwing +`npm ERR! missing:` errors. +([github.com](https://github.com/microsoft/vscode-vsce/issues/421), +[daydreamer-riri.me](https://daydreamer-riri.me/posts/compatibility-issues-between-vsce-and-pnpm/)) + +--- + +## Solution Overview + +1. **Bundle your extension** using a bundler such as **esbuild** or **Webpack** +2. **Use `--no-dependencies`** when running `vsce package` and `vsce publish` + +Because all dependencies are bundled, `vsce` no longer needs to resolve them +from `node_modules`. + +--- + +## Step-by-Step Setup + +### 1. Install Tools + +```bash +pnpm add -D @vscode/vsce esbuild +``` + +@vscode/vsce` is the CLI for packaging and publishing VSCode extensions. Recent +versions (e.g., v3.6.0) support npm (≥6) and Yarn (1.x), but don't officially +support pnpm. + +### 2\. Configure `package.json` + +Scripts Add build and packaging commands: jsonc Copy code + +```json +{ + "scripts": { + "vscode:prepublish": "pnpm run bundle", + "bundle": "esbuild ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node --minify", + "package": "pnpm vsce package --no-dependencies", + "publish": "pnpm vsce publish --no-dependencies" + } +} +``` + +- `vscode:prepublish`: runs before packaging; bundles source using esbuild +- `bundle`: compiles `extension.ts` into `out/main.js` and excludes the `vscode` + module +- `package` / `publish`: calls VSCE via pnpm, skipping dependency resolution + +### 3\. Why It Works + +By bundling dependencies manually, `vsce` doesn’t need to resolve them during +packaging or publishing. The `--no-dependencies` option avoids pnpm’s symlink +issues entirely. + +## Sample `package.json` Snippet + +```json +{ + "devDependencies": { + "@vscode/vsce": "^3.6.0", + "esbuild": "^0.XX.X" + }, + "scripts": { + "vscode:prepublish": "pnpm run bundle", + "bundle": "esbuild ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node --minify", + "package": "pnpm vsce package --no-dependencies", + "publish": "pnpm vsce publish --no-dependencies" + } +} +``` + +### **Wrap-Up** + +Using **pnpm** with VS Code extensions involves a few extra steps because `vsce` +doesn’t support pnpm’s dependency structure directly. The ideal workflow: _ +**Bundle your extension first**, then _ **Use `--no-dependencies`** to package +and publish safely. diff --git a/pages/blog/psf-funding-open-source-projects-development-scicookie/index-en.qmd b/pages/blog/psf-funding-open-source-projects-development-scicookie/index-en.qmd new file mode 100644 index 000000000..235039a17 --- /dev/null +++ b/pages/blog/psf-funding-open-source-projects-development-scicookie/index-en.qmd @@ -0,0 +1,224 @@ +--- +title: "PSF funding open source projects development: SciCookie" +slug: psf-funding-open-source-projects-development-scicookie +date: 2023-09-22 +authors: ["Anavelyz Perez", "Yurely Camacho"] +tags: [psf, osl, scicookie, grant, community, collaboration, development] +categories: [open source, software development, python] +description: | + In this article, we will share our experience in applying for and + executing a Python Software Foundation (PSF) grant on behalf of Open + Science Labs (OSL), submitted between January and February 2023. The + proposal was submitted to contribute to the development and maintenance + of SciCookie, a Python tool within the [OSL incubation + projects](/projects/incubation/). +thumbnail: "/header.svg" +template: "blog-post.html" +--- + + + + +In this article, we will share our experience in applying for and executing a +Python Software Foundation (PSF) grant on behalf of Open Science Labs (OSL), +submitted between January and February 2023. The proposal was submitted to +contribute to the development and maintenance of SciCookie, a Python tool within +the [OSL incubation projects](/projects/incubation/). + +We'll begin by introducing SciCookie, highlighting its key features and aspects +that might interest you. Then, we'll go over the grant application process and +share our reflections on the experience, along with the lessons we learned. + +## What is SciCookie? + +As we mentioned at the beginning, SciCookie is a Python tool designed to provide +a Python project template. Its main goals are to simplify the process of +creating your projects and save you a considerable amount of time because, +according to your needs and planning, it gives you a starting point for the +configuration of your project. SciCookie provides several tools that are as +up-to-date as possible and adheres to community standards. + +SciCookie is mainly based on PyOpenSci recommendations regarding the tools, +libraries, best practices and workflows employed by the significant Python +scientific groups. The elements we mention are listed as options, which means +that you can adapt various approaches in your project, through a text interface +(TUI) provided by SciCookie. + +SciCookie is available from [PyPI](https://pypi.org/project/scicookie/) and +[conda](https://anaconda.org/conda-forge/scicookie). You can also visit its +repository at [GitHub](https://github.com/osl-incubator/scicookie). + +Now that you know a bit about this project, let's tell you about PSF and how it +supports the Python community. + +## What is PSF and how does it support the Python community? + +The Python Software Foundation (PSF) is an organization dedicated to the +advancement and improvement of open source technologies. Its mission is to +promote, protect and advance the Python programming language. In addition, it +supports and facilitates the development/growth of the Python developers +community; a diverse and international community. + +Among the programs that PSF promotes to achieve its mission, there is a _Grants +Program_, where proposals for projects related to the development of Python, +technologies associated with this programming language and educational +resources, are welcome. Since the creation of the program, PSF has supported +several interesting projects, you can click +[here](https://www.python.org/psf/records/board/resolutions/) to see the list or +have a clearer notion of the proposals, and maybe you will be encouraged to +apply with new projects or ideas. + +You should know that the PSF in the grants program evaluates a number of aspects +in each proposal, including the usefulness of the project and the impact on the +Python community. In case you want to know more, we recommend you visit the +space that PSF has on its website for the +[grants program](https://www.python.org/psf/grants/). + +So far, we have given you a brief overview of the main aspects of the two +parties involved: SciCookie and PSF. We will continue by telling you about the +grant application, what motivated us, the arranging and assigning tasks. + +## What was the grant application process like? + +The PSF grant application process was a long and challenging, but also very +rewarding. It began with careful planning and research. We studied the needs of +the scientific community and found a project that could help meet those needs. +In other words, we build on a strong case for the grant. + +We were studying the different projects within the Open Science Labs incubation +program; where there are a series of approaches and technologies implemented, +including Python projects associated with DevOps, Data Science, and scientific +projects. The option that was best suited to apply for the grant in our case was +SciCookie; because it is a very useful tool and is focused on helping the Python +community. + +After completing the planning and research, we began the formal application +process. This included completing an online form and submitting a detailed +proposal. The proposal contains a project description, timeline, budget and +impact section. In our case and in order to review each aspect carefully, we +produced a +[file with the responses](https://github.com/OpenScienceLabs/grant-proposals/blob/96263f736e7f36eb22a3dd1baa16376fd1782e98/psf_proposal.md) +[1] and filled in the budget scheme (template provided by the PSF). This process +was done under the advice of the OSL Steering Council. + +In addition to the above, a series of issues were created and edited in the +project repository, in order to be clear about the activities to be carried out +and the time it would take to develop them in case the proposal was approved. + +Once we had submitted our proposal, we had to wait some months for a decision. +It was a period of great uncertainty, but finally we received the news that our +proposal had been accepted! + +## How was the workflow? + +With the PSF grant, we were able to start developing and maintaining SciCookie +part-time. We worked with some community developers to add new features, improve +documentation and fix bugs. This included the creation of a +[user guide](https://github.com/osl-incubator/scicookie/blob/main/docs/guide.md) +to help enthusiasts and developers to use SciCookie. + +In terms of task specification, as we described in the previous section, a +series of issues were generated in the project repository, and each one of us +handled some of the issues on a weekly basis via Pull Requests (PRs). These were +approved by members of the Open Science Labs team, who were also on hand +throughout the execution of the proposal. + +Being a bit more specific about the follow-up, we had from an initial meeting +where we discussed the fundamental aspects of the project and set up what was +necessary to carry it out, to weekly meetings to present our progress, to check +if we had any obstacles or doubts that did not allow us to move forward. +Likewise, each PR was reviewed and if there were any observations, we had +feedback on this. + +In summary, we can tell you that it was quite a dynamic workflow, where a +friendly space was built and allowed us to learn a lot. + +![Flujo de trabajo](workflow.png) + +> We would like to take this opportunity to thank +> [Ivan Ogasawara](https://github.com/xmnlab) and to +> [Ever Vino](https://github.com/EverVino), for their time and dedication. Both +> are active members of OSL and members of the steering council; they were there +> to support us and clarify our questions. + +Here we tell you about our experience and the collaboration phase. + +## How was our learning process? + +SciCookie provided us, for the first time, with the opportunity to make such a +significant contribution to an open science and open source project. It also +allowed us to acquire new knowledge about some aspects and technologies linked +to the Python programming language, since at that time, our knowledge was more +oriented to the use of libraries, objects, loops, among others. + +About this learning process we can tell you that we did not know many things and +it was necessary to learn along the way, sometimes this was a bit challenging +but, in general, very profitable. Among the anecdotes that we rescued is that, a +couple of times, we "exploded" the code and we didn't know why; the cause was +that we didn't know the exact use of single or double quotes, double braces, +spaces or tabs within the template. But then we were able to move forward and we +even made improvements in the workflow of the project. + +Regarding the latter, we can certainly tell you that learning curves are always +steep. At the beginning you see everything uphill, but when you are familiar +with the technology and the tools, everything becomes easier. Daring is always +the first step. + +On the other hand, if you are interested in collaborating on open source +projects, it is vital to have basic knowledge of Git and GitHub version control +tools, and to understand their essential commands such as git pull, git push, +git rebase, git log, git stash, among others. You may also need knowledge of +conda and poetry. We also learned a bit of jinja2 and make, and reviewed +knowledge of function creation, conditional evaluation, GitHub workflow, +documentation aspects and some of the technologies associated with it. + +In summary, the experience of applying for and executing a PSF grant was a +valuable experience. We learned a lot about the process, how to develop and +maintain a Python tool, what structure a Python library or package project +should have, and how to build a community around an open source project. We are +also grateful for the support of the PSF, which has allowed us to make a +contribution to SciCookie. We feel satisfied with the work we have done and are +excited about the future of this tool. + +To all that we have told you, we add an invitation to collaborate on open source +or open science projects and, if you have already done so, we encourage you to +continue to do so. We were often motivated by seeing our PRs being approved, we +shared feelings of achievement and new challenges and, most importantly, we were +applying what open source promotes: small collaborations make big changes and +add to the projects, achieving good and useful results. + +After all this, you may wonder about the barriers to collaboration. We dedicate +the following lines to describe what we rescued from our experience. + +## Can you find barriers to collaboration? + +The progress of your contributions depends on you. It is vital to ask questions +and not get bogged down by doubts. Often there is someone who can show you that +the problem you thought was big was simply a small one, perhaps the code didn't +work because it was single quotes instead of double quotes, for example. + +From the OSL community we can highlight that it focuses on creating friendly, +opportunity-filled spaces where you can share and acquire new knowledge, +eliminating barriers and discrimination. Perhaps you can find these same +characteristics in other open science and/or open source projects. + +That's why we want to invite you again to support and join the diverse Python +and open source community. It's an excellent experience and the fact of +contributing to something that can be useful to other people is quite +satisfying. + +In general, collaborating on open source projects is a great way to improve your +programming skills, you also have the opportunity to work with other developers +and learn from them, get feedback on your work. If you want to support or boost +your project, the first thing to do is to get started. Many communities are open +to new contributions and innovative ideas. + +Leave us your comments if you want to know more about what we have told you in +this space :D + +[1] **Additional note**: SciCookie originally went by the name of +cookiecutter-python and then renamed to osl-python-template. + +Graphic elements of the cover were extracted from +[Work illustrations by Storyset](https://storyset.com/work), and then edited to +conform to the article. diff --git a/pages/blog/psf-funding-open-source-projects-development-scicookie/index.qmd b/pages/blog/psf-funding-open-source-projects-development-scicookie/index.qmd new file mode 100644 index 000000000..6814680c5 --- /dev/null +++ b/pages/blog/psf-funding-open-source-projects-development-scicookie/index.qmd @@ -0,0 +1,242 @@ +--- +title: "PSF financiando el desarrollo de proyectos open source: SciCookie" +slug: psf-funding-open-source-projects-development-scicookie +date: 2023-09-02 +author: ["Anavelyz Perez", "Yurely Camacho"] +tags: + [psf, osl, scicookie, subvención, grant, comunidad, colaboración, desarrollo] +categories: [código abierto, desarrollo de software, python] +description: | + En este artículo, compartiremos nuestra experiencia en la solicitud y ejecución + de una subvención de la Python Software Foundation (PSF) a nombre de Open + Science Labs (OSL) que fue enviada entre enero y febrero de 2023. La propuesta + se hizo con la finalidad de contribuir con el desarrollo y mantenimiento de + SciCookie, una herramienta de Python que se encuentra dentro de los + [proyectos de incubación de OSL](/projects/incubation/). +thumbnail: "/header.svg" +template: "blog-post.html" +--- + + + + +En este artículo, compartiremos nuestra experiencia en la solicitud y ejecución +de una subvención de la Python Software Foundation (PSF) a nombre de Open +Science Labs (OSL) que fue enviada entre enero y febrero de 2023. La propuesta +se hizo con la finalidad de contribuir con el desarrollo y mantenimiento de +SciCookie, una herramienta de Python que se encuentra dentro de los +[proyectos de incubación de OSL](/projects/incubation/). + +Comenzaremos con una breve introducción a SciCookie, algunas de sus +características clave y aspectos de interés. Luego, discutiremos el proceso de +solicitud de la subvención y haremos algunas reflexiones sobre la experiencia y +lo que aprendimos. + +## ¿Qué es SciCookie? + +Como te mencionamos al inicio, SciCookie es una herramienta de Python diseñada +para proveer una plantilla de proyecto Python. Sus principales objetivos son +simplificar el proceso de creación de tus proyectos y ahorrarte una cantidad +considerable de tiempo porque, de acuerdo a tus necesidades y planificación, te +da un punto de partida para la configuración de tu proyecto. SciCookie +proporciona varias herramientas lo más actualizadas posibles, además se adhiere +a los estándares de la comunidad. + +Esto último es porque SciCookie, se basa principalmente en las recomendaciones +de PyOpenSci en lo que se refiere a las herramientas, bibliotecas, mejores +prácticas y flujos de trabajo empleados por los grupos científicos +significativos de Python. Los elementos que mencionamos se encuentran como +opciones, lo que significa puedes adaptar diversos enfoques en tu proyecto, +mediante una interfaz de texto (TUI) que te proporciona SciCookie. + +SciCookie está disponible en [PyPI](https://pypi.org/project/scicookie/) y +[conda](https://anaconda.org/conda-forge/scicookie). También puedes visitar su +repositorio en [GitHub](https://github.com/osl-incubator/scicookie). + +Ahora que ya conoces un poco sobre este proyecto, te contamos un poco sobre PSF +y cómo apoya a la comunidad Python. + +## ¿Qué es PSF y cómo apoya a la comunidad? + +La Python Software Foundation (PSF) es una organización dedicada al avance y +mejora de las tecnologías de código abierto. Su misión es promover, proteger e +impulsar el lenguaje de programación Python. Además, apoya y facilita el +desarrollo/crecimiento de la comunidad de programadores Python; una comunidad +diversa e internacional. + +Entre los programas que promueve PSF para lograr su misión, se encuentra un +Grants Program (programa de subvenciones), donde las propuestas para proyectos +relacionados con el desarrollo de Python, tecnologías asociadas a este lenguaje +de programación y recursos educacionales, son bienvenidas. Desde la creación del +programa, PSF ha apoyado varios proyectos interesantes, puedes hacer clic +[aquí](https://www.python.org/psf/records/board/resolutions/) para ver la lista +o tengas una noción más clara de las propuestas, y quizá te animes a aplicar con +nuevos proyectos o ideas. + +Continuando con el programa de subvenciones, debes conocer que la PSF evalúa una +serie de aspectos en cada propuesta, entre ellos la utilidad del proyecto y el +impacto en la comunidad Python. En caso de que desees conocer más, te +recomendamos visitar el espacio que tiene PSF en su página web para el +[grants Program](https://www.python.org/psf/grants/). + +Hasta ahora y de manera resumida, te hemos relatado los principales aspectos +sobre las dos partes involucradas: SciCookie y PSF. Proseguiremos contándote +sobre la solicitud de la subvención, lo que nos motivó y la división de nuestras +tareas. + +## ¿Cómo fue el proceso de solicitud del grant o subvención? + +El proceso de solicitud de subvención de la PSF fue un proceso largo y +desafiante, pero también muy gratificante. Comenzó con una cuidadosa +planificación e investigación. Debíamos comprender las necesidades de la +comunidad científica y encontrar un proyecto que pudiese ayudar a satisfacerlas. +Es decir, se requería desarrollar un caso sólido para la subvención. + +En vista de esto, hicimos un estudio de los distintos proyectos dentro del +programa de incubación de Open Science Labs; donde hay diferentes enfoques y +tecnologías implementadas, incluyendo proyectos en Python asociados a DevOps, +Data Science, y proyectos científicos. La opción que mejor se adaptó para +solicitar la subvención en nuestro caso fue SciCookie; porque es una herramienta +bastante útil y se encuentra enfocada en ayudar a la comunidad Python. + +Después de completar la planificación y la investigación, comenzamos el proceso +de solicitud formal. Esto incluyó completar un formulario en línea y presentar +una propuesta detallada. La propuesta contiene una descripción del proyecto, un +cronograma, un presupuesto y una sección de impacto. En nuestro caso y para +revisar con detenimiento cada aspecto elaboramos un +[archivo con las respuestas](https://github.com/OpenScienceLabs/grant-proposals/blob/96263f736e7f36eb22a3dd1baa16376fd1782e98/psf_proposal.md) +[1] y llenamos el esquema del presupuesto (plantilla proporcionada por la PSF). +Este proceso se hizo bajo la asesoría del Steering Council de OSL. + +Además de lo anterior, se crearon y editaron una serie de issues en el +repositorio del proyecto, para tener claras las actividades a realizar y el +tiempo que tomaría desarrollarlas en caso de que fuese aprobada la solicitud. + +Una vez realizada la presentación de la propuesta por nuestra parte, tuvimos que +esperar varios meses para recibir una decisión. Fue un período de gran +incertidumbre, pero finalmente recibimos la noticia de que nuestra propuesta +había sido ¡aceptada! + +## ¿Cómo fue el flujo de trabajo? + +Con la subvención de la PSF, pudimos comenzar a desarrollar y mantener SciCookie +a medio tiempo. Trabajamos con algunos desarrolladores de la comunidad para +agregar nuevas funcionalidades, mejorar la documentación y corregir errores o +bugs. Entre esto destacamos la creación de una +[guía de usuario](https://github.com/osl-incubator/scicookie/blob/main/docs/guide.md) +para ayudar a los entusiastas y desarrolladores a aprender a utilizar SciCookie. + +En cuanto la especificación de las tareas, como te mencionamos en la sección +anterior, se crearon una serie de issues en el repositorio del proyecto, y en +base en esto cada una resolvía semanalmente varios de los issues mediante Pull +Requests (PRs). Estos eran aprobados por miembros del equipo de Open Science +Labs, quiénes además estuvieron al pendiente durante toda la ejecución de la +propuesta. + +Siendo un poco más específicas sobre el seguimiento, tuvimos desde una reunión +inicial donde abordamos los aspectos fundamentales del proyecto y configuramos +lo necesario para llevarlo a cabo, hasta reuniones semanales para presentar +nuestros avances, verificar si se nos había presentado alguna traba o duda que +no nos permitiera avanzar. Así mismo, cada PR era revisado y si existía alguna +observación, teníamos feedback sobre esto. + +En resumen, te podemos decir que fue un flujo de trabajo bastante dinámico, +donde se construyó un espacio amigable y nos permitió aprender bastante. + +![Flujo de trabajo](workflow.png) + +> Aprovechamos este espacio para agradecer a +> [Ivan Ogasawara](https://github.com/xmnlab) y a +> [Ever Vino](https://github.com/EverVino), por su tiempo y dedicación. Ambos +> son miembros activos de OSL y parte del steering council; estuvieron +> apoyándonos y aclarando nuestras dudas. + +A continuación te contamos sobre nuestra experiencia y la etapa de colaboración. + +## ¿Cómo fue nuestro proceso de aprendizaje? + +SciCookie nos brindó, por primera vez, la oportunidad de hacer una contribución +tan significativa en un proyecto de ciencia abierta y open source. También nos +permitió adquirir nuevos conocimientos sobre algunos aspectos y tecnologías +ligadas al lenguaje de programación Python, ya que en ese momento, nuestros +conocimientos estaban más orientados al uso de bibliotecas, objetos, bucles, +entre otros. + +Sobre este proceso de aprendizaje podemos decirte que desconocíamos muchas cosas +y fue necesario aprender en el camino, en ocasiones esto se nos tornó un poco +desafiante pero, en general, muy provechoso. Entre las anécdotas que rescatamos +está que, un par de veces, nos “explotó” el código y no sabíamos el porqué; la +causa era que no sabíamos el uso exacto de comillas simples o dobles, doble +llaves, espacios o tabulaciones dentro de la plantilla. Pero ya luego pudimos +avanzar e incluso hicimos mejoras en el workflow del proyecto. + +Con relación a esto último, sin duda podemos comentarte que las curvas de +aprendizaje siempre son empinadas. Al principio ves todo cuesta arriba, pero +cuando ya estás familiarizado con la tecnología y las herramientas, todo se +vuelve más fácil. Atreverse siempre es el primer paso. + +Por otro lado, si te interesa colaborar en proyectos de código abierto, es vital +tener conocimientos básicos de herramientas de control de versiones Git y +GitHub, y entender sus comandos esenciales como git pull, git push, git rebase, +git log, git stash, entre otros. También puedes necesitar conocimientos sobre +conda y poetry. Nosotras además de esto, aprendimos un poco de jinja2 y make, y +repasamos conocimientos de creación de funciones, evaluación de condicionales, +workflow de GitHub, aspectos de documentación y algunas tecnologías asociadas a +esto. + +En resumen, la experiencia de solicitar y ejecutar una subvención de la PSF fue +una experiencia valiosa. Aprendimos mucho sobre el proceso, cómo desarrollar y +mantener una herramienta de Python, qué estructura debe tener un proyecto de +biblioteca o paquete Python y cómo construir una comunidad alrededor de un +proyecto de código abierto. También estamos agradecidos por el apoyo de la PSF, +que nos ha permitido hacer un aporte a SciCookie. Nos sentimos satisfechas del +trabajo que hemos realizado y estamos emocionadas por el futuro de esta +herramienta. + +A todo lo que te hemos comentado, le sumamos una invitación a que colabores en +proyectos de código abierto o ciencia abierta y, si ya lo has hecho, te animamos +a que continúes haciéndolo. A nosotras muchas veces nos motivaba el hecho de ver +nuestros PRs siendo aprobados, compartimos sentimientos de logro y nuevos retos +y, lo más importante es que estuvimos aplicando lo que promueve el open source o +código abierto: pequeñas colaboraciones hacen grandes cambios y van sumando a +los proyectos, logrando buenos y útiles resultados. + +Luego de todo esto, quizá te puedas preguntar sobre las barreras en la +colaboración. Dedicamos las siguientes líneas para describir lo que rescatamos +de nuestra experiencia. + +## ¿Puedes encontrar barreras en la colaboración? + +El progreso de tus contribuciones depende de ti. Es vital preguntar y no +estancarse con las dudas. A menudo hay alguien que puede mostrarte que el +problema que considerabas grande simplemente era algo pequeño, tal vez el código +no funcionaba porque eran comillas simples en lugar de comillas dobles, por +ejemplo. + +De la comunidad de OSL podemos destacar que se centra en crear espacios +amigables y llenos de oportunidades en los que puedas compartir y adquirir +nuevos conocimientos, eliminando barreras y la discriminación. Quizá puedes +encontrar estas mismas características en otros proyectos de ciencia abierta y/o +código abierto. + +Por eso te queremos invitar nuevamente a apoyar y unirte a la diversa comunidad +de Python y del open source, es una excelente experiencia y el hecho de +contribuir en algo que puede ser útil a otras personas es bastante +satisfactorio. + +En general, colaborar en proyectos de código abierto es una insuperable manera +de mejorar tus habilidades de programación, también tienes la oportunidad de +trabajar con otros desarrolladores y aprender de ellos, recibir +retroalimentación sobre tu trabajo. Si deseas apoyar o darle un impulso a tu +proyecto, lo primero que debes hacer es empezar. Muchas comunidades están +abiertas a nuevos aportes e ideas innovadoras. + +Déjanos tus comentarios si deseas conocer más detalles sobre lo que te hemos +contado en este espacio :D + +[1] **Nota adicional**: SciCookie originalmente llevaba por nombre +cookiecutter-python y luego pasó a ser osl-python-template. + +Elementos gráficos de la portada fueron extraídos de +[Work illustrations by Storyset](https://storyset.com/work), y luego editados +para adaptarlos al artículo. diff --git a/pages/blog/scaling-machine-learning-projects-with-dask/index.ipynb b/pages/blog/scaling-machine-learning-projects-with-dask/index.ipynb deleted file mode 100644 index f44c83861..000000000 --- a/pages/blog/scaling-machine-learning-projects-with-dask/index.ipynb +++ /dev/null @@ -1,338 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "id": "3b2f4318-ba2d-40ae-9783-47a934627807", - "metadata": {}, - "source": [ - "---\n", - "title: Scaling Machine Learning Projects with Dask\n", - "slug: scaling-machine-learning-projects-with-dask\n", - "date: 2024-01-30\n", - "authors:\n", - " - Satarupa Deb\n", - "tags:\n", - " - open-source\n", - " - Machine Learning\n", - " - Dask\n", - " - python\n", - "categories:\n", - " - Python\n", - " - Machine Learning\n", - " - Parallel computing\n", - "description: |\n", - " This blog explores the usability of Dask in handling significant\n", - " challenges related to scaling models with large datasets, training and testing\n", - " of models, and implementing parallel computing functionalities. It also\n", - " provides a brief overview of the basic features of Dask.\n", - "thumbnail: /image.png\n", - "template: blog-post.html\n", - "\n", - "---" - ] - }, - { - "cell_type": "markdown", - "id": "d900c1a1-1613-4910-9e10-96c39fabf8dc", - "metadata": {}, - "source": [ - "# Scaling Python Data Analysis with Dask\n", - "\n", - "As the volume of digital data continues to expand, coupled with the emergence of new machine learning models each day, companies are increasingly dependent on data analysis to inform business decisions. To effectively test and train these models with large datasets, scaling becomes a significant challenge, particularly in connecting Python analysts to distributed hardware. This challenge is particularly pronounced in the realm of data science and machine learning workloads. The complexities in this process often result in discrepancies that can lead to flawed training of data and consequently, inaccurate results.\n", - "\n", - "In this blog, we will suggest an effective solution to address the challenges discussed above. Imagine how much easier the scaling process would be with a Python library that could perform on both parallel and distributed computing. This is precisely what **Dask** does!\n", - "\n", - "## What is Dask?\n", - "\n", - "**Dask** is an open-source, parallel and distributed computing library in Python that facilitates efficient and scalable processing of large datasets. It is designed to seamlessly integrate with existing Python libraries and tools, providing a familiar interface for users already comfortable with Python and its libraries like NumPy, Pandas, Jupyter, Scikit-Learn, and others but want to scale those workloads across a cluster. Dask is particularly useful for working with larger-than-memory datasets, parallelizing computations, and handling distributed computing.\n", - "\n", - "## Setting Up Dask\n", - "\n", - "Installing Dask is straightforward and can be done using Conda or Pip. For Anaconda users, Dask comes pre-installed, highlighting its popularity in the data science community. Alternatively, you can install Dask via Pip, ensuring to include the complete extension to install all required dependencies automatically.\n", - "\n", - "```bash\n", - "#install using conda\n", - "conda install dask\n", - "```\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "1269bbbf-a5e4-43b8-af7e-a2ad8a97c732", - "metadata": {}, - "outputs": [], - "source": [ - "#install using conda\n", - "!pip install \"dask[complete]\" -q" - ] - }, - { - "cell_type": "markdown", - "id": "f139ceb9-73e5-40f6-a978-038e89917c3b", - "metadata": {}, - "source": [ - "## Basic Concepts of Dask\n", - "\n", - "At its core, Dask extends the capabilities of traditional tools like pandas, NumPy, and Spark to handle larger-than-memory datasets. It achieves this by breaking large objects like arrays and dataframes into smaller, manageable chunks or partitions. This approach allows Dask to distribute computations efficiently across all available cores on your machine.\n", - "\n", - "## Dask DataFrames\n", - "\n", - "One of the standout features of Dask is its ability to handle large datasets effortlessly. With Dask DataFrames, you can seamlessly work with datasets exceeding 1 GB in size. By breaking the dataset into smaller chunks, Dask ensures efficient processing while maintaining the familiar interface of pandas DataFrames.\n", - "\n", - "## Features of Dask:\n", - "\n", - "1. **Parallel and Distributed Computing:**\n", - " Dask enables parallel and distributed computing, making it a go-to solution for handling datasets that exceed the available memory of a single machine. It breaks down computations into smaller tasks, allowing for concurrent execution and optimal resource utilization." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "11414276-66ff-4bba-a8e0-e8315d49038f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ 9986.14978723 10073.19700192 9985.6576724 ... 9923.550924\n", - " 9978.70237439 9990.8504103 ]\n" - ] - } - ], - "source": [ - "#demonstrating parallel and distributed computing using Dask\n", - "import dask.array as da\n", - "\n", - "# Create a large random array\n", - "x = da.random.random((10000, 10000), chunks=(1000, 1000)) # 10,000 x 10,000 array\n", - "\n", - "# Perform element-wise computation\n", - "y = x * 2\n", - "\n", - "# Compute the sum along one axis\n", - "z = y.sum(axis=0)\n", - "\n", - "# Compute the result in parallel across multiple cores or distributed across a cluster\n", - "result = z.compute()\n", - "\n", - "print(result)\n" - ] - }, - { - "cell_type": "markdown", - "id": "5ef1efce-6001-4895-8ae1-395f8a199c3f", - "metadata": {}, - "source": [ - "2. **Dask Collections:**\n", - " Dask provides high-level abstractions known as Dask collections, which are parallel and distributed counterparts to familiar Python data structures. These include `dask.array` for parallel arrays, `dask.bag` for parallel bags, and `dask.dataframe` for parallel dataframes, seamlessly integrating with existing Python libraries." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "0e29dd78-875c-4aea-91fb-19f2aaf1cb4a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2.0\n" - ] - } - ], - "source": [ - "#Using dask collections\n", - "import dask.array as da\n", - "\n", - "# Creating a dummy dataset using Dask\n", - "x = da.ones((100, 100), chunks=(10, 10)) # Creating a 100x100 array of ones with chunks of 10x10\n", - "y = x + x.T # Adding the transpose of x to itself\n", - "result = y.mean() # Calculating the mean of y\n", - "\n", - "# Computing the result\n", - "print(result.compute()) # Outputting the computed result\n" - ] - }, - { - "cell_type": "markdown", - "id": "89052ee6-ca13-47d2-bb7d-c415e42a9a17", - "metadata": {}, - "source": [ - "3. **Lazy Evaluation:**\n", - " One of Dask's core principles is lazy evaluation. Instead of immediately computing results, Dask builds a task graph representing the computation. The actual computation occurs only when the results are explicitly requested. This approach enhances efficiency and allows for optimizations in resource usage.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "137b40a2-9c56-4834-bc14-c8cd5c89a244", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.5112260135512784\n" - ] - } - ], - "source": [ - "#Lazy Evalution with dask\n", - "import dask.dataframe as dd\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "# Creating a dummy dataset\n", - "num_rows = 100 # Number of rows\n", - "data = {\n", - " 'column': np.random.randint(0, 100, size=num_rows),\n", - " 'value': np.random.rand(num_rows)\n", - "}\n", - "\n", - "# Creating a Pandas DataFrame\n", - "df_pandas = pd.DataFrame(data)\n", - "\n", - "# Saving the Pandas DataFrame to a CSV file\n", - "df_pandas.to_csv('your_dataset.csv', index=False)\n", - "\n", - "# Reading the CSV file into a Dask DataFrame\n", - "df = dd.read_csv('your_dataset.csv')\n", - "\n", - "# Filtering the Dask DataFrame\n", - "filtered_df = df[df['column'] > 10]\n", - "\n", - "# Calculating the mean of the filtered DataFrame\n", - "mean_result = filtered_df['value'].mean()\n", - "\n", - "# No computation happens until explicitly requested\n", - "print(mean_result.compute()) # Outputting the computed result\n" - ] - }, - { - "cell_type": "markdown", - "id": "2d2800b0-5ac3-429b-8e15-bec442f45df3", - "metadata": {}, - "source": [ - "4. **Integration with Existing Libraries:**\n", - " Dask is designed to integrate seamlessly with popular Python libraries, such as NumPy, Pandas, and scikit-learn. This means that you can often replace existing code with Dask equivalents without significant modifications." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "c3e4cc2e-f84f-4033-af96-f5d68bf0d5c1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[1.40940907 1.32044698 1.48172367 ... 1.4266846 0.84142743 0.33577001]\n", - " [1.32044698 1.02252065 1.17250384 ... 0.40216939 1.58544767 1.12049071]\n", - " [1.48172367 1.17250384 1.98886224 ... 0.86271956 1.27977778 0.95136532]\n", - " ...\n", - " [1.4266846 0.40216939 0.86271956 ... 1.44980096 1.38712404 0.75331149]\n", - " [0.84142743 1.58544767 1.27977778 ... 1.38712404 1.50814693 1.01719649]\n", - " [0.33577001 1.12049071 0.95136532 ... 0.75331149 1.01719649 1.47050452]]\n" - ] - } - ], - "source": [ - "# Integration with NumPy\n", - "import dask.array as da\n", - "import numpy as np\n", - "\n", - "# Generating a random NumPy array\n", - "x_np = np.random.random((100, 100))\n", - "\n", - "# Converting the NumPy array to a Dask array\n", - "x_dask = da.from_array(x_np, chunks=(10, 10))\n", - "\n", - "# Performing operations on the Dask array\n", - "y_dask = x_dask + x_dask.T\n", - "\n", - "# Computing the result\n", - "print(y_dask.compute())\n" - ] - }, - { - "cell_type": "markdown", - "id": "a6bfffe1-fa8f-46fd-b734-248fd1631df6", - "metadata": {}, - "source": [ - "5. **Task Scheduling:**\n", - " Dask dynamically schedules the execution of tasks, optimizing the computation based on available resources. This makes it well-suited for handling larger-than-memory datasets efficiently. Dask is a powerful tool for data scientists and engineers working with large-scale data processing tasks, providing a convenient way to scale computations without requiring a complete rewrite of existing code.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "2356c4a1-2c78-426d-9019-127cd754ec62", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(1, 4, 9, 16, 25, 36, 49, 64, 81)\n" - ] - } - ], - "source": [ - "# Dynamic task scheduling with Dask\n", - "import dask\n", - "\n", - "\n", - "@dask.delayed\n", - "def square(x):\n", - " return x * x\n", - "\n", - "data = [1, 2, 3, 4, 5, 6, 7, 8, 9]\n", - "results = []\n", - "\n", - "for value in data:\n", - " result = square(value)\n", - " results.append(result)\n", - "\n", - "final_result = dask.compute(*results)\n", - "print(final_result)\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "6dbaf782-33c5-4956-a332-2620a49bd554", - "metadata": {}, - "source": [ - "## Conclusion:\n", - "\n", - "**Dask** stands as a powerful tool in the Python ecosystem, addressing the challenges posed by the ever-increasing scale of data. Its ability to seamlessly integrate with existing libraries, support lazy evaluation, and provide parallel and distributed computing makes it a valuable asset for data scientists and engineers tackling large-scale data processing tasks. Whether you're working on a single machine with moderately sized datasets or dealing with big data challenges that require distributed computing, Dask offers a flexible and efficient solution. As we continue to navigate the era of big data, Dask proves to be a key player in unlocking the full potential of Python for scalable and parallelized data processing. Start harnessing the power of Dask today and supercharge your data processing workflows!\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pages/blog/scaling-machine-learning-projects-with-dask/index.qmd b/pages/blog/scaling-machine-learning-projects-with-dask/index.qmd new file mode 100644 index 000000000..b74195585 --- /dev/null +++ b/pages/blog/scaling-machine-learning-projects-with-dask/index.qmd @@ -0,0 +1,217 @@ +--- +title: Scaling Machine Learning Projects with Dask +slug: scaling-machine-learning-projects-with-dask +date: 2024-01-30 +authors: + - Satarupa Deb +tags: + - open-source + - Machine Learning + - Dask + - python +categories: + - Python + - Machine Learning + - Parallel computing +description: | + This blog explores the usability of Dask in handling significant + challenges related to scaling models with large datasets, training and testing + of models, and implementing parallel computing functionalities. It also + provides a brief overview of the basic features of Dask. +thumbnail: /image.png +template: blog-post.html + +--- +# Scaling Python Data Analysis with Dask + +As the volume of digital data continues to expand, coupled with the emergence of new machine learning models each day, companies are increasingly dependent on data analysis to inform business decisions. To effectively test and train these models with large datasets, scaling becomes a significant challenge, particularly in connecting Python analysts to distributed hardware. This challenge is particularly pronounced in the realm of data science and machine learning workloads. The complexities in this process often result in discrepancies that can lead to flawed training of data and consequently, inaccurate results. + +In this blog, we will suggest an effective solution to address the challenges discussed above. Imagine how much easier the scaling process would be with a Python library that could perform on both parallel and distributed computing. This is precisely what **Dask** does! + +## What is Dask? + +**Dask** is an open-source, parallel and distributed computing library in Python that facilitates efficient and scalable processing of large datasets. It is designed to seamlessly integrate with existing Python libraries and tools, providing a familiar interface for users already comfortable with Python and its libraries like NumPy, Pandas, Jupyter, Scikit-Learn, and others but want to scale those workloads across a cluster. Dask is particularly useful for working with larger-than-memory datasets, parallelizing computations, and handling distributed computing. + +## Setting Up Dask + +Installing Dask is straightforward and can be done using Conda or Pip. For Anaconda users, Dask comes pre-installed, highlighting its popularity in the data science community. Alternatively, you can install Dask via Pip, ensuring to include the complete extension to install all required dependencies automatically. + +```bash +#install using conda +conda install dask +``` + + + +```python +#install using conda +!pip install "dask[complete]" -q +``` + +## Basic Concepts of Dask + +At its core, Dask extends the capabilities of traditional tools like pandas, NumPy, and Spark to handle larger-than-memory datasets. It achieves this by breaking large objects like arrays and dataframes into smaller, manageable chunks or partitions. This approach allows Dask to distribute computations efficiently across all available cores on your machine. + +## Dask DataFrames + +One of the standout features of Dask is its ability to handle large datasets effortlessly. With Dask DataFrames, you can seamlessly work with datasets exceeding 1 GB in size. By breaking the dataset into smaller chunks, Dask ensures efficient processing while maintaining the familiar interface of pandas DataFrames. + +## Features of Dask: + +1. **Parallel and Distributed Computing:** + Dask enables parallel and distributed computing, making it a go-to solution for handling datasets that exceed the available memory of a single machine. It breaks down computations into smaller tasks, allowing for concurrent execution and optimal resource utilization. + + +```python +#demonstrating parallel and distributed computing using Dask +import dask.array as da + +# Create a large random array +x = da.random.random((10000, 10000), chunks=(1000, 1000)) # 10,000 x 10,000 array + +# Perform element-wise computation +y = x * 2 + +# Compute the sum along one axis +z = y.sum(axis=0) + +# Compute the result in parallel across multiple cores or distributed across a cluster +result = z.compute() + +print(result) + +``` + + [ 9986.14978723 10073.19700192 9985.6576724 ... 9923.550924 + 9978.70237439 9990.8504103 ] + + +2. **Dask Collections:** + Dask provides high-level abstractions known as Dask collections, which are parallel and distributed counterparts to familiar Python data structures. These include `dask.array` for parallel arrays, `dask.bag` for parallel bags, and `dask.dataframe` for parallel dataframes, seamlessly integrating with existing Python libraries. + + +```python +#Using dask collections +import dask.array as da + +# Creating a dummy dataset using Dask +x = da.ones((100, 100), chunks=(10, 10)) # Creating a 100x100 array of ones with chunks of 10x10 +y = x + x.T # Adding the transpose of x to itself +result = y.mean() # Calculating the mean of y + +# Computing the result +print(result.compute()) # Outputting the computed result + +``` + + 2.0 + + +3. **Lazy Evaluation:** + One of Dask's core principles is lazy evaluation. Instead of immediately computing results, Dask builds a task graph representing the computation. The actual computation occurs only when the results are explicitly requested. This approach enhances efficiency and allows for optimizations in resource usage. + + + + +```python +#Lazy Evalution with dask +import dask.dataframe as dd +import numpy as np +import pandas as pd + +# Creating a dummy dataset +num_rows = 100 # Number of rows +data = { + 'column': np.random.randint(0, 100, size=num_rows), + 'value': np.random.rand(num_rows) +} + +# Creating a Pandas DataFrame +df_pandas = pd.DataFrame(data) + +# Saving the Pandas DataFrame to a CSV file +df_pandas.to_csv('your_dataset.csv', index=False) + +# Reading the CSV file into a Dask DataFrame +df = dd.read_csv('your_dataset.csv') + +# Filtering the Dask DataFrame +filtered_df = df[df['column'] > 10] + +# Calculating the mean of the filtered DataFrame +mean_result = filtered_df['value'].mean() + +# No computation happens until explicitly requested +print(mean_result.compute()) # Outputting the computed result + +``` + + 0.5112260135512784 + + +4. **Integration with Existing Libraries:** + Dask is designed to integrate seamlessly with popular Python libraries, such as NumPy, Pandas, and scikit-learn. This means that you can often replace existing code with Dask equivalents without significant modifications. + + +```python +# Integration with NumPy +import dask.array as da +import numpy as np + +# Generating a random NumPy array +x_np = np.random.random((100, 100)) + +# Converting the NumPy array to a Dask array +x_dask = da.from_array(x_np, chunks=(10, 10)) + +# Performing operations on the Dask array +y_dask = x_dask + x_dask.T + +# Computing the result +print(y_dask.compute()) + +``` + + [[1.40940907 1.32044698 1.48172367 ... 1.4266846 0.84142743 0.33577001] + [1.32044698 1.02252065 1.17250384 ... 0.40216939 1.58544767 1.12049071] + [1.48172367 1.17250384 1.98886224 ... 0.86271956 1.27977778 0.95136532] + ... + [1.4266846 0.40216939 0.86271956 ... 1.44980096 1.38712404 0.75331149] + [0.84142743 1.58544767 1.27977778 ... 1.38712404 1.50814693 1.01719649] + [0.33577001 1.12049071 0.95136532 ... 0.75331149 1.01719649 1.47050452]] + + +5. **Task Scheduling:** + Dask dynamically schedules the execution of tasks, optimizing the computation based on available resources. This makes it well-suited for handling larger-than-memory datasets efficiently. Dask is a powerful tool for data scientists and engineers working with large-scale data processing tasks, providing a convenient way to scale computations without requiring a complete rewrite of existing code. + + + +```python +# Dynamic task scheduling with Dask +import dask + + +@dask.delayed +def square(x): + return x * x + +data = [1, 2, 3, 4, 5, 6, 7, 8, 9] +results = [] + +for value in data: + result = square(value) + results.append(result) + +final_result = dask.compute(*results) +print(final_result) + + +``` + + (1, 4, 9, 16, 25, 36, 49, 64, 81) + + +## Conclusion: + +**Dask** stands as a powerful tool in the Python ecosystem, addressing the challenges posed by the ever-increasing scale of data. Its ability to seamlessly integrate with existing libraries, support lazy evaluation, and provide parallel and distributed computing makes it a valuable asset for data scientists and engineers tackling large-scale data processing tasks. Whether you're working on a single machine with moderately sized datasets or dealing with big data challenges that require distributed computing, Dask offers a flexible and efficient solution. As we continue to navigate the era of big data, Dask proves to be a key player in unlocking the full potential of Python for scalable and parallelized data processing. Start harnessing the power of Dask today and supercharge your data processing workflows! + diff --git a/pages/blog/scicookie-collaborating-and-learning/index.ipynb b/pages/blog/scicookie-collaborating-and-learning/index.ipynb deleted file mode 100644 index ffaad51ed..000000000 --- a/pages/blog/scicookie-collaborating-and-learning/index.ipynb +++ /dev/null @@ -1,96 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "---\n", - "title: \"Collaborating and learning from SciCookie\"\n", - "slug: scicookie-collaborating-and-learning\n", - "date: 2024-02-03\n", - "authors: [\"Daniela Iglesias Rocabado\"]\n", - "tags: [open-source, open-science, python, proyects]\n", - "categories: [python]\n", - "description: |\n", - " The SciCookie template, developed by Open Science Labs, is a Python package based on the Cookieninja A Cookiecutter Fork\n", - " command-line utility. Serving as a versatile boilerplate, it simplifies project creation for both beginners and experienced\n", - " developers, saving significant time. Derived from ongoing research on scientific Python tools and best practices, SciCookie\n", - " aligns with PyOpenSci recommendations, providing a standardized starting point for projects that can be easily customized\n", - " while adhering to industry standards.\n", - "thumbnail: \"/header.jpeg\"\n", - "template: \"blog-post.html\"\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# SciCookie\n", - "\n", - "The SciCookie template, developed by Open Science Labs, is a project template tool package, that uses Cookieninja as a backend, a Cookiecutter fork. Functioning as a versatile boilerplate, it facilitates project creation for both beginners and experienced developers, streamlining the process and saving considerable time. SciCookie provides an initial project layout with recommended tools, workflows, and structure. Additionally, it incorporates features like automatic documentation generation, automated testing, and project-specific configuration to enhance the development workflow. The template is aligned with PyOpenSci recommendations, derived from ongoing research on tools, libraries, best practices, and workflows in scientific Python. As a result, SciCookie offers authors a standardized starting point for projects that can be easily adjusted to meet specific requirements while maintaining industry standards.\n", - "\n", - "\n", - "## Benefits of using SciCookie\n", - "\n", - "- **Organized Workflow:** Utilizing the SciCookie template allows for maintaining an organized workflow. By configuring elements such as project design, build system, command-line interface, and documentation engine, the development process is streamlined, ensuring a coherent and organized framework for the project.\n", - "- **Enhanced Project Tools:** SciCookie provides the flexibility to choose from various tools that can enhance the project. These tools automate tasks, ensure consistent code formatting, and identify errors and vulnerabilities.\n", - "- **DevOps Integration:** Another significant benefit is the seamless integration of the Python project with DevOps tools. These tools automate and optimize the development process, from code status to completion and maintenance." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## How to use SciCookie?\n", - "\n", - "### Quickstart\n", - "\n", - ">**Note:**\n", - "Navigate to the folder where you want to create your project.\n", - "\n", - "\n", - "```bash\n", - "$ pip install scicookie\n", - "```\n", - "\n", - "Once you have entered your folder, you need to generate a Python package project:\n", - "\n", - "```bash\n", - "$ scicookie\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## SciCookie profiles\n", - "\n", - "SciCookie enables the community to create different profiles within SciCookie's platform tailored to the standards and preferences of each group or working community. Currently, only the OSL profile is available, but it is open to the community to submit pull requests with specific profiles. Each profile offers different configuration options to choose required tools, define titles for specific messages, and also set titles for documentation and help URLs.\n", - "\n", - "```bash\n", - "$ scicookie --profile osl\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Demo Video\n", - "\n", - "For a better explanation, please watch a demonstrative video of the installation of SciCookie and the creation of a project using the OSL profile.\n", - "\n", - "\n" - ] - } - ], - "metadata": { - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pages/blog/scicookie-collaborating-and-learning/index.qmd b/pages/blog/scicookie-collaborating-and-learning/index.qmd new file mode 100644 index 000000000..31d4d4565 --- /dev/null +++ b/pages/blog/scicookie-collaborating-and-learning/index.qmd @@ -0,0 +1,59 @@ +--- +title: "Collaborating and learning from SciCookie" +slug: scicookie-collaborating-and-learning +date: 2024-02-03 +authors: ["Daniela Iglesias Rocabado"] +tags: [open-source, open-science, python, proyects] +categories: [python] +description: | + The SciCookie template, developed by Open Science Labs, is a Python package based on the Cookieninja A Cookiecutter Fork + command-line utility. Serving as a versatile boilerplate, it simplifies project creation for both beginners and experienced + developers, saving significant time. Derived from ongoing research on scientific Python tools and best practices, SciCookie + aligns with PyOpenSci recommendations, providing a standardized starting point for projects that can be easily customized + while adhering to industry standards. +thumbnail: "/header.jpeg" +template: "blog-post.html" +--- +# SciCookie + +The SciCookie template, developed by Open Science Labs, is a project template tool package, that uses Cookieninja as a backend, a Cookiecutter fork. Functioning as a versatile boilerplate, it facilitates project creation for both beginners and experienced developers, streamlining the process and saving considerable time. SciCookie provides an initial project layout with recommended tools, workflows, and structure. Additionally, it incorporates features like automatic documentation generation, automated testing, and project-specific configuration to enhance the development workflow. The template is aligned with PyOpenSci recommendations, derived from ongoing research on tools, libraries, best practices, and workflows in scientific Python. As a result, SciCookie offers authors a standardized starting point for projects that can be easily adjusted to meet specific requirements while maintaining industry standards. + + +## Benefits of using SciCookie + +- **Organized Workflow:** Utilizing the SciCookie template allows for maintaining an organized workflow. By configuring elements such as project design, build system, command-line interface, and documentation engine, the development process is streamlined, ensuring a coherent and organized framework for the project. +- **Enhanced Project Tools:** SciCookie provides the flexibility to choose from various tools that can enhance the project. These tools automate tasks, ensure consistent code formatting, and identify errors and vulnerabilities. +- **DevOps Integration:** Another significant benefit is the seamless integration of the Python project with DevOps tools. These tools automate and optimize the development process, from code status to completion and maintenance. + +## How to use SciCookie? + +### Quickstart + +>**Note:** +Navigate to the folder where you want to create your project. + + +```bash +$ pip install scicookie +``` + +Once you have entered your folder, you need to generate a Python package project: + +```bash +$ scicookie +``` + +## SciCookie profiles + +SciCookie enables the community to create different profiles within SciCookie's platform tailored to the standards and preferences of each group or working community. Currently, only the OSL profile is available, but it is open to the community to submit pull requests with specific profiles. Each profile offers different configuration options to choose required tools, define titles for specific messages, and also set titles for documentation and help URLs. + +```bash +$ scicookie --profile osl +``` + +### Demo Video + +For a better explanation, please watch a demonstrative video of the installation of SciCookie and the creation of a project using the OSL profile. + + + diff --git a/pages/blog/scicookie-recibe-nueva-subvencion-de-psf-para-mejoras-crecimiento/index.qmd b/pages/blog/scicookie-recibe-nueva-subvencion-de-psf-para-mejoras-crecimiento/index.qmd new file mode 100644 index 000000000..bf2c2c179 --- /dev/null +++ b/pages/blog/scicookie-recibe-nueva-subvencion-de-psf-para-mejoras-crecimiento/index.qmd @@ -0,0 +1,50 @@ +--- +title: "SciCookie recibe nueva subvención de PSF para mejoras y crecimiento" +slug: scicookie-recibe-nueva-subvencion-de-psf-para-mejoras-crecimiento +date: 2024-03-01 +authors: ["Anavelyz Perez", "Yurely Camacho"] +tags: + [psf, osl, scicookie, subvención, grant, comunidad, colaboración, desarrollo] +categories: [código abierto, desarrollo de software, python] +description: | + ¡Nos complace anunciar que SciCookie ha recibido una nueva subvención + de la Python Software Foundation (PSF)! +thumbnail: "/header.svg" +template: "blog-post.html" +--- + + + + +¡Nos complace anunciar que SciCookie ha recibido una nueva subvención de la +Python Software Foundation (PSF)! + +En Enero de 2024 la PSF aprobó una nueva subvención para SciCookie, la propuesta +fue enviada a finales de diciembre de 2023, se estará ejecutando entre Febrero y +Abril de 2024, durante este tiempo estaremos realizando las siguientes tareas: + +- Mejorar la documentación de la herramienta: para que sea más clara, completa y +accesible para nuevos usuarios. +- Mejorar aspectos de configuración: para facilitar la instalación y uso de + SciCookie. +- Incorporar y/o actualizar bibliotecas y otras herramientas: para ampliar las + funcionalidades. +- Mejorar la estructura de los archivos: para que el código sea más organizado y +fácil de mantener. +- Aplicar a la revisión por pares de pyOpenSci: para obtener la certificación de +calidad de la herramienta. + + En general, las mejoras que se realizarán con esta nueva subvención permitirán +que SciCookie sea una herramienta aún más poderosa y útil para la comunidad +científica. Esperamos que esto contribuya a que más investigadores y científicos +puedan utilizar SciCookie para sus proyectos, lo que a su vez impulsará el +avance de la ciencia y el código abierto. Estamos muy entusiasmadas con esta +nueva etapa de SciCookie y esperamos poder compartir con ustedes los avances en +próximos posts. Agradecemos a la PSF por su apoyo y a toda la comunidad por su +colaboración. + +Si deseas, puedes echar un vistazo en el repositorio de SciCookie en GitHub +haciendo clic [>aquí<](https://github.com/osl-incubator/scicookie). + +Elementos gráficos de la portada fueron extraídos de +[Work illustrations by Storyset](https://storyset.com/work). diff --git a/pages/blog/streamlining-project-automation-with-makim/index.ipynb b/pages/blog/streamlining-project-automation-with-makim/index.ipynb deleted file mode 100644 index dbca96736..000000000 --- a/pages/blog/streamlining-project-automation-with-makim/index.ipynb +++ /dev/null @@ -1,1016 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "id": "14c8a840-5020-486d-a007-f2d4769dfc69", - "metadata": {}, - "source": [ - "---\n", - "title: \"Streamlining Project Automation with Makim\"\n", - "slug: \"streamlining-project-automation-with-makim\"\n", - "date: 2024-03-18\n", - "authors: [\"Ivan Ogasawara\"]\n", - "tags: [\"makim\", \"automation\", \"devops\", \"open-source\"]\n", - "categories: [\"devops\", \"automation\", \"python\"]\n", - "description: |\n", - " In software development, where efficiency, consistency, and reliability are paramount,\n", - " automation tools play a crucial role. Makim, an innovative open-source tool, steps\n", - " into the spotlight to improve automation workflows. It simplifies script execution,\n", - " environment management, and task dependencies, positioning itself as a great asset in\n", - " modern development environments.\n", - " environment.\n", - "thumbnail: \"/header.png\"\n", - "template: \"blog-post.html\"\n", - "---" - ] - }, - { - "cell_type": "markdown", - "id": "232d0e08-2248-40d7-a1f9-c52bff1b357a", - "metadata": {}, - "source": [ - "# Streamlining Project Automation with Makim\n", - "\n", - "In software development, where efficiency, consistency, and reliability are paramount, automation tools play a crucial role. Makim, an innovative open-source tool, steps into the spotlight to improve automation workflows. It simplifies script execution, environment management, and task dependencies, positioning itself as a great asset in modern development environments.\n", - "\n", - "## Introducing Makim\n", - "\n", - "`Makim` elevates project automation by offering a structured, yet flexible approach to manage routine tasks, complex task dependencies, and environment configurations. Its design is centered around the `.makim.yaml` configuration file, allowing developers to orchestrate their workflows with precision and ease. Unlike traditional script execution tools, Makim's Python-based architecture and support for multiple programming languages and shells enhance its versatility and applicability across diverse projects.\n", - "\n", - "Especially suited for DevOps Engineers and Software Developers, Makim eliminates redundancy in automation tasks. Its core functionality extends beyond simple script execution, encompassing:\n", - "\n", - "- Argument definition for scripts\n", - "- Organization of tasks into groups\n", - "- Advanced dependency management between tasks\n", - "- Utilization of environment variables and custom variables\n", - "- Dynamic content generation with Jinja2 templates\n", - "- Specification of working directories for tasks\n", - "- Execution flexibility through support for multiple interpreters or shells\n", - "\n", - "Despite its broad capabilities, Makim currently lacks support for Windows but plans to extend its compatibility in future versions.\n", - "\n", - "## Getting Started with Makim\n", - "\n", - "### Installation\n", - "\n", - "Makim can be installed via `pip` or `conda`, catering to different setup preferences:\n", - "\n", - "- To install `Makim` using `pip`, run:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "761586e2-98ef-4fc1-a73b-7d9d4e1c8e7e", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -q \"makim==1.14.0\"" - ] - }, - { - "cell_type": "markdown", - "id": "b0af5f25-4807-4637-9036-62fa6f894bef", - "metadata": {}, - "source": [ - "- For those who prefer `conda`, execute:\n", - "\n", - " ```\n", - " conda install \"makim=1.14.0\"\n", - " ```\n", - "\n", - "Given Makim's active development, pinning to a specific version is recommended to ensure consistency.\n", - "\n", - "For this tutorial, we will disable the output color feature provided by typer, the command-line interface engine used by **Makim**." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e6f6a633-79e3-4690-928b-4e74b725c83c", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "\n", - "os.environ[\"NO_COLOR\"] = \"1\"" - ] - }, - { - "cell_type": "markdown", - "id": "4ff221f5-e18b-4b0b-bbb5-4cd8547bf35a", - "metadata": {}, - "source": [ - "### Configuring `.makim.yaml`\n", - "\n", - "The `.makim.yaml` file is the foundation of your Makim configuration. Here's how to start:\n", - "\n", - "1. **Create the `.makim.yaml` File**: Place this file at the root of your project directory.\n", - " \n", - "2. **Define Your Automation Tasks**: Configure your tasks, specifying actions, arguments, and dependencies. For example:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "dd79bbd0-73db-4d25-ae06-a143aef3b8e1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting .makim.yaml\n" - ] - } - ], - "source": [ - "%%writefile .makim.yaml\n", - "version: 1.0.0\n", - "groups:\n", - " clean:\n", - " env-file: .env\n", - " targets:\n", - " tmp:\n", - " help: Use this target to clean up temporary files\n", - " run: |\n", - " echo \"Cleaning up...\"\n", - " tests:\n", - " targets:\n", - " unit:\n", - " help: Build the program\n", - " args:\n", - " clean:\n", - " type: bool\n", - " action: store_true\n", - " help: if not set, the clean dependency will not be triggered.\n", - " dependencies:\n", - " - target: clean.tmp\n", - " if: ${{ args.clean == true }}\n", - " run: |\n", - " echo \"Runnint unit tests...\"" - ] - }, - { - "cell_type": "markdown", - "id": "7bb49d00-0f25-4679-97ce-2bd2ecfa6e3c", - "metadata": {}, - "source": [ - "This setup demonstrates Makim's ability to manage tasks with conditional logic and dependencies.\n", - "\n", - "### Exploring Makim's CLI\n", - "\n", - "Makim's CLI provides insights into available commands, arguments, and configurations through the auto-generated help menu:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "5e17c7ab-a183-40d9-bc1a-96ff8be6a91b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1m \u001b[0m\n", - "\u001b[1m \u001b[0m\u001b[1mUsage: \u001b[0m\u001b[1mmakim [OPTIONS] COMMAND [ARGS]...\u001b[0m\u001b[1m \u001b[0m\u001b[1m \u001b[0m\n", - "\u001b[1m \u001b[0m\n", - " Makim is a tool that helps you to organize and simplify your helper commands. \n", - " \n", - "\u001b[2m╭─\u001b[0m\u001b[2m Options \u001b[0m\u001b[2m───────────────────────────────────────────────────────────────────\u001b[0m\u001b[2m─╮\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-version\u001b[0m \u001b[1m-v\u001b[0m \u001b[1m \u001b[0m Show the version and exit \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-file\u001b[0m \u001b[1mTEXT\u001b[0m Makim config file \u001b[2m[default: .makim.yaml]\u001b[0m \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-dry\u001b[0m\u001b[1m-run\u001b[0m \u001b[1m \u001b[0m Execute the command in dry mode \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-verbose\u001b[0m \u001b[1m \u001b[0m Execute the command in verbose mode \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-install\u001b[0m\u001b[1m-completion\u001b[0m \u001b[1m \u001b[0m Install completion for the current \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m shell. \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-show\u001b[0m\u001b[1m-completion\u001b[0m \u001b[1m \u001b[0m Show completion for the current shell, \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m to copy it or customize the \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m installation. \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-help\u001b[0m \u001b[1m \u001b[0m Show this message and exit. \u001b[2m│\u001b[0m\n", - "\u001b[2m╰──────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n", - "\u001b[2m╭─\u001b[0m\u001b[2m Commands \u001b[0m\u001b[2m──────────────────────────────────────────────────────────────────\u001b[0m\u001b[2m─╮\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1mclean.tmp \u001b[0m\u001b[1m \u001b[0m Use this target to clean up temporary files \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1mtests.unit \u001b[0m\u001b[1m \u001b[0m Build the program \u001b[2m│\u001b[0m\n", - "\u001b[2m╰──────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n", - " \n", - " If you have any problem, open an issue at: \n", - " https://github.com/osl-incubator/makim \n", - " \n", - "\n" - ] - } - ], - "source": [ - "!makim --help" - ] - }, - { - "cell_type": "markdown", - "id": "be6b96d3-79f8-48e2-8e9e-edeb38a4dc58", - "metadata": {}, - "source": [ - "This feature facilitates easy access to Makim's functionalities, enhancing usability and understanding of the tool.\n", - "\n", - "### Executing Your First Commands\n", - "\n", - "With your `.makim.yaml` file set up, you can begin to use `makim`:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "535b89a1-cb6c-48ce-b4d3-2a9296c97f2b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Makim file: .makim.yaml\n", - "Cleaning up...\n" - ] - } - ], - "source": [ - "!makim clean.tmp" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "6c997aeb-eed5-4801-8bfb-dad26bb72dcb", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Makim file: .makim.yaml\n", - "Runnint unit tests...\n" - ] - } - ], - "source": [ - "!makim tests.unit" - ] - }, - { - "cell_type": "markdown", - "id": "48ed3970-3854-49cb-8109-53a38a72d8d4", - "metadata": {}, - "source": [ - "In the case you type your command wrong, **Makim** will suggest you some alternative:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "800ad2a4-0c45-4b1a-b4ef-927ab1230e60", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Usage: makim [OPTIONS] COMMAND [ARGS]...\n", - "\u001b[2mTry \u001b[0m\u001b[2m'makim \u001b[0m\u001b[1;2m-\u001b[0m\u001b[1;2m-help\u001b[0m\u001b[2m'\u001b[0m\u001b[2m for help.\u001b[0m\n", - "╭─ Error ──────────────────────────────────────────────────────────────────────╮\n", - "│ No such command 'tests.unittest'. │\n", - "╰──────────────────────────────────────────────────────────────────────────────╯\n", - "\u001b[31mCommand tests.unittest not found. Did you mean tests.unit'?\u001b[0m\n" - ] - } - ], - "source": [ - "!makim tests.unittest" - ] - }, - { - "cell_type": "markdown", - "id": "aae58d63-d028-47b1-a57e-fb21fc3f9216", - "metadata": {}, - "source": [ - "**Makim** CLI is empowered by **Typer**, and it allows us to have auto-completion for Makim groups and targets! If you want to install, you can run the following command:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "5839d8fb-0a4c-43bf-8b11-1a1ab36cc178", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[32mbash completion installed in /home/xmn/.bash_completions/makim.sh\u001b[0m\n", - "Completion will take effect once you restart the terminal\n" - ] - } - ], - "source": [ - "!makim --install-completion " - ] - }, - { - "cell_type": "markdown", - "id": "962a1698-31b4-4223-b08d-e065d5463bd9", - "metadata": {}, - "source": [ - "After this command you will need to restart the terminal in order to use this auto-completion feature." - ] - }, - { - "cell_type": "markdown", - "id": "2cc6ebb1-304b-4aab-b5f8-ca72289e1815", - "metadata": {}, - "source": [ - "## Advanced Features and Examples\n", - "\n", - "Makim's adaptability is showcased through various features and practical examples:\n", - "\n", - "- **Conditional Dependencies and Arguments**: Define complex task dependencies with conditional execution based on passed arguments.\n", - "- **Dynamic Configuration with Jinja2**: Leverage Jinja2 templates for advanced scripting and dynamic content generation.\n", - "- **Environment and Custom Variable Management**: Organize and utilize variables effectively across different scopes of your project.\n", - "- **Specifying Working Directories**: Control the execution context of your tasks by setting working directories.\n", - "\n", - "These examples underscore Makim's capability to accommodate intricate automation scenarios, streamlining development workflows.\n", - "\n", - "## Exploring Makim Through Examples\n", - "\n", - "### Utilizing Various Interpreters\n", - "\n", - "Makim extends its functionality beyond conventional script execution by supporting various interpreters and shell languages, facilitating a versatile development environment. While **xonsh** is the default interpreter - blending the capabilities of Bash and Python for an enriched command-line experience - Makim's architecture allows for seamless integration with other environments. For developers seeking to leverage this feature, a foundational understanding of **xonsh** can be beneficial. Comprehensive details and usage guidelines are available in the [official xonsh documentation](https://xon.sh/).\n", - "\n", - "This section demonstrates executing straightforward commands across multiple interpreters, showcasing Makim's adaptability to diverse programming contexts." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "0d942c28-d13a-4131-b5e4-113667a69c3e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting .env\n" - ] - } - ], - "source": [ - "%%writefile .env\n", - "MSG_PREFIX=\"Running Makim: Hello, World,\"" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "f68e8008-f0f1-4ead-a42e-4e524fc03c22", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting .makim.yaml\n" - ] - } - ], - "source": [ - "%%writefile .makim.yaml\n", - "version: 1.0\n", - "env-file: .env\n", - "groups:\n", - " tests:\n", - " targets:\n", - " node:\n", - " help: Test using nodejs\n", - " shell: node\n", - " run: console.log(\"${{ env.MSG_PREFIX }} from NodeJS!\");\n", - " perl:\n", - " help: Test using perl\n", - " shell: perl\n", - " run: print \"${{ env.MSG_PREFIX }} from Perl!\\n\";\n", - "\n", - " python:\n", - " help: Test using php\n", - " shell: python\n", - " run: print(\"${{ env.MSG_PREFIX }} from Python!\")\n", - "\n", - " r:\n", - " help: Test using R\n", - " shell: Rscript\n", - " run: print(\"${{ env.MSG_PREFIX }} from R!\")\n", - "\n", - " sh:\n", - " help: Test using sh\n", - " shell: sh\n", - " run: echo \"${{ env.MSG_PREFIX }} from sh!\"\n", - "\n", - " run-all:\n", - " help: Run tests for all the other targets\n", - " dependencies:\n", - " - target: node\n", - " - target: perl\n", - " - target: python\n", - " - target: r\n", - " - target: sh" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "ce809dd8-7db4-4c56-8622-ec3411bad4cf", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1m \u001b[0m\n", - "\u001b[1m \u001b[0m\u001b[1mUsage: \u001b[0m\u001b[1mmakim [OPTIONS] COMMAND [ARGS]...\u001b[0m\u001b[1m \u001b[0m\u001b[1m \u001b[0m\n", - "\u001b[1m \u001b[0m\n", - " Makim is a tool that helps you to organize and simplify your helper commands. \n", - " \n", - "\u001b[2m╭─\u001b[0m\u001b[2m Options \u001b[0m\u001b[2m───────────────────────────────────────────────────────────────────\u001b[0m\u001b[2m─╮\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-version\u001b[0m \u001b[1m-v\u001b[0m \u001b[1m \u001b[0m Show the version and exit \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-file\u001b[0m \u001b[1mTEXT\u001b[0m Makim config file \u001b[2m[default: .makim.yaml]\u001b[0m \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-dry\u001b[0m\u001b[1m-run\u001b[0m \u001b[1m \u001b[0m Execute the command in dry mode \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-verbose\u001b[0m \u001b[1m \u001b[0m Execute the command in verbose mode \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-install\u001b[0m\u001b[1m-completion\u001b[0m \u001b[1m \u001b[0m Install completion for the current \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m shell. \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-show\u001b[0m\u001b[1m-completion\u001b[0m \u001b[1m \u001b[0m Show completion for the current shell, \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m to copy it or customize the \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m installation. \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1m-\u001b[0m\u001b[1m-help\u001b[0m \u001b[1m \u001b[0m Show this message and exit. \u001b[2m│\u001b[0m\n", - "\u001b[2m╰──────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n", - "\u001b[2m╭─\u001b[0m\u001b[2m Commands \u001b[0m\u001b[2m──────────────────────────────────────────────────────────────────\u001b[0m\u001b[2m─╮\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1mtests.node \u001b[0m\u001b[1m \u001b[0m Test using nodejs \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1mtests.perl \u001b[0m\u001b[1m \u001b[0m Test using perl \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1mtests.python \u001b[0m\u001b[1m \u001b[0m Test using php \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1mtests.r \u001b[0m\u001b[1m \u001b[0m Test using R \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1mtests.run-all \u001b[0m\u001b[1m \u001b[0m Run tests for all the other targets \u001b[2m│\u001b[0m\n", - "\u001b[2m│\u001b[0m \u001b[1mtests.sh \u001b[0m\u001b[1m \u001b[0m Test using sh \u001b[2m│\u001b[0m\n", - "\u001b[2m╰──────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n", - " \n", - " If you have any problem, open an issue at: \n", - " https://github.com/osl-incubator/makim \n", - " \n", - "\n" - ] - } - ], - "source": [ - "!makim --help" - ] - }, - { - "cell_type": "markdown", - "id": "14ed7f09-9bea-490d-ad3d-629ea4ee971f", - "metadata": {}, - "source": [ - "Prior to executing these targets, it is necessary to install the required dependencies:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "defb03d3-404d-489b-9990-ed66552aa341", - "metadata": {}, - "outputs": [], - "source": [ - "!mamba install -q -y perl nodejs r-base sh " - ] - }, - { - "cell_type": "markdown", - "id": "402bf345-9a18-403f-be99-e428e7c78b97", - "metadata": {}, - "source": [ - "Proceed to execute all defined targets by invoking the run-all target, which encapsulates all other targets as its dependencies for a sequential execution process:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "8f4e23ef-0238-4393-9747-8f945de40f79", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Makim file: .makim.yaml\n", - "Running Makim: Hello, World, from NodeJS!\n", - "(node:1634785) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n", - "(Use `node --trace-warnings ...` to show where the warning was created)\n", - "Running Makim: Hello, World, from Perl!\n", - "Running Makim: Hello, World, from Python!\n", - "[1] \"Running Makim: Hello, World, from R!\"\n", - "Running Makim: Hello, World, from sh!\n" - ] - } - ], - "source": [ - "!makim tests.run-all" - ] - }, - { - "cell_type": "markdown", - "id": "d57e524f-9ddd-4e1e-9d90-e47154c0950d", - "metadata": {}, - "source": [ - "In scenarios where your chosen interpreter supports debugging - such as Python or Xonsh through the use of `breakpoint()` - you can introduce a breakpoint within your code. This enables the debugging of your **Makim** target, allowing for an interactive examination of the execution flow and variable states." - ] - }, - { - "cell_type": "markdown", - "id": "43d8808a-6b75-4e54-a193-4ba148de913a", - "metadata": {}, - "source": [ - "### Using Variables (vars)\n", - "\n", - "**Makim** facilitates the definition of variables within the `.makim.yaml` configuration, supporting all the **YAML** data types, including strings, lists, and dictionaries. This feature enhances script configurability and reusability across different tasks and environments.\n", - "\n", - "Consider reviewing the provided example to understand how to effectively leverage variables in your **Makim** configurations:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "ff7b4903-8caf-454b-828f-4ab6cf57d8fa", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting .makim.yaml\n" - ] - } - ], - "source": [ - "%%writefile .makim.yaml\n", - "version: 1.0\n", - "\n", - "vars:\n", - " project-name: \"my-project\"\n", - " dependencies:\n", - " \"dep1\": \"v1\"\n", - " \"dep2\": \"v1.1\"\n", - " \"dep3\": \"v2.3\"\n", - " authorized-users:\n", - " - admin1\n", - " - admin2\n", - " - admin3\n", - "\n", - "groups:\n", - " staging:\n", - " vars:\n", - " env-name: \"staging\"\n", - " staging-dependencies:\n", - " \"dep4\": \"v4.3\"\n", - " \"dep5\": \"v1.1.1\"\n", - " staging-authorized-users:\n", - " - staging1\n", - " - staging2\n", - " - staging3\n", - " targets:\n", - " create-users:\n", - " help: Create users for staging, this example uses jinja2 for loop.\n", - " # each target can also specify their `vars`, but it will not be used in this example\n", - " run: |\n", - " def create_user(username):\n", - " print(f\">>> creating user: {username} ... DONE!\")\n", - " \n", - " print(\"create admin users:\")\n", - " {% for user in vars.authorized_users %}\n", - " create_user(\"${{ user }}\")\n", - " {% endfor %}\n", - "\n", - " print(\"\\ncreate staging users:\")\n", - " {% for user in vars.staging_authorized_users %}\n", - " create_user(\"${{ user }}\")\n", - " {% endfor %}\n", - "\n", - " install:\n", - " help: install deps for staging using native xonsh `for` loop (it could work with Python as well)\n", - " # each target can also specify their `vars`, but it will not be used in this example\n", - " run: |\n", - " def install(package, version):\n", - " print(f\">>> installing: {package}@{version} ... DONE!\")\n", - " \n", - " print(\"install global dependencies:\")\n", - " for package, version in ${{ vars.dependencies | safe }}.items():\n", - " install(package, version)\n", - "\n", - " print(\"\\ninstall staging dependencies:\")\n", - " for package, version in ${{ vars.staging_dependencies | safe }}.items():\n", - " install(package, version)" - ] - }, - { - "cell_type": "markdown", - "id": "2b67905e-2e5b-4d54-bee8-eadcfe12cd66", - "metadata": {}, - "source": [ - "Now, let's proceed to create users within the staging environment:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "89f4ea8e-b58e-4da3-9097-61deaf5e6c70", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Makim file: .makim.yaml\n", - "create admin users:\n", - ">>> creating user: admin1 ... DONE!\n", - ">>> creating user: admin2 ... DONE!\n", - ">>> creating user: admin3 ... DONE!\n", - "\n", - "create staging users:\n", - ">>> creating user: staging1 ... DONE!\n", - ">>> creating user: staging2 ... DONE!\n", - ">>> creating user: staging3 ... DONE!\n" - ] - } - ], - "source": [ - "!makim staging.create-users" - ] - }, - { - "cell_type": "markdown", - "id": "cd07e3af-4527-4530-952e-01848701e185", - "metadata": {}, - "source": [ - "Last but not least, let's run the install target:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "2eb7291c-610a-427c-a970-bb923b17678e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Makim file: .makim.yaml\n", - "install global dependencies:\n", - ">>> installing: dep1@v1 ... DONE!\n", - ">>> installing: dep2@v1.1 ... DONE!\n", - ">>> installing: dep3@v2.3 ... DONE!\n", - "\n", - "install staging dependencies:\n", - ">>> installing: dep4@v4.3 ... DONE!\n", - ">>> installing: dep5@v1.1.1 ... DONE!\n" - ] - } - ], - "source": [ - "!makim staging.install" - ] - }, - { - "cell_type": "markdown", - "id": "fcfc9258-a97d-4378-9cbb-e1be2678c801", - "metadata": {}, - "source": [ - "### Defining Arguments\n", - "\n", - "**Makim** enhances script flexibility by allowing the use of arguments. It enables not only the definition of arguments for tasks but also the passing of arguments to dependencies and the specification of conditions for those dependencies.\n", - "\n", - "Explore this functionality through this example:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "cb5c198b-4644-4e92-b1a4-cc66c55fc29d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting .makim.yaml\n" - ] - } - ], - "source": [ - "%%writefile .makim.yaml\n", - "version: 1.0.0\n", - "groups:\n", - " print:\n", - " env-file: .env\n", - " targets:\n", - " name:\n", - " help: Print given name\n", - " args:\n", - " name:\n", - " type: str\n", - " required: true\n", - " run: print(\"${{ args.name }}\")\n", - " list:\n", - " help: Build the program\n", - " args:\n", - " i-am-sure:\n", - " type: bool\n", - " dependencies:\n", - " - target: print.name\n", - " if: ${{ args.i_am_sure == true }}\n", - " args:\n", - " name: Mary\n", - " - target: print.name\n", - " if: ${{ args.i_am_sure == true }}\n", - " args:\n", - " name: John\n", - " - target: print.name\n", - " if: ${{ args.i_am_sure == true }}\n", - " args:\n", - " name: Ellen\n", - " - target: print.name\n", - " if: ${{ args.i_am_sure == true }}\n", - " args:\n", - " name: Marc" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "551d69e9-d074-4b70-b868-f7e9fa6faba6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Makim file: .makim.yaml\n" - ] - } - ], - "source": [ - "!makim print.list" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "dfe7b8ba-f438-4f62-88fc-e99e11a15bee", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Makim file: .makim.yaml\n", - "Mary\n", - "John\n", - "Ellen\n", - "Marc\n" - ] - } - ], - "source": [ - "!makim print.list --i-am-sure" - ] - }, - { - "cell_type": "markdown", - "id": "5f7c72db-31d6-47f5-a8aa-932f3e5bf2a7", - "metadata": {}, - "source": [ - "### Utilizing Environment Variables\n", - "\n", - "The previous sections demonstrated the use of environment variables. Here, we'll delve into their application in more detail.\n", - "\n", - "**Makim** permits the incorporation of environment variables from `.env` files or directly within the `.makim.yaml` file, applicable at global, group, and target levels.\n", - "\n", - "Examine an example to understand the implementation:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "6e0c4c5a-ef21-43e0-ac18-40672850b93a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting .env\n" - ] - } - ], - "source": [ - "%%writefile .env\n", - "ENV=dev" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "678192e5-7af3-4862-bc76-4beecf030bd2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting .makim.yaml\n" - ] - } - ], - "source": [ - "%%writefile .makim.yaml\n", - "version: 1.0\n", - "env-file: .env\n", - "env:\n", - " GLOBAL_VAR: \"1\"\n", - "groups:\n", - " global-scope:\n", - " env:\n", - " GROUP_VAR: \"2\"\n", - " targets:\n", - " test-var-env-file:\n", - " help: Test env variable defined in the global scope from env-file\n", - " run: |\n", - " import os\n", - " assert str(os.getenv(\"ENV\")) == \"dev\"\n", - "\n", - " test-var-env:\n", - " help: Test env variable defined in the global scope in `env` section\n", - " env:\n", - " TARGET_VAR: \"3\"\n", - " run: |\n", - " import os\n", - " # you can get an environment variable directly with xonsh/python\n", - " assert str(os.getenv(\"GLOBAL_VAR\")) == \"1\"\n", - " # or you can get an environment variable using jinja2 tag\n", - " assert \"${{ env.GROUP_VAR }}\" == \"2\"\n", - " assert \"${{ env.get(\"TARGET_VAR\") }}\" == \"3\"\n", - " assert \"${{ env.get(\"UNKNOWN_TARGET_VAR\", \"4\") }}\" == \"4\"" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "6221cfec-19b5-450e-853e-737ce0ed024f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Makim file: .makim.yaml\n" - ] - } - ], - "source": [ - "!makim global-scope.test-var-env-file" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "548a68cf-b597-4eb9-8438-eb97729dcb46", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Makim file: .makim.yaml\n" - ] - } - ], - "source": [ - "!makim global-scope.test-var-env" - ] - }, - { - "cell_type": "markdown", - "id": "ce5e09d7-bf8b-4b0e-9127-48958ee79601", - "metadata": {}, - "source": [ - "### Specifying the Working Directory\n", - "\n", - "Makim provides the capability to set a specific working directory for tasks at any scope: global, group, or target.\n", - "\n", - "Review a straightforward example to learn how to apply this feature:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "65335daf-ac06-497c-a0f8-6dbb9f5a570b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting .makim.yaml\n" - ] - } - ], - "source": [ - "%%writefile .makim.yaml\n", - "version: 1.0\n", - "working-directory: \"/tmp\"\n", - "\n", - "groups:\n", - " check-wd:\n", - " targets:\n", - " is-tmp:\n", - " help: Test if working directory is `tmp`\n", - " run: |\n", - " import os\n", - " print(os.getcwd())\n", - " assert os.getcwd() == \"/tmp\"" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "0938bcee-26fb-4028-82f6-bbed8453eba7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Makim file: .makim.yaml\n", - "/tmp\n" - ] - } - ], - "source": [ - "!makim check-wd.is-tmp" - ] - }, - { - "cell_type": "markdown", - "id": "ae987426-9ead-4375-bddf-b5ded48aeb1a", - "metadata": {}, - "source": [ - "This tutorial concludes with a showcase of Makim's key features. While this overview covers the essentials, diving deeper into **Makim** will reveal more advanced and intriguing ways to leverage its capabilities." - ] - }, - { - "cell_type": "markdown", - "id": "a5f5fb97-503f-4a3b-92a7-8ecef4d9687c", - "metadata": {}, - "source": [ - "## Contributing to Makim\n", - "\n", - "Makim's growth is propelled by its community. Contributions, whether through code, documentation, or feedback, are welcome. Explore the [GitHub repository](https://github.com/osl-incubator/makim) and consider contributing to foster Makim's development." - ] - }, - { - "cell_type": "markdown", - "id": "822a5ed4-6941-4761-9a7b-9910dd075849", - "metadata": {}, - "source": [ - "## Conclusion\n", - "\n", - "Makim stands out as a transformative tool in project automation, bridging the gap between simplicity and complexity. Its comprehensive feature set, coupled with the flexibility of its configuration, makes Makim a quintessential tool for developers and DevOps engineers alike. As you incorporate Makim into your projects, its impact on enhancing productivity and consistency will become evident, marking it as an indispensable part of your development toolkit.\n", - "\n", - "Dive deeper into Makim's functionalities by visiting the [official documentation](https://github.com/osl-incubator/makim). Try it and let us know your thoughts about it!" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pages/blog/streamlining-project-automation-with-makim/index.qmd b/pages/blog/streamlining-project-automation-with-makim/index.qmd new file mode 100644 index 000000000..fb053a1b1 --- /dev/null +++ b/pages/blog/streamlining-project-automation-with-makim/index.qmd @@ -0,0 +1,622 @@ +--- +title: "Streamlining Project Automation with Makim" +slug: "streamlining-project-automation-with-makim" +date: 2024-03-18 +authors: ["Ivan Ogasawara"] +tags: ["makim", "automation", "devops", "open-source"] +categories: ["devops", "automation", "python"] +description: | + In software development, where efficiency, consistency, and reliability are paramount, + automation tools play a crucial role. Makim, an innovative open-source tool, steps + into the spotlight to improve automation workflows. It simplifies script execution, + environment management, and task dependencies, positioning itself as a great asset in + modern development environments. + environment. +thumbnail: "/header.png" +template: "blog-post.html" +--- +# Streamlining Project Automation with Makim + +In software development, where efficiency, consistency, and reliability are paramount, automation tools play a crucial role. Makim, an innovative open-source tool, steps into the spotlight to improve automation workflows. It simplifies script execution, environment management, and task dependencies, positioning itself as a great asset in modern development environments. + +## Introducing Makim + +`Makim` elevates project automation by offering a structured, yet flexible approach to manage routine tasks, complex task dependencies, and environment configurations. Its design is centered around the `.makim.yaml` configuration file, allowing developers to orchestrate their workflows with precision and ease. Unlike traditional script execution tools, Makim's Python-based architecture and support for multiple programming languages and shells enhance its versatility and applicability across diverse projects. + +Especially suited for DevOps Engineers and Software Developers, Makim eliminates redundancy in automation tasks. Its core functionality extends beyond simple script execution, encompassing: + +- Argument definition for scripts +- Organization of tasks into groups +- Advanced dependency management between tasks +- Utilization of environment variables and custom variables +- Dynamic content generation with Jinja2 templates +- Specification of working directories for tasks +- Execution flexibility through support for multiple interpreters or shells + +Despite its broad capabilities, Makim currently lacks support for Windows but plans to extend its compatibility in future versions. + +## Getting Started with Makim + +### Installation + +Makim can be installed via `pip` or `conda`, catering to different setup preferences: + +- To install `Makim` using `pip`, run: + + +```python +!pip install -q "makim==1.14.0" +``` + +- For those who prefer `conda`, execute: + + ``` + conda install "makim=1.14.0" + ``` + +Given Makim's active development, pinning to a specific version is recommended to ensure consistency. + +For this tutorial, we will disable the output color feature provided by typer, the command-line interface engine used by **Makim**. + + +```python +import os + +os.environ["NO_COLOR"] = "1" +``` + +### Configuring `.makim.yaml` + +The `.makim.yaml` file is the foundation of your Makim configuration. Here's how to start: + +1. **Create the `.makim.yaml` File**: Place this file at the root of your project directory. + +2. **Define Your Automation Tasks**: Configure your tasks, specifying actions, arguments, and dependencies. For example: + + +```python +%%writefile .makim.yaml +version: 1.0.0 +groups: + clean: + env-file: .env + targets: + tmp: + help: Use this target to clean up temporary files + run: | + echo "Cleaning up..." + tests: + targets: + unit: + help: Build the program + args: + clean: + type: bool + action: store_true + help: if not set, the clean dependency will not be triggered. + dependencies: + - target: clean.tmp + if: ${{ args.clean == true }} + run: | + echo "Runnint unit tests..." +``` + + Overwriting .makim.yaml + + +This setup demonstrates Makim's ability to manage tasks with conditional logic and dependencies. + +### Exploring Makim's CLI + +Makim's CLI provides insights into available commands, arguments, and configurations through the auto-generated help menu: + + +```python +!makim --help +``` + +   +  Usage: makim [OPTIONS] COMMAND [ARGS]...   +   + Makim is a tool that helps you to organize and simplify your helper commands. + + ╭─ Options ────────────────────────────────────────────────────────────────────╮ + │ --version -v   Show the version and exit │ + │ --file TEXT Makim config file [default: .makim.yaml] │ + │ --dry-run   Execute the command in dry mode │ + │ --verbose   Execute the command in verbose mode │ + │ --install-completion   Install completion for the current │ + │ shell. │ + │ --show-completion   Show completion for the current shell, │ + │ to copy it or customize the │ + │ installation. │ + │ --help   Show this message and exit. │ + ╰──────────────────────────────────────────────────────────────────────────────╯ + ╭─ Commands ───────────────────────────────────────────────────────────────────╮ + │ clean.tmp   Use this target to clean up temporary files │ + │ tests.unit   Build the program │ + ╰──────────────────────────────────────────────────────────────────────────────╯ + + If you have any problem, open an issue at: + https://github.com/osl-incubator/makim + + + + +This feature facilitates easy access to Makim's functionalities, enhancing usability and understanding of the tool. + +### Executing Your First Commands + +With your `.makim.yaml` file set up, you can begin to use `makim`: + + +```python +!makim clean.tmp +``` + + Makim file: .makim.yaml + Cleaning up... + + + +```python +!makim tests.unit +``` + + Makim file: .makim.yaml + Runnint unit tests... + + +In the case you type your command wrong, **Makim** will suggest you some alternative: + + +```python +!makim tests.unittest +``` + + Usage: makim [OPTIONS] COMMAND [ARGS]... + Try 'makim --help' for help. + ╭─ Error ──────────────────────────────────────────────────────────────────────╮ + │ No such command 'tests.unittest'. │ + ╰──────────────────────────────────────────────────────────────────────────────╯ + Command tests.unittest not found. Did you mean tests.unit'? + + +**Makim** CLI is empowered by **Typer**, and it allows us to have auto-completion for Makim groups and targets! If you want to install, you can run the following command: + + +```python +!makim --install-completion +``` + + bash completion installed in /home/xmn/.bash_completions/makim.sh + Completion will take effect once you restart the terminal + + +After this command you will need to restart the terminal in order to use this auto-completion feature. + +## Advanced Features and Examples + +Makim's adaptability is showcased through various features and practical examples: + +- **Conditional Dependencies and Arguments**: Define complex task dependencies with conditional execution based on passed arguments. +- **Dynamic Configuration with Jinja2**: Leverage Jinja2 templates for advanced scripting and dynamic content generation. +- **Environment and Custom Variable Management**: Organize and utilize variables effectively across different scopes of your project. +- **Specifying Working Directories**: Control the execution context of your tasks by setting working directories. + +These examples underscore Makim's capability to accommodate intricate automation scenarios, streamlining development workflows. + +## Exploring Makim Through Examples + +### Utilizing Various Interpreters + +Makim extends its functionality beyond conventional script execution by supporting various interpreters and shell languages, facilitating a versatile development environment. While **xonsh** is the default interpreter - blending the capabilities of Bash and Python for an enriched command-line experience - Makim's architecture allows for seamless integration with other environments. For developers seeking to leverage this feature, a foundational understanding of **xonsh** can be beneficial. Comprehensive details and usage guidelines are available in the [official xonsh documentation](https://xon.sh/). + +This section demonstrates executing straightforward commands across multiple interpreters, showcasing Makim's adaptability to diverse programming contexts. + + +```python +%%writefile .env +MSG_PREFIX="Running Makim: Hello, World," +``` + + Overwriting .env + + + +```python +%%writefile .makim.yaml +version: 1.0 +env-file: .env +groups: + tests: + targets: + node: + help: Test using nodejs + shell: node + run: console.log("${{ env.MSG_PREFIX }} from NodeJS!"); + perl: + help: Test using perl + shell: perl + run: print "${{ env.MSG_PREFIX }} from Perl!\n"; + + python: + help: Test using php + shell: python + run: print("${{ env.MSG_PREFIX }} from Python!") + + r: + help: Test using R + shell: Rscript + run: print("${{ env.MSG_PREFIX }} from R!") + + sh: + help: Test using sh + shell: sh + run: echo "${{ env.MSG_PREFIX }} from sh!" + + run-all: + help: Run tests for all the other targets + dependencies: + - target: node + - target: perl + - target: python + - target: r + - target: sh +``` + + Overwriting .makim.yaml + + + +```python +!makim --help +``` + +   +  Usage: makim [OPTIONS] COMMAND [ARGS]...   +   + Makim is a tool that helps you to organize and simplify your helper commands. + + ╭─ Options ────────────────────────────────────────────────────────────────────╮ + │ --version -v   Show the version and exit │ + │ --file TEXT Makim config file [default: .makim.yaml] │ + │ --dry-run   Execute the command in dry mode │ + │ --verbose   Execute the command in verbose mode │ + │ --install-completion   Install completion for the current │ + │ shell. │ + │ --show-completion   Show completion for the current shell, │ + │ to copy it or customize the │ + │ installation. │ + │ --help   Show this message and exit. │ + ╰──────────────────────────────────────────────────────────────────────────────╯ + ╭─ Commands ───────────────────────────────────────────────────────────────────╮ + │ tests.node   Test using nodejs │ + │ tests.perl   Test using perl │ + │ tests.python   Test using php │ + │ tests.r   Test using R │ + │ tests.run-all   Run tests for all the other targets │ + │ tests.sh   Test using sh │ + ╰──────────────────────────────────────────────────────────────────────────────╯ + + If you have any problem, open an issue at: + https://github.com/osl-incubator/makim + + + + +Prior to executing these targets, it is necessary to install the required dependencies: + + +```python +!mamba install -q -y perl nodejs r-base sh +``` + +Proceed to execute all defined targets by invoking the run-all target, which encapsulates all other targets as its dependencies for a sequential execution process: + + +```python +!makim tests.run-all +``` + + Makim file: .makim.yaml + Running Makim: Hello, World, from NodeJS! + (node:1634785) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set. + (Use `node --trace-warnings ...` to show where the warning was created) + Running Makim: Hello, World, from Perl! + Running Makim: Hello, World, from Python! + [1] "Running Makim: Hello, World, from R!" + Running Makim: Hello, World, from sh! + + +In scenarios where your chosen interpreter supports debugging - such as Python or Xonsh through the use of `breakpoint()` - you can introduce a breakpoint within your code. This enables the debugging of your **Makim** target, allowing for an interactive examination of the execution flow and variable states. + +### Using Variables (vars) + +**Makim** facilitates the definition of variables within the `.makim.yaml` configuration, supporting all the **YAML** data types, including strings, lists, and dictionaries. This feature enhances script configurability and reusability across different tasks and environments. + +Consider reviewing the provided example to understand how to effectively leverage variables in your **Makim** configurations: + + +```python +%%writefile .makim.yaml +version: 1.0 + +vars: + project-name: "my-project" + dependencies: + "dep1": "v1" + "dep2": "v1.1" + "dep3": "v2.3" + authorized-users: + - admin1 + - admin2 + - admin3 + +groups: + staging: + vars: + env-name: "staging" + staging-dependencies: + "dep4": "v4.3" + "dep5": "v1.1.1" + staging-authorized-users: + - staging1 + - staging2 + - staging3 + targets: + create-users: + help: Create users for staging, this example uses jinja2 for loop. + # each target can also specify their `vars`, but it will not be used in this example + run: | + def create_user(username): + print(f">>> creating user: {username} ... DONE!") + + print("create admin users:") + {% for user in vars.authorized_users %} + create_user("${{ user }}") + {% endfor %} + + print("\ncreate staging users:") + {% for user in vars.staging_authorized_users %} + create_user("${{ user }}") + {% endfor %} + + install: + help: install deps for staging using native xonsh `for` loop (it could work with Python as well) + # each target can also specify their `vars`, but it will not be used in this example + run: | + def install(package, version): + print(f">>> installing: {package}@{version} ... DONE!") + + print("install global dependencies:") + for package, version in ${{ vars.dependencies | safe }}.items(): + install(package, version) + + print("\ninstall staging dependencies:") + for package, version in ${{ vars.staging_dependencies | safe }}.items(): + install(package, version) +``` + + Overwriting .makim.yaml + + +Now, let's proceed to create users within the staging environment: + + +```python +!makim staging.create-users +``` + + Makim file: .makim.yaml + create admin users: + >>> creating user: admin1 ... DONE! + >>> creating user: admin2 ... DONE! + >>> creating user: admin3 ... DONE! + + create staging users: + >>> creating user: staging1 ... DONE! + >>> creating user: staging2 ... DONE! + >>> creating user: staging3 ... DONE! + + +Last but not least, let's run the install target: + + +```python +!makim staging.install +``` + + Makim file: .makim.yaml + install global dependencies: + >>> installing: dep1@v1 ... DONE! + >>> installing: dep2@v1.1 ... DONE! + >>> installing: dep3@v2.3 ... DONE! + + install staging dependencies: + >>> installing: dep4@v4.3 ... DONE! + >>> installing: dep5@v1.1.1 ... DONE! + + +### Defining Arguments + +**Makim** enhances script flexibility by allowing the use of arguments. It enables not only the definition of arguments for tasks but also the passing of arguments to dependencies and the specification of conditions for those dependencies. + +Explore this functionality through this example: + + +```python +%%writefile .makim.yaml +version: 1.0.0 +groups: + print: + env-file: .env + targets: + name: + help: Print given name + args: + name: + type: str + required: true + run: print("${{ args.name }}") + list: + help: Build the program + args: + i-am-sure: + type: bool + dependencies: + - target: print.name + if: ${{ args.i_am_sure == true }} + args: + name: Mary + - target: print.name + if: ${{ args.i_am_sure == true }} + args: + name: John + - target: print.name + if: ${{ args.i_am_sure == true }} + args: + name: Ellen + - target: print.name + if: ${{ args.i_am_sure == true }} + args: + name: Marc +``` + + Overwriting .makim.yaml + + + +```python +!makim print.list +``` + + Makim file: .makim.yaml + + + +```python +!makim print.list --i-am-sure +``` + + Makim file: .makim.yaml + Mary + John + Ellen + Marc + + +### Utilizing Environment Variables + +The previous sections demonstrated the use of environment variables. Here, we'll delve into their application in more detail. + +**Makim** permits the incorporation of environment variables from `.env` files or directly within the `.makim.yaml` file, applicable at global, group, and target levels. + +Examine an example to understand the implementation: + + +```python +%%writefile .env +ENV=dev +``` + + Overwriting .env + + + +```python +%%writefile .makim.yaml +version: 1.0 +env-file: .env +env: + GLOBAL_VAR: "1" +groups: + global-scope: + env: + GROUP_VAR: "2" + targets: + test-var-env-file: + help: Test env variable defined in the global scope from env-file + run: | + import os + assert str(os.getenv("ENV")) == "dev" + + test-var-env: + help: Test env variable defined in the global scope in `env` section + env: + TARGET_VAR: "3" + run: | + import os + # you can get an environment variable directly with xonsh/python + assert str(os.getenv("GLOBAL_VAR")) == "1" + # or you can get an environment variable using jinja2 tag + assert "${{ env.GROUP_VAR }}" == "2" + assert "${{ env.get("TARGET_VAR") }}" == "3" + assert "${{ env.get("UNKNOWN_TARGET_VAR", "4") }}" == "4" +``` + + Overwriting .makim.yaml + + + +```python +!makim global-scope.test-var-env-file +``` + + Makim file: .makim.yaml + + + +```python +!makim global-scope.test-var-env +``` + + Makim file: .makim.yaml + + +### Specifying the Working Directory + +Makim provides the capability to set a specific working directory for tasks at any scope: global, group, or target. + +Review a straightforward example to learn how to apply this feature: + + +```python +%%writefile .makim.yaml +version: 1.0 +working-directory: "/tmp" + +groups: + check-wd: + targets: + is-tmp: + help: Test if working directory is `tmp` + run: | + import os + print(os.getcwd()) + assert os.getcwd() == "/tmp" +``` + + Overwriting .makim.yaml + + + +```python +!makim check-wd.is-tmp +``` + + Makim file: .makim.yaml + /tmp + + +This tutorial concludes with a showcase of Makim's key features. While this overview covers the essentials, diving deeper into **Makim** will reveal more advanced and intriguing ways to leverage its capabilities. + +## Contributing to Makim + +Makim's growth is propelled by its community. Contributions, whether through code, documentation, or feedback, are welcome. Explore the [GitHub repository](https://github.com/osl-incubator/makim) and consider contributing to foster Makim's development. + +## Conclusion + +Makim stands out as a transformative tool in project automation, bridging the gap between simplicity and complexity. Its comprehensive feature set, coupled with the flexibility of its configuration, makes Makim a quintessential tool for developers and DevOps engineers alike. As you incorporate Makim into your projects, its impact on enhancing productivity and consistency will become evident, marking it as an indispensable part of your development toolkit. + +Dive deeper into Makim's functionalities by visiting the [official documentation](https://github.com/osl-incubator/makim). Try it and let us know your thoughts about it! diff --git a/pages/blog/three-years-with-google-summer-of-code-what-ive-learned/index.qmd b/pages/blog/three-years-with-google-summer-of-code-what-ive-learned/index.qmd new file mode 100644 index 000000000..040314330 --- /dev/null +++ b/pages/blog/three-years-with-google-summer-of-code-what-ive-learned/index.qmd @@ -0,0 +1,145 @@ +--- +title: "Three Years with Google Summer of Code: What I've Learned" +slug: three-years-with-google-summer-of-code-what-ive-learned +date: 2025-11-02 +authors: ["Ivan Ogasawara"] +tags: [open-source, gsoc, mentoring] +categories: [gsoc] +description: | + Three years in GSoC taught us one thing: mentoring matters more than code. + As a 2025 mentoring org (with AlphaOneLabs, Extralit, Makim, Sugar), our + playbook is simple—balance mentors, set explicit contribution rules, meet + regularly; and for contributors: communicate publicly and ship small, + tested PRs. +thumbnail: "/header.png" +template: "blog-post.html" +--- + +# Three Years with Google Summer of Code: What I've Learned + +Mentoring is at the heart of Open Science Labs (OSL). It's why we joined Google +Summer of Code (GSoC) in the first place. We started as a sub-organization under +the NumFOCUS umbrella for two years, and in 2025 we were accepted as a +**Mentoring Organization**. Huge thanks to **Anavelyz Pérez** for keeping us on +track. We’re pleased to have secured four contributor slots for 2025 with +**AlphaOneLabs**, **Extralit**, **Makim**, and **Sugar**. + +We're incredibly proud of the contributors and mentors who made GSoC 2025 a +success. We were also, honestly, a bit heartbroken—many strong applicants did +real work and still didn't get in. On a personal note, attending the **GSoC +Summit** was a highlight: I met inspiring people and learned a lot from their +experiences. + +Below are the lessons that stood out across these three years and practical +recommendations for organizers, mentors, and contributors. + +--- + +## The Big Lesson + +**GSoC isn't just about code—it's about mentoring.** Code is the artifact; +mentoring is the engine. The best summers happen when we design for learning, +clarity, and care. Everything else follows. + +--- + +## Recommendations for Organizers + +- **Confirm your slot count early.** The number of slots you _realistically_ + expect should shape how many projects you onboard and how you scope them. + +- **Balance mentors as well as projects.** When allocating slots, distribute + contributors across both projects _and_ mentors. Avoid situations where one + mentor has two contributors while another has none—burnout and uneven support + help no one. + +--- + +## Recommendations for Mentors + +- **Limit the number of projects per mentor.** The pre-selection phase is + intense. If you're stretched across multiple proposals, candidates won't get + the guidance they deserve. One well-mentored project beats three + under-mentored ones. + +- **Codify contribution rules up front.** Document expectations clearly and link + them everywhere: + + - Max PR size (e.g., “prefer ≤300 lines; split larger changes”). + - Stale PR policy (e.g., “no updates for 10 days → close or draft”). + - Code style, linting, and formatting rules. + - Clear stance on AI-generated code (allowed or not, and under what + conditions). + +- **Keep your CONTRIBUTING.md and PR template current.** Treat them as living + documents. If you change the rules mid-summer, call it out in a pinned + message. + +- **Equip contributors to grow.** Share starter issues, architecture diagrams, + walkthrough videos, and links to docs or talks. Provide “good first PR” + examples. + +- **Meet regularly.** Short weekly 1:1s or cohort calls work wonders. Use + agendas. End with explicit next steps. + +- **Nurture community, not competition.** Encourage contributors to help each + other, co-review PRs, and pair on debugging. A supportive, respectful culture + is non-negotiable. + +- **Have a Plan B for great applicants who aren't selected.** If you have + bandwidth, offer an internship track, micro-grants, or + “fellows-without-funding” with mentorship and recognition. It keeps momentum + and grows your contributor base. + +--- + +## Recommendations for Contributors + +- **Default to public communication.** Ask questions in the project's public + channels. It helps others learn and shows the team how you collaborate. + +- **If a mentor is unresponsive, switch projects.** They're likely overloaded; + repeated pings won't help. Find a project with responsive maintainers and + bandwidth for new contributors. + +- **Avoid giant PRs.** Huge changes are hard to review and often get stuck. Ship + small, focused PRs that follow the project's style and tests. + +- **Show you understand the project's culture.** Read the docs. Match coding + style. Follow the templates. Keep commits scoped and messages clear. + +- **Be careful with AI-generated code.** Don't paste blindly. Understand the + problem, explain your choices, remove unnecessary comments, and **never** + include emojis in code comments. + +- **Discuss big changes before you implement them.** Don't refactor core + components or alter architecture without buy-in. Open an issue, propose a + design, gather feedback. + +- **Ship tests and pass CI.** If you fix a bug or add a feature, include tests. + Make sure CI is green before asking for review. + +- **Submit up to three proposals to multiple orgs.** Each org has limited slots, + so contributing and applying across organizations can improve your chances—but + focus on quality! Note: only one proposal can be accepted per contributor. + ([developers.google.com][can_i_submit_more_than_one_proposal]) + +* **Write a crisp proposal.** Be clear, specific, and concise (≤10 pages). + Demonstrate understanding of the project and outline concrete steps, + milestones, and risks. Ask maintainers for early feedback so you have time to + refine. + +--- + +## Looking Ahead + +I'm excited to keep participating in GSoC in the coming years and to keep +welcoming new contributors into open-source communities. Thank you to the GSoC +team for running this program year after year—it raises the visibility of +projects, gives newcomers a safe place to learn from experts, and strengthens +the open-source ecosystem. For hundreds of students and first-time contributors, +GSoC isn't just a summer; it's a beginning. + +[can_i_submit_more_than_one_proposal]: + https://developers.google.com/open-source/gsoc/faq#can_i_submit_more_than_one_proposal + "Frequently Asked Questions | Google Summer of Code" diff --git a/pages/blog/typer-a-python-library-for-building-cli-applications/index.ipynb b/pages/blog/typer-a-python-library-for-building-cli-applications/index.ipynb deleted file mode 100644 index 1a17103ac..000000000 --- a/pages/blog/typer-a-python-library-for-building-cli-applications/index.ipynb +++ /dev/null @@ -1,487 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "id": "20d00ff3-2b98-43d3-87b3-2b8142356054", - "metadata": {}, - "source": [ - "---\n", - "title: \"Typer: A Python Library for Building CLI Applications\"\n", - "slug: typer-a-python-library-for-building-cli-applications\n", - "date: 2024-01-11\n", - "authors: [\"Ivan Ogasawara\"]\n", - "tags: [open-source, cli, python]\n", - "categories: [python]\n", - "description: |\n", - " Typer is an exciting library for Python developers, designed to make the creation of\n", - " command-line interface (CLI) applications not just easier, but also more enjoyable.\n", - " Built on top of the well-known Click library, Typer leverages Python 3.6+ features,\n", - " like type hints, to define CLI commands in a straightforward and intuitive way.\n", - "thumbnail: \"/header.svg\"\n", - "template: \"blog-post.html\"\n", - "---" - ] - }, - { - "cell_type": "markdown", - "id": "24d656a5-38e6-4ed0-bf15-b8854b96b475", - "metadata": {}, - "source": [ - "# Typer: A Python Library for Building CLI Applications\n", - "\n", - "## What is Typer?\n", - "\n", - "Typer is an exciting library for Python developers, designed to make the creation of command-line interface (CLI) applications not just easier, but also more enjoyable. Built on top of the well-known Click library, Typer leverages Python 3.6+ features, like type hints, to define CLI commands in a straightforward and intuitive way.\n", - "\n", - "## Why Choose Typer?\n", - "\n", - "- **Simplicity**: With Typer, you can create powerful CLI applications using minimal code.\n", - "- **Type Hints**: Leverages Python's type hints for parameter declaration, reducing errors and improving code clarity.\n", - "- **Automatic Help**: Generates help text and error messages based on your code.\n", - "- **Subcommands**: Supports nested commands, allowing complex CLI applications.\n", - "\n", - "## Getting Started with Typer\n", - "\n", - "### Installation\n", - "\n", - "To begin using Typer, you first need to install it. You can easily do this using pip:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "df3d4252-6fc3-46c0-93a2-009b29a06950", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install typer -q" - ] - }, - { - "cell_type": "markdown", - "id": "29687fe2-1c91-4193-8090-948d8f0e7f01", - "metadata": {}, - "source": [ - "### Creating Your First Typer Application\n", - "\n", - "Let's start with a simple example. We'll create an application that greets a user.\n", - "\n", - "First, import Typer and create an instance of it:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "1978b4e0-0fb0-4206-951b-30b59d3932c7", - "metadata": {}, - "outputs": [], - "source": [ - "import typer\n", - "\n", - "app = typer.Typer()" - ] - }, - { - "cell_type": "markdown", - "id": "6ca9ee46-01b3-45a1-a8d2-4cc6572469d0", - "metadata": {}, - "source": [ - "Now, define a function that will act as your command. Use type hints for function arguments:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "ed5cbac1-0d88-423e-a89a-13269674f8e5", - "metadata": {}, - "outputs": [], - "source": [ - "@app.command()\n", - "def greet(\n", - " name: str = typer.Option(\n", - " \"--name\",\n", - " \"-n\",\n", - " help=\"The name of the person to greet.\"\n", - " )\n", - ") -> None:\n", - " \"\"\"Greets the user by name.\"\"\"\n", - " typer.echo(f\"Hello {name}!\")" - ] - }, - { - "cell_type": "markdown", - "id": "7d3e5fa5-89eb-452f-bff3-0db5c27a6e14", - "metadata": {}, - "source": [ - "To run this application, use the following code block at the end of your script:" - ] - }, - { - "cell_type": "markdown", - "id": "f3f1a6c8-f1e2-45aa-8bfc-de265508b1f2", - "metadata": {}, - "source": [ - "```python\n", - "if __name__ == \"__main__\":\n", - " app()\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "ade80555-11b1-41fe-9f52-05c025c905ec", - "metadata": {}, - "source": [ - "### Running the Application\n", - "\n", - "Save the script as `greet.py` and run it from the command line:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "e2dc5366-aacc-4fcf-a35c-37a44c07e4a1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting greet.py\n" - ] - } - ], - "source": [ - "%%writefile greet.py\n", - "\n", - "import typer\n", - "\n", - "app = typer.Typer()\n", - "\n", - "@app.command()\n", - "def greet(\n", - " name: str = typer.Option(\n", - " ..., \n", - " \"--name\",\n", - " help=\"The name of the person to greet.\"\n", - " )\n", - ") -> None:\n", - " \"\"\"Greets the user by name.\"\"\"\n", - " typer.echo(f\"Hello {name}!\")\n", - "\n", - "if __name__ == \"__main__\":\n", - " app()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "ffbab51f-316b-45ae-ae0f-89ae4f2fb091", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hello Alice!\n" - ] - } - ], - "source": [ - "!python greet.py --name Alice" - ] - }, - { - "cell_type": "markdown", - "id": "888efccd-2376-4ae2-b5f7-9f9bb533a061", - "metadata": {}, - "source": [ - "### Help Documentation\n", - "\n", - "Typer automatically generates help documentation for your application. Try running:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "df751231-f505-4325-b866-d1aca5c8ea1e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Usage: greet.py [OPTIONS]\n", - "\n", - " Greets the user by name.\n", - "\n", - "Options:\n", - " --name TEXT The name of the person to greet. [required]\n", - " --install-completion [bash|zsh|fish|powershell|pwsh]\n", - " Install completion for the specified shell.\n", - " --show-completion [bash|zsh|fish|powershell|pwsh]\n", - " Show completion for the specified shell, to\n", - " copy it or customize the installation.\n", - " --help Show this message and exit.\n" - ] - } - ], - "source": [ - "!python greet.py --help" - ] - }, - { - "cell_type": "markdown", - "id": "32c3a083-7758-4ccf-a284-b3c746bcdfc9", - "metadata": {}, - "source": [ - "You'll get a detailed description of how to use the command, including available options.\n", - "\n", - "## Working with Subcommands\n", - "\n", - "Typer supports subcommands, allowing you to build more complex applications. In the following example, the script is structured around a main Typer application (app) and two sub-applications (app_user and app_product). This hierarchical structure is a hallmark of Typer, allowing for the organization of commands into distinct categories – in this case, user-related and product-related operations. Such an approach not only enhances the readability and maintainability of the code but also provides a more intuitive interface for the end users. They can easily navigate through the different functionalities of the application, whether it's creating or updating users, or handling product information." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "f65b9abc-3ec4-403f-baea-0fc900dde19d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting ecommerce.py\n" - ] - } - ], - "source": [ - "%%writefile ecommerce.py\n", - "import typer\n", - "\n", - "from typer import Context, Option\n", - "\n", - "\n", - "app = typer.Typer(help=\"Operations for e-commerce.\")\n", - "app_user = typer.Typer(help=\"Operations for user model.\")\n", - "app_product = typer.Typer(help=\"Operations for product model.\")\n", - "\n", - "app.add_typer(app_user, name=\"user\")\n", - "app.add_typer(app_product, name=\"product\")\n", - "\n", - "@app.callback(invoke_without_command=True)\n", - "def main(\n", - " ctx: Context,\n", - " version: bool = Option(\n", - " None,\n", - " \"--version\",\n", - " \"-v\",\n", - " is_flag=True,\n", - " help=\"Show the version and exit.\",\n", - " ),\n", - ") -> None:\n", - " \"\"\"Process envers for specific flags, otherwise show the help menu.\"\"\"\n", - " if version:\n", - " __version__ = \"0.1.0\"\n", - " typer.echo(f\"Version: {__version__}\")\n", - " raise typer.Exit()\n", - "\n", - " if ctx.invoked_subcommand is None:\n", - " typer.echo(ctx.get_help())\n", - " raise typer.Exit(0)\n", - "\n", - "@app_user.command(\"create\")\n", - "def user_create(name: str = typer.Argument(..., help=\"Name of the user to create.\")) -> None:\n", - " \"\"\"Create a new user with the given name.\"\"\"\n", - " print(f\"Creating user: {name} - Done\")\n", - "\n", - "\n", - "@app_user.command(\"update\")\n", - "def user_update(name: str = typer.Argument(..., help=\"Name of the user to update.\")) -> None:\n", - " \"\"\"Update user data with the given name.\"\"\"\n", - " print(f\"Updating user: {name} - Done\")\n", - "\n", - "@app_product.command(\"create\")\n", - "def product_create(name: str = typer.Argument(..., help=\"Name of the product to create.\")) -> None:\n", - " \"\"\"Create a new product with the given name.\"\"\"\n", - " print(f\"Creating product: {name} - Done\")\n", - "\n", - "\n", - "@app_product.command(\"update\")\n", - "def product_update(name: str = typer.Argument(..., help=\"Name of the product to update.\")) -> None:\n", - " \"\"\"Update a product with the given name.\"\"\"\n", - " print(f\"Updating product: {name} - Done\")\n", - "\n", - "\n", - "if __name__ == \"__main__\":\n", - " app()" - ] - }, - { - "cell_type": "markdown", - "id": "5e4b18ac-db76-4428-a6e9-46d68aa4b80b", - "metadata": {}, - "source": [ - "A key feature demonstrated in the script is the use of the callback function with the invoke_without_command=True parameter. This setup enables the execution of specific code (like displaying the version or help text) before any subcommands are processed. It's a powerful tool for handling pre-command logic or global options that apply to the entire CLI application.\n", - "\n", - "Moreover, the script showcases the simplicity and elegance of defining commands in Typer. Each operation, such as creating or updating users and products, is defined as a function, with parameters automatically translated into command-line options or arguments. This approach not only makes the code more readable but also leverages Python's type hints to ensure that the command-line arguments are correctly interpreted, providing a seamless and error-free user experience." - ] - }, - { - "cell_type": "markdown", - "id": "d5a36362-e9dd-4653-a72b-41e54ee88717", - "metadata": {}, - "source": [ - "In the following lines, there are some examples of the CLI call with different parameters." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "5edf02bf-631b-482c-8e58-f272ce837669", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Usage: ecommerce.py [OPTIONS] COMMAND [ARGS]...\n", - "\n", - " Operations for e-commerce.\n", - "\n", - "Options:\n", - " -v, --version Show the version and exit.\n", - " --install-completion [bash|zsh|fish|powershell|pwsh]\n", - " Install completion for the specified shell.\n", - " --show-completion [bash|zsh|fish|powershell|pwsh]\n", - " Show completion for the specified shell, to\n", - " copy it or customize the installation.\n", - " --help Show this message and exit.\n", - "\n", - "Commands:\n", - " product Operations for product model.\n", - " user Operations for user model.\n" - ] - } - ], - "source": [ - "!python ecommerce.py --help" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "39f172a1-7a30-4c66-a1db-771711efa3ca", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Version: 0.1.0\n" - ] - } - ], - "source": [ - "!python ecommerce.py --version" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "70d3db3d-1333-4a97-8b3b-8a2afc0e186d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Usage: ecommerce.py user [OPTIONS] COMMAND [ARGS]...\n", - "\n", - " Operations for user model.\n", - "\n", - "Options:\n", - " --help Show this message and exit.\n", - "\n", - "Commands:\n", - " create Create a new user with the given name.\n", - " update Update user data with the given name.\n" - ] - } - ], - "source": [ - "!python ecommerce.py user --help" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "707cebd3-bc21-4b52-885e-b72e0923e391", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Usage: ecommerce.py user create [OPTIONS] NAME\n", - "\n", - " Create a new user with the given name.\n", - "\n", - "Arguments:\n", - " NAME Name of the user to create. [required]\n", - "\n", - "Options:\n", - " --help Show this message and exit.\n" - ] - } - ], - "source": [ - "!python ecommerce.py user create --help" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4a6ff744-17fb-44eb-a80c-86f97b4f960a", - "metadata": {}, - "outputs": [], - "source": [ - "!python ecommerce.py product --help" - ] - }, - { - "cell_type": "markdown", - "id": "c636a291-3240-4b45-bcd2-93e25b442fe0", - "metadata": {}, - "source": [ - "## Conclusion\n", - "\n", - "Typer is a powerful yet straightforward tool for building CLI applications in Python. By leveraging Python's type hints, it offers an intuitive way to define commands and parameters, automatically handles help documentation, and supports complex command structures with subcommands. Whether you're a beginner or an experienced Python developer, Typer can significantly enhance your productivity in CLI development.\n", - "\n", - "Happy coding with Typer! 🐍✨" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pages/blog/typer-a-python-library-for-building-cli-applications/index.qmd b/pages/blog/typer-a-python-library-for-building-cli-applications/index.qmd new file mode 100644 index 000000000..8cf59f693 --- /dev/null +++ b/pages/blog/typer-a-python-library-for-building-cli-applications/index.qmd @@ -0,0 +1,288 @@ +--- +title: "Typer: A Python Library for Building CLI Applications" +slug: typer-a-python-library-for-building-cli-applications +date: 2024-01-11 +authors: ["Ivan Ogasawara"] +tags: [open-source, cli, python] +categories: [python] +description: | + Typer is an exciting library for Python developers, designed to make the creation of + command-line interface (CLI) applications not just easier, but also more enjoyable. + Built on top of the well-known Click library, Typer leverages Python 3.6+ features, + like type hints, to define CLI commands in a straightforward and intuitive way. +thumbnail: "/header.svg" +template: "blog-post.html" +--- +# Typer: A Python Library for Building CLI Applications + +## What is Typer? + +Typer is an exciting library for Python developers, designed to make the creation of command-line interface (CLI) applications not just easier, but also more enjoyable. Built on top of the well-known Click library, Typer leverages Python 3.6+ features, like type hints, to define CLI commands in a straightforward and intuitive way. + +## Why Choose Typer? + +- **Simplicity**: With Typer, you can create powerful CLI applications using minimal code. +- **Type Hints**: Leverages Python's type hints for parameter declaration, reducing errors and improving code clarity. +- **Automatic Help**: Generates help text and error messages based on your code. +- **Subcommands**: Supports nested commands, allowing complex CLI applications. + +## Getting Started with Typer + +### Installation + +To begin using Typer, you first need to install it. You can easily do this using pip: + + +```python +!pip install typer -q +``` + +### Creating Your First Typer Application + +Let's start with a simple example. We'll create an application that greets a user. + +First, import Typer and create an instance of it: + + +```python +import typer + +app = typer.Typer() +``` + +Now, define a function that will act as your command. Use type hints for function arguments: + + +```python +@app.command() +def greet( + name: str = typer.Option( + "--name", + "-n", + help="The name of the person to greet." + ) +) -> None: + """Greets the user by name.""" + typer.echo(f"Hello {name}!") +``` + +To run this application, use the following code block at the end of your script: + +```python +if __name__ == "__main__": + app() +``` + +### Running the Application + +Save the script as `greet.py` and run it from the command line: + + +```python +%%writefile greet.py + +import typer + +app = typer.Typer() + +@app.command() +def greet( + name: str = typer.Option( + ..., + "--name", + help="The name of the person to greet." + ) +) -> None: + """Greets the user by name.""" + typer.echo(f"Hello {name}!") + +if __name__ == "__main__": + app() +``` + + Overwriting greet.py + + + +```python +!python greet.py --name Alice +``` + + Hello Alice! + + +### Help Documentation + +Typer automatically generates help documentation for your application. Try running: + + +```python +!python greet.py --help +``` + + Usage: greet.py [OPTIONS] + + Greets the user by name. + + Options: + --name TEXT The name of the person to greet. [required] + --install-completion [bash|zsh|fish|powershell|pwsh] + Install completion for the specified shell. + --show-completion [bash|zsh|fish|powershell|pwsh] + Show completion for the specified shell, to + copy it or customize the installation. + --help Show this message and exit. + + +You'll get a detailed description of how to use the command, including available options. + +## Working with Subcommands + +Typer supports subcommands, allowing you to build more complex applications. In the following example, the script is structured around a main Typer application (app) and two sub-applications (app_user and app_product). This hierarchical structure is a hallmark of Typer, allowing for the organization of commands into distinct categories – in this case, user-related and product-related operations. Such an approach not only enhances the readability and maintainability of the code but also provides a more intuitive interface for the end users. They can easily navigate through the different functionalities of the application, whether it's creating or updating users, or handling product information. + + +```python +%%writefile ecommerce.py +import typer + +from typer import Context, Option + + +app = typer.Typer(help="Operations for e-commerce.") +app_user = typer.Typer(help="Operations for user model.") +app_product = typer.Typer(help="Operations for product model.") + +app.add_typer(app_user, name="user") +app.add_typer(app_product, name="product") + +@app.callback(invoke_without_command=True) +def main( + ctx: Context, + version: bool = Option( + None, + "--version", + "-v", + is_flag=True, + help="Show the version and exit.", + ), +) -> None: + """Process envers for specific flags, otherwise show the help menu.""" + if version: + __version__ = "0.1.0" + typer.echo(f"Version: {__version__}") + raise typer.Exit() + + if ctx.invoked_subcommand is None: + typer.echo(ctx.get_help()) + raise typer.Exit(0) + +@app_user.command("create") +def user_create(name: str = typer.Argument(..., help="Name of the user to create.")) -> None: + """Create a new user with the given name.""" + print(f"Creating user: {name} - Done") + + +@app_user.command("update") +def user_update(name: str = typer.Argument(..., help="Name of the user to update.")) -> None: + """Update user data with the given name.""" + print(f"Updating user: {name} - Done") + +@app_product.command("create") +def product_create(name: str = typer.Argument(..., help="Name of the product to create.")) -> None: + """Create a new product with the given name.""" + print(f"Creating product: {name} - Done") + + +@app_product.command("update") +def product_update(name: str = typer.Argument(..., help="Name of the product to update.")) -> None: + """Update a product with the given name.""" + print(f"Updating product: {name} - Done") + + +if __name__ == "__main__": + app() +``` + + Overwriting ecommerce.py + + +A key feature demonstrated in the script is the use of the callback function with the invoke_without_command=True parameter. This setup enables the execution of specific code (like displaying the version or help text) before any subcommands are processed. It's a powerful tool for handling pre-command logic or global options that apply to the entire CLI application. + +Moreover, the script showcases the simplicity and elegance of defining commands in Typer. Each operation, such as creating or updating users and products, is defined as a function, with parameters automatically translated into command-line options or arguments. This approach not only makes the code more readable but also leverages Python's type hints to ensure that the command-line arguments are correctly interpreted, providing a seamless and error-free user experience. + +In the following lines, there are some examples of the CLI call with different parameters. + + +```python +!python ecommerce.py --help +``` + + Usage: ecommerce.py [OPTIONS] COMMAND [ARGS]... + + Operations for e-commerce. + + Options: + -v, --version Show the version and exit. + --install-completion [bash|zsh|fish|powershell|pwsh] + Install completion for the specified shell. + --show-completion [bash|zsh|fish|powershell|pwsh] + Show completion for the specified shell, to + copy it or customize the installation. + --help Show this message and exit. + + Commands: + product Operations for product model. + user Operations for user model. + + + +```python +!python ecommerce.py --version +``` + + Version: 0.1.0 + + + +```python +!python ecommerce.py user --help +``` + + Usage: ecommerce.py user [OPTIONS] COMMAND [ARGS]... + + Operations for user model. + + Options: + --help Show this message and exit. + + Commands: + create Create a new user with the given name. + update Update user data with the given name. + + + +```python +!python ecommerce.py user create --help +``` + + Usage: ecommerce.py user create [OPTIONS] NAME + + Create a new user with the given name. + + Arguments: + NAME Name of the user to create. [required] + + Options: + --help Show this message and exit. + + + +```python +!python ecommerce.py product --help +``` + +## Conclusion + +Typer is a powerful yet straightforward tool for building CLI applications in Python. By leveraging Python's type hints, it offers an intuitive way to define commands and parameters, automatically handles help documentation, and supports complex command structures with subcommands. Whether you're a beginner or an experienced Python developer, Typer can significantly enhance your productivity in CLI development. + +Happy coding with Typer! 🐍✨ diff --git a/pages/blog/unlocking-the-power-of-multiple-dispatch-in-python-with-plum-dispatch/index.ipynb b/pages/blog/unlocking-the-power-of-multiple-dispatch-in-python-with-plum-dispatch/index.ipynb deleted file mode 100644 index b201f1fd0..000000000 --- a/pages/blog/unlocking-the-power-of-multiple-dispatch-in-python-with-plum-dispatch/index.ipynb +++ /dev/null @@ -1,216 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "id": "f170f83a-7776-4ed8-b295-4eb57b5d0219", - "metadata": {}, - "source": [ - "---\n", - "title: \"Unlocking the Power of Multiple Dispatch in Python with Plum-Dispatch\"\n", - "slug: unlocking-the-power-of-multiple-dispatch-in-python-with-plum-dispatch\n", - "date: 2024-01-05\n", - "authors: [\"Ivan Ogasawara\"]\n", - "tags: [open-source, multiple-dispatch, python]\n", - "categories: [python]\n", - "description: |\n", - " Python, known for its simplicity and readability, sometimes requires a bit of\n", - " creativity when it comes to implementing certain programming paradigms. One such\n", - " paradigm is multiple dispatch (or multimethods), which allows functions to behave\n", - " differently based on the type of their arguments. This is where plum-dispatch\n", - " comes into play.\n", - "thumbnail: \"/header.png\"\n", - "template: \"blog-post.html\"\n", - "---" - ] - }, - { - "cell_type": "markdown", - "id": "46aadcef-e7c2-480a-bf3a-bbe20d09cc3b", - "metadata": {}, - "source": [ - "Python, known for its simplicity and readability, sometimes requires a bit of creativity when it comes to implementing certain programming paradigms. One such paradigm is multiple dispatch (or multimethods), which allows functions to behave differently based on the type of their arguments. While not natively supported in Python, this feature can be incredibly powerful, particularly in complex applications such as mathematical computations, data processing, or when working with abstract syntax trees (ASTs). This is where `plum-dispatch` comes into play.\n", - "\n", - "## What is Multiple Dispatch?\n", - "\n", - "Multiple dispatch is a feature where the function to be executed is determined by the types of multiple arguments. This is different from single dispatch (which Python supports natively via the `functools.singledispatch` decorator), where the function called depends only on the type of the first argument.\n", - "\n", - "## Introducing Plum-Dispatch\n", - "\n", - "`plum-dispatch` is a Python library that provides an efficient and easy-to-use implementation of multiple dispatch. It allows you to define multiple versions of a function, each tailored to different types of input arguments.\n", - "\n", - "### Installation\n", - "\n", - "First things first, let's install `plum-dispatch`:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "5d233782-8974-4758-aac8-0a6cfe757376", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install plum-dispatch -q" - ] - }, - { - "cell_type": "markdown", - "id": "84a8444d-6f2b-442e-8ab8-44f92c645c01", - "metadata": {}, - "source": [ - "### Basic Usage\n", - "\n", - "To demonstrate the basic usage of `plum-dispatch`, let's start with a simple example. Suppose we have a function that needs to behave differently when passed an integer versus when it's passed a string." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "8a1320a3-4531-4d60-80af-4c8ae6301ad4", - "metadata": {}, - "outputs": [], - "source": [ - "from plum import dispatch\n", - "\n", - "\n", - "class Processor:\n", - " @dispatch\n", - " def process(self, data: int):\n", - " return f\"Processing integer: {data}\"\n", - "\n", - " @dispatch\n", - " def process(self, data: str):\n", - " return f\"Processing string: {data}\"" - ] - }, - { - "cell_type": "markdown", - "id": "b396547c-3601-4d32-b6b3-25eba3c13be0", - "metadata": {}, - "source": [ - "In this example, `Processor` has two `process` methods, one for integers and one for strings. `plum-dispatch` takes care of determining which method to call based on the type of `data`." - ] - }, - { - "cell_type": "markdown", - "id": "6f2f654d-c142-408b-981c-ea0f4875a36d", - "metadata": {}, - "source": [ - "### Advanced Example: Working with ASTs\n", - "\n", - "`plum-dispatch` shines in more complex scenarios, such as when working with different types of nodes in an abstract syntax tree. Let's create a simple AST representation with different node types and a visitor class to process these nodes." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "4314c83a-8c69-41e7-be27-7738cd1003a9", - "metadata": {}, - "outputs": [], - "source": [ - "class StringNode:\n", - " def __init__(self, value):\n", - " self.value = value\n", - "\n", - "class NumberNode:\n", - " def __init__(self, value):\n", - " self.value = value\n", - "\n", - "class BaseASTVisitor:\n", - " @dispatch\n", - " def visit(self, node: StringNode):\n", - " raise Exception(\"Not implemented yet.\")\n", - "\n", - " @dispatch\n", - " def visit(self, node: NumberNode):\n", - " raise Exception(\"Not implemented yet.\")\n", - "\n", - "class ASTVisitor(BaseASTVisitor):\n", - " @dispatch\n", - " def visit(self, node: StringNode):\n", - " return f\"Visited StringNode with value: {node.value}\"\n", - "\n", - " @dispatch\n", - " def visit(self, node: NumberNode):\n", - " return f\"Visited NumberNode with value: {node.value}\"" - ] - }, - { - "cell_type": "markdown", - "id": "d04e9401-e2a3-415a-9b85-0d1e0bf459e6", - "metadata": {}, - "source": [ - "With `plum-dispatch`, our `ASTVisitor` can have a single `visit` method that behaves differently depending on whether it's visiting a `StringNode` or a `NumberNode`." - ] - }, - { - "cell_type": "markdown", - "id": "312c3a2f-27b4-4abc-ab0d-ab6cd531066d", - "metadata": {}, - "source": [ - "### Putting It All Together\n", - "Now, let's see `plum-dispatch` in action:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "a321b28b-25ab-47a7-bd28-bbed52107952", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Processing integer: 123\n", - "Processing string: abc\n", - "Visited StringNode with value: Hello\n", - "Visited NumberNode with value: 456\n" - ] - } - ], - "source": [ - "processor = Processor()\n", - "print(processor.process(123)) # \"Processing integer: 123\"\n", - "print(processor.process(\"abc\")) # \"Processing string: abc\"\n", - "\n", - "visitor = ASTVisitor()\n", - "print(visitor.visit(StringNode(\"Hello\"))) # \"Visited StringNode with value: Hello\"\n", - "print(visitor.visit(NumberNode(456))) # \"Visited NumberNode with value: 456\"" - ] - }, - { - "cell_type": "markdown", - "id": "1045a8fa-87ca-44fa-86ea-425429377ee1", - "metadata": {}, - "source": [ - "## Conclusion\n", - "\n", - "`plum-dispatch` offers a neat and powerful way to implement multiple dispatch in Python, making your code more modular, readable, and elegant. Whether you're dealing with simple data types or complex structures like ASTs, `plum-dispatch` can help you write more efficient and maintainable code.\n", - "\n", - "For more complex examples and advanced usage, check out the [plum-dispatch documentation](https://github.com/wesselb/plum)." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pages/blog/unlocking-the-power-of-multiple-dispatch-in-python-with-plum-dispatch/index.qmd b/pages/blog/unlocking-the-power-of-multiple-dispatch-in-python-with-plum-dispatch/index.qmd new file mode 100644 index 000000000..1e6bea7cb --- /dev/null +++ b/pages/blog/unlocking-the-power-of-multiple-dispatch-in-python-with-plum-dispatch/index.qmd @@ -0,0 +1,116 @@ +--- +title: "Unlocking the Power of Multiple Dispatch in Python with Plum-Dispatch" +slug: unlocking-the-power-of-multiple-dispatch-in-python-with-plum-dispatch +date: 2024-01-05 +authors: ["Ivan Ogasawara"] +tags: [open-source, multiple-dispatch, python] +categories: [python] +description: | + Python, known for its simplicity and readability, sometimes requires a bit of + creativity when it comes to implementing certain programming paradigms. One such + paradigm is multiple dispatch (or multimethods), which allows functions to behave + differently based on the type of their arguments. This is where plum-dispatch + comes into play. +thumbnail: "/header.png" +template: "blog-post.html" +--- +Python, known for its simplicity and readability, sometimes requires a bit of creativity when it comes to implementing certain programming paradigms. One such paradigm is multiple dispatch (or multimethods), which allows functions to behave differently based on the type of their arguments. While not natively supported in Python, this feature can be incredibly powerful, particularly in complex applications such as mathematical computations, data processing, or when working with abstract syntax trees (ASTs). This is where `plum-dispatch` comes into play. + +## What is Multiple Dispatch? + +Multiple dispatch is a feature where the function to be executed is determined by the types of multiple arguments. This is different from single dispatch (which Python supports natively via the `functools.singledispatch` decorator), where the function called depends only on the type of the first argument. + +## Introducing Plum-Dispatch + +`plum-dispatch` is a Python library that provides an efficient and easy-to-use implementation of multiple dispatch. It allows you to define multiple versions of a function, each tailored to different types of input arguments. + +### Installation + +First things first, let's install `plum-dispatch`: + + +```python +!pip install plum-dispatch -q +``` + +### Basic Usage + +To demonstrate the basic usage of `plum-dispatch`, let's start with a simple example. Suppose we have a function that needs to behave differently when passed an integer versus when it's passed a string. + + +```python +from plum import dispatch + + +class Processor: + @dispatch + def process(self, data: int): + return f"Processing integer: {data}" + + @dispatch + def process(self, data: str): + return f"Processing string: {data}" +``` + +In this example, `Processor` has two `process` methods, one for integers and one for strings. `plum-dispatch` takes care of determining which method to call based on the type of `data`. + +### Advanced Example: Working with ASTs + +`plum-dispatch` shines in more complex scenarios, such as when working with different types of nodes in an abstract syntax tree. Let's create a simple AST representation with different node types and a visitor class to process these nodes. + + +```python +class StringNode: + def __init__(self, value): + self.value = value + +class NumberNode: + def __init__(self, value): + self.value = value + +class BaseASTVisitor: + @dispatch + def visit(self, node: StringNode): + raise Exception("Not implemented yet.") + + @dispatch + def visit(self, node: NumberNode): + raise Exception("Not implemented yet.") + +class ASTVisitor(BaseASTVisitor): + @dispatch + def visit(self, node: StringNode): + return f"Visited StringNode with value: {node.value}" + + @dispatch + def visit(self, node: NumberNode): + return f"Visited NumberNode with value: {node.value}" +``` + +With `plum-dispatch`, our `ASTVisitor` can have a single `visit` method that behaves differently depending on whether it's visiting a `StringNode` or a `NumberNode`. + +### Putting It All Together +Now, let's see `plum-dispatch` in action: + + +```python +processor = Processor() +print(processor.process(123)) # "Processing integer: 123" +print(processor.process("abc")) # "Processing string: abc" + +visitor = ASTVisitor() +print(visitor.visit(StringNode("Hello"))) # "Visited StringNode with value: Hello" +print(visitor.visit(NumberNode(456))) # "Visited NumberNode with value: 456" +``` + + Processing integer: 123 + Processing string: abc + Visited StringNode with value: Hello + Visited NumberNode with value: 456 + + +## Conclusion + +`plum-dispatch` offers a neat and powerful way to implement multiple dispatch in Python, making your code more modular, readable, and elegant. Whether you're dealing with simple data types or complex structures like ASTs, `plum-dispatch` can help you write more efficient and maintainable code. + +For more complex examples and advanced usage, check out the [plum-dispatch documentation](https://github.com/wesselb/plum). diff --git a/pages/blog/working-with-boolean-expressions-in-python/index.ipynb b/pages/blog/working-with-boolean-expressions-in-python/index.ipynb deleted file mode 100644 index f39017259..000000000 --- a/pages/blog/working-with-boolean-expressions-in-python/index.ipynb +++ /dev/null @@ -1,397 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "id": "4a31e70a-75c7-47aa-8793-b44efeaaa0b1", - "metadata": {}, - "source": [ - "---\n", - "title: \"Working with Boolean Expressions in Python\"\n", - "slug: \"working-with-boolean-expressions-in-python\"\n", - "date: 2024-01-31\n", - "authors: [\"Ivan Ogasawara\"]\n", - "tags: [\"boolean expressions\", \"boolean\", \"python\"]\n", - "categories: [\"devops\", \"automation\", \"python\"]\n", - "description: |\n", - " Boolean logic is a crucial component in many programming tasks, especially\n", - " in searching and querying data. In this tutorial, we'll explore how to use boolean.py,\n", - " a Python library, for handling Boolean expressions. \n", - " We'll apply it to medical symptom data as an illustrative example.\n", - "thumbnail: \"/header.png\"\n", - "template: \"blog-post.html\"\n", - "---" - ] - }, - { - "cell_type": "markdown", - "id": "458e17dc-2853-49cd-9ad3-ecd0c617019c", - "metadata": {}, - "source": [ - "# Working with Boolean Expressions in Python\n", - "\n", - "Boolean logic is a crucial component in many programming tasks, especially in searching and querying data. In this tutorial, we'll explore how to use `boolean.py`, a Python library, for handling Boolean expressions. We'll apply it to medical symptom data as an illustrative example.\n", - "\n", - "## Introduction to `boolean.py`\n", - "\n", - "`boolean.py` is a Python library for creating, manipulating, and evaluating Boolean expressions. It's particularly useful in scenarios where you need to parse and evaluate expressions that represent conditions or filters, such as searching for specific symptoms in a medical database.\n", - "\n", - "## Installation\n", - "\n", - "First and foremost, you need to have `boolean.py` installed in your Python environment. You can easily install it using pip. Run the following command:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "d231485b-1aef-45b6-99b6-f4d850238e16", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -q boolean.py" - ] - }, - { - "cell_type": "markdown", - "id": "fefb2dd4-bfa3-4603-96e0-fd520c11d2da", - "metadata": {}, - "source": [ - "## Basic Usage\n", - "\n", - "We begin with basic Boolean operations, which are the foundation of working with this library.\n", - "\n", - "### Creating Boolean Variables" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "ab358eda-ea90-4635-b6cc-ab8ece1a6439", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(Symbol('fever'), Symbol('cough'))" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import boolean\n", - "\n", - "from boolean.boolean import BooleanAlgebra\n", - "\n", - "# Create a Boolean algebra system\n", - "algebra = BooleanAlgebra()\n", - "\n", - "# Define some symptoms as variables\n", - "fever = algebra.Symbol('fever')\n", - "cough = algebra.Symbol('cough')\n", - "\n", - "fever, cough" - ] - }, - { - "cell_type": "markdown", - "id": "0225585b-5fdc-466b-806c-bb9f266d12b6", - "metadata": {}, - "source": [ - "In this code, we import `BooleanAlgebra` from `boolean.py` and create an instance of it. We then define Boolean variables (symbols) for common symptoms. These symbols act as the basic units for our Boolean expressions.\n" - ] - }, - { - "cell_type": "markdown", - "id": "47e97f30-9bfa-4cf2-9ead-829c7f75cb3b", - "metadata": {}, - "source": [ - "### Basic Boolean Operations" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "84a63a66-72e4-430f-b8c1-b27a472c8e02", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "fever&cough\n", - "fever|cough\n", - "~fever\n" - ] - } - ], - "source": [ - "# AND operation\n", - "symptoms_and = fever & cough\n", - "print(symptoms_and)\n", - "\n", - "# OR operation\n", - "symptoms_or = fever | cough\n", - "print(symptoms_or)\n", - "\n", - "# NOT operation\n", - "no_fever = ~fever\n", - "print(no_fever)" - ] - }, - { - "cell_type": "markdown", - "id": "8e68c8b3-fae1-4ffc-8bd8-2682f522ce8b", - "metadata": {}, - "source": [ - "Here, we demonstrate basic Boolean operations: AND, OR, and NOT. These operations are essential in constructing more complex Boolean expressions. For example, `symptoms_and` represents a scenario where a patient has both fever and cough." - ] - }, - { - "cell_type": "markdown", - "id": "183fe7e0-acb6-48dc-b0be-020f7dc51aee", - "metadata": {}, - "source": [ - "## Advanced Expressions\n", - "\n", - "Now, let's create more complex expressions that could simulate queries for medical symptoms.\n", - "\n", - "### Complex Expressions" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "a777a904-405d-45cd-8323-bad8fb398c58", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "((fever|cough)&~headache)&fatigue\n" - ] - } - ], - "source": [ - "headache = algebra.Symbol('headache')\n", - "fatigue = algebra.Symbol('fatigue')\n", - "\n", - "# Complex expression\n", - "complex_symptoms = (fever | cough) & ~headache & fatigue\n", - "print(complex_symptoms)" - ] - }, - { - "cell_type": "markdown", - "id": "931725d2-c48c-4c6c-974d-f9bb229ee12c", - "metadata": {}, - "source": [ - "This complex expression can represent a more specific medical query. For instance, it could be used to find patients who have either fever or cough, do not have a headache, but are experiencing fatigue." - ] - }, - { - "cell_type": "markdown", - "id": "724132e3-b4a1-44ee-ba6b-4c388685f3ce", - "metadata": {}, - "source": [ - "### Evaluating Expressions\n", - "\n", - "We can evaluate these expressions with specific values to simulate checking a patient's symptoms against our criteria." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "9a28ae95-012b-4c77-8329-5dcde7410bb2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Does the patient match the criteria? (((1)|(0))&(~(0)))&(1) = True\n" - ] - } - ], - "source": [ - "# Define truthy and falsy symbols\n", - "true = algebra.TRUE\n", - "false = algebra.FALSE\n", - "\n", - "# Define complex expression\n", - "complex_symptoms = (fever | cough) & ~headache & fatigue\n", - "\n", - "# Define a patient's symptoms using truthy and falsy symbols\n", - "patient_symptoms = {fever: true, cough: false, headache: false, fatigue: true}\n", - "\n", - "# Substitute symbols in the expression with the corresponding patient symptoms\n", - "substituted_expression = complex_symptoms.subs(patient_symptoms)\n", - "\n", - "# Evaluate the expression\n", - "# The expression itself is the result since boolean.py does not evaluate to Python booleans\n", - "result = substituted_expression\n", - "\n", - "print(\"Does the patient match the criteria?\", result, \"=\", result.simplify() == true)\n" - ] - }, - { - "cell_type": "markdown", - "id": "2e5d3c70-2750-4aa9-bdd2-a60a4203671b", - "metadata": {}, - "source": [ - "In this example, we demonstrate how to evaluate a Boolean expression against a set of patient symptoms. We use `.subs()` to replace each symbol in our expression with the corresponding value (symptom presence) from `patient_symptoms`. The `.simplify()` method then evaluates this substituted expression to a Boolean value, indicating whether the patient's symptoms match our query criteria.\n" - ] - }, - { - "cell_type": "markdown", - "id": "d2972b0a-635a-49cb-9527-1d265b43d2bd", - "metadata": {}, - "source": [ - "## Parsing Expressions from Strings\n", - "\n", - "Lastly, we explore parsing Boolean expressions from strings, a powerful feature for dynamic expression construction.\n", - "\n", - "### Parsing Example" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "ff5f2bfb-05cb-4cf5-8b13-23e0ad6de5e8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "parsed expression: (fever|cough)&~headache&fatigue\n", - "Does the patient match the criteria? True\n" - ] - } - ], - "source": [ - "expression_string = \"(fever | cough) & ~headache & fatigue\"\n", - "\n", - "# Parse the expression from string\n", - "parsed_expression = algebra.parse(expression_string)\n", - "\n", - "# Display the parsed expression\n", - "print(\"parsed expression:\", parsed_expression)\n", - "\n", - "# Evaluate with the same patient symptoms\n", - "result = parsed_expression.subs(patient_symptoms)\n", - "print(f\"Does the patient match the criteria? {result.simplify() == true}\")" - ] - }, - { - "cell_type": "markdown", - "id": "618bb340-10f6-44ea-82b1-869a7c090da0", - "metadata": {}, - "source": [ - "Parsing expressions from strings is extremely useful when you need to construct Boolean expressions dynamically, such as from user inputs or configuration files. In this example, we parse a string representing a Boolean expression and then evaluate it as before." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "d452b111-c9fc-4566-8af8-bf552b7184f3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[EE] Unknown token for token: \"\"\" at position: 9\n" - ] - } - ], - "source": [ - "expression_string = '(fever | \"blood cough\") & ~headache & fatigue'\n", - "\n", - "# Parse the expression from string\n", - "try:\n", - " parsed_expression = algebra.parse(expression_string)\n", - "except boolean.ParseError as e:\n", - " print(\"[EE]\", str(e))" - ] - }, - { - "cell_type": "markdown", - "id": "4f042b9c-18f6-4955-9dcf-acb7ef9e3197", - "metadata": {}, - "source": [ - "The string parser doesn't work with complex strings directly, but creating a manual symbol for the disease works fine." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "ff396a7c-f69d-4058-a219-c196d8c4936a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================================================================\n", - "patient without fever\n", - "Does the patient match the criteria? False\n", - "================================================================================\n", - "patient with all symptoms\n", - "Does the patient match the criteria? True\n" - ] - } - ], - "source": [ - "blood_cough = boolean.Symbol(\"blood cough\")\n", - "\n", - "chest_infection = blood_cough & fever\n", - "\n", - "print(\"=\" * 80)\n", - "print(\"patient without fever\")\n", - "chest_infection_patient_symptoms = {blood_cough: true, fever: false}\n", - "result = chest_infection.subs(chest_infection_patient_symptoms)\n", - "print(f\"Does the patient match the criteria? {result.simplify() == true}\")\n", - "\n", - "print(\"=\" * 80)\n", - "print(\"patient with all symptoms\")\n", - "chest_infection_patient_symptoms = {blood_cough: true, fever: true}\n", - "result = chest_infection.subs(chest_infection_patient_symptoms)\n", - "print(f\"Does the patient match the criteria? {result.simplify() == true}\")" - ] - }, - { - "cell_type": "markdown", - "id": "85dd008d-61ca-408c-9928-7cee4029b654", - "metadata": {}, - "source": [ - "## Conclusion\n", - "\n", - "Throughout this tutorial, we have explored how to use `boolean.py` for handling and evaluating Boolean expressions in Python. By starting from basic operations and moving to parsing expressions from strings, we've covered a range of functionalities provided by this library. While we focused on a medical context, the principles and methods are broadly applicable across different domains." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pages/blog/working-with-boolean-expressions-in-python/index.qmd b/pages/blog/working-with-boolean-expressions-in-python/index.qmd new file mode 100644 index 000000000..84777a686 --- /dev/null +++ b/pages/blog/working-with-boolean-expressions-in-python/index.qmd @@ -0,0 +1,215 @@ +--- +title: "Working with Boolean Expressions in Python" +slug: "working-with-boolean-expressions-in-python" +date: 2024-01-31 +authors: ["Ivan Ogasawara"] +tags: ["boolean expressions", "boolean", "python"] +categories: ["devops", "automation", "python"] +description: | + Boolean logic is a crucial component in many programming tasks, especially + in searching and querying data. In this tutorial, we'll explore how to use boolean.py, + a Python library, for handling Boolean expressions. + We'll apply it to medical symptom data as an illustrative example. +thumbnail: "/header.png" +template: "blog-post.html" +--- +# Working with Boolean Expressions in Python + +Boolean logic is a crucial component in many programming tasks, especially in searching and querying data. In this tutorial, we'll explore how to use `boolean.py`, a Python library, for handling Boolean expressions. We'll apply it to medical symptom data as an illustrative example. + +## Introduction to `boolean.py` + +`boolean.py` is a Python library for creating, manipulating, and evaluating Boolean expressions. It's particularly useful in scenarios where you need to parse and evaluate expressions that represent conditions or filters, such as searching for specific symptoms in a medical database. + +## Installation + +First and foremost, you need to have `boolean.py` installed in your Python environment. You can easily install it using pip. Run the following command: + + +```python +!pip install -q boolean.py +``` + +## Basic Usage + +We begin with basic Boolean operations, which are the foundation of working with this library. + +### Creating Boolean Variables + + +```python +import boolean + +from boolean.boolean import BooleanAlgebra + +# Create a Boolean algebra system +algebra = BooleanAlgebra() + +# Define some symptoms as variables +fever = algebra.Symbol('fever') +cough = algebra.Symbol('cough') + +fever, cough +``` + + + + + (Symbol('fever'), Symbol('cough')) + + + +In this code, we import `BooleanAlgebra` from `boolean.py` and create an instance of it. We then define Boolean variables (symbols) for common symptoms. These symbols act as the basic units for our Boolean expressions. + + +### Basic Boolean Operations + + +```python +# AND operation +symptoms_and = fever & cough +print(symptoms_and) + +# OR operation +symptoms_or = fever | cough +print(symptoms_or) + +# NOT operation +no_fever = ~fever +print(no_fever) +``` + + fever&cough + fever|cough + ~fever + + +Here, we demonstrate basic Boolean operations: AND, OR, and NOT. These operations are essential in constructing more complex Boolean expressions. For example, `symptoms_and` represents a scenario where a patient has both fever and cough. + +## Advanced Expressions + +Now, let's create more complex expressions that could simulate queries for medical symptoms. + +### Complex Expressions + + +```python +headache = algebra.Symbol('headache') +fatigue = algebra.Symbol('fatigue') + +# Complex expression +complex_symptoms = (fever | cough) & ~headache & fatigue +print(complex_symptoms) +``` + + ((fever|cough)&~headache)&fatigue + + +This complex expression can represent a more specific medical query. For instance, it could be used to find patients who have either fever or cough, do not have a headache, but are experiencing fatigue. + +### Evaluating Expressions + +We can evaluate these expressions with specific values to simulate checking a patient's symptoms against our criteria. + + +```python +# Define truthy and falsy symbols +true = algebra.TRUE +false = algebra.FALSE + +# Define complex expression +complex_symptoms = (fever | cough) & ~headache & fatigue + +# Define a patient's symptoms using truthy and falsy symbols +patient_symptoms = {fever: true, cough: false, headache: false, fatigue: true} + +# Substitute symbols in the expression with the corresponding patient symptoms +substituted_expression = complex_symptoms.subs(patient_symptoms) + +# Evaluate the expression +# The expression itself is the result since boolean.py does not evaluate to Python booleans +result = substituted_expression + +print("Does the patient match the criteria?", result, "=", result.simplify() == true) + +``` + + Does the patient match the criteria? (((1)|(0))&(~(0)))&(1) = True + + +In this example, we demonstrate how to evaluate a Boolean expression against a set of patient symptoms. We use `.subs()` to replace each symbol in our expression with the corresponding value (symptom presence) from `patient_symptoms`. The `.simplify()` method then evaluates this substituted expression to a Boolean value, indicating whether the patient's symptoms match our query criteria. + + +## Parsing Expressions from Strings + +Lastly, we explore parsing Boolean expressions from strings, a powerful feature for dynamic expression construction. + +### Parsing Example + + +```python +expression_string = "(fever | cough) & ~headache & fatigue" + +# Parse the expression from string +parsed_expression = algebra.parse(expression_string) + +# Display the parsed expression +print("parsed expression:", parsed_expression) + +# Evaluate with the same patient symptoms +result = parsed_expression.subs(patient_symptoms) +print(f"Does the patient match the criteria? {result.simplify() == true}") +``` + + parsed expression: (fever|cough)&~headache&fatigue + Does the patient match the criteria? True + + +Parsing expressions from strings is extremely useful when you need to construct Boolean expressions dynamically, such as from user inputs or configuration files. In this example, we parse a string representing a Boolean expression and then evaluate it as before. + + +```python +expression_string = '(fever | "blood cough") & ~headache & fatigue' + +# Parse the expression from string +try: + parsed_expression = algebra.parse(expression_string) +except boolean.ParseError as e: + print("[EE]", str(e)) +``` + + [EE] Unknown token for token: """ at position: 9 + + +The string parser doesn't work with complex strings directly, but creating a manual symbol for the disease works fine. + + +```python +blood_cough = boolean.Symbol("blood cough") + +chest_infection = blood_cough & fever + +print("=" * 80) +print("patient without fever") +chest_infection_patient_symptoms = {blood_cough: true, fever: false} +result = chest_infection.subs(chest_infection_patient_symptoms) +print(f"Does the patient match the criteria? {result.simplify() == true}") + +print("=" * 80) +print("patient with all symptoms") +chest_infection_patient_symptoms = {blood_cough: true, fever: true} +result = chest_infection.subs(chest_infection_patient_symptoms) +print(f"Does the patient match the criteria? {result.simplify() == true}") +``` + + ================================================================================ + patient without fever + Does the patient match the criteria? False + ================================================================================ + patient with all symptoms + Does the patient match the criteria? True + + +## Conclusion + +Throughout this tutorial, we have explored how to use `boolean.py` for handling and evaluating Boolean expressions in Python. By starting from basic operations and moving to parsing expressions from strings, we've covered a range of functionalities provided by this library. While we focused on a medical context, the principles and methods are broadly applicable across different domains. diff --git a/scripts/check-broken-links-internal.py b/scripts/check-broken-links-internal.py index 9b1efc053..77e1ce6ef 100644 --- a/scripts/check-broken-links-internal.py +++ b/scripts/check-broken-links-internal.py @@ -90,6 +90,10 @@ def check_links(folder_path: Path, port: int): if __name__ == "__main__": + # Repo root = parent of directory containing this script + _repo_root = Path(__file__).resolve().parents[1] + _default_build = _repo_root / "build" + parser = argparse.ArgumentParser( description="Check for broken internal links." ) @@ -97,16 +101,23 @@ def check_links(folder_path: Path, port: int): "--port", "-p", default=8000, type=int, help="Port for HTTP server." ) parser.add_argument( - "--folder", "-f", default="build", type=str, help="Folder to serve." + "--folder", + "-f", + default=None, + type=str, + help="Folder to serve (default: repo root / build).", ) args = parser.parse_args() - folder_path = Path(args.folder) + folder_path = Path(args.folder) if args.folder else _default_build HTTP_PORT = args.port if not folder_path.exists(): - print(f"Error: The path {folder_path} doesn't exist.") - sys.exit(1) + print( + "[WW] Build folder not found; skipping internal link check. " + "Run 'makim pages.build' first." + ) + sys.exit(0) # Start the HTTP server server = start_http_server(folder_path, HTTP_PORT)