From efd5f2d59489eea4520a6cd6c5ccf9db056c2755 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Wed, 12 Mar 2025 17:45:53 -0700 Subject: [PATCH 01/22] Fix formatting and improve clarity in documentation files --- pages/tech/conduct.md | 2 +- pages/tech/data-science/contributing.md | 5 +++-- pages/tech/data-science/environments.md | 5 +++-- pages/tech/data-science/index.md | 6 ++++-- pages/tech/data-science/style.md | 6 ++++-- pages/tech/github.md | 2 +- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/pages/tech/conduct.md b/pages/tech/conduct.md index ef23e86..51b4ad6 100644 --- a/pages/tech/conduct.md +++ b/pages/tech/conduct.md @@ -62,7 +62,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -admin@carbonplan.org. +. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/pages/tech/data-science/contributing.md b/pages/tech/data-science/contributing.md index a8d9381..245e6ff 100644 --- a/pages/tech/data-science/contributing.md +++ b/pages/tech/data-science/contributing.md @@ -22,5 +22,6 @@ This page serves as a general contribution guide for CarbonPlan data science pro - more? export default ({ children }) => ( -
{children}
-) + +{' '} +
{children}
) diff --git a/pages/tech/data-science/environments.md b/pages/tech/data-science/environments.md index 1ca2185..862ab75 100644 --- a/pages/tech/data-science/environments.md +++ b/pages/tech/data-science/environments.md @@ -7,5 +7,6 @@ import Section from '../../../components/section' - testing environments export default ({ children }) => ( -
{children}
-) + +{' '} +
{children}
) diff --git a/pages/tech/data-science/index.md b/pages/tech/data-science/index.md index 3b96f6b..5a8da04 100644 --- a/pages/tech/data-science/index.md +++ b/pages/tech/data-science/index.md @@ -26,5 +26,7 @@ pip install "carbonplan[data,styles]" - [Python Environments Guide](data-science/environments) export default ({ children }) => ( -
{children}
-) + +{' '} + +
{children}
) diff --git a/pages/tech/data-science/style.md b/pages/tech/data-science/style.md index adc0bfb..1e7ac26 100644 --- a/pages/tech/data-science/style.md +++ b/pages/tech/data-science/style.md @@ -135,5 +135,7 @@ repos: ``` export default ({ children }) => ( -
{children}
-) + +{' '} + +
{children}
) diff --git a/pages/tech/github.md b/pages/tech/github.md index 4e51def..d63f8fa 100644 --- a/pages/tech/github.md +++ b/pages/tech/github.md @@ -10,7 +10,7 @@ There are many tutorials (i.e. [here](https://lab.github.com/githubtraining/intr ### Navigating the CarbonPlan Organization -GitHub Organizations are shared accounts that allow organizations, like CarbonPlan, collaborate on multiple projects at once. CarbonPlan's GitHub organization (https://github.com/carbonplan) is the central development location for all of our data-science and front-end projects. Important featurs at the organization level include: +GitHub Organizations are shared accounts that allow organizations, like CarbonPlan, collaborate on multiple projects at once. CarbonPlan's GitHub organization () is the central development location for all of our data-science and front-end projects. Important featurs at the organization level include: - [Repositories](https://github.com/orgs/carbonplan/repositories): a listing of our projects (aka repositories) - [People](https://github.com/orgs/carbonplan/people): a listing of members of the CarbonPlan GitHub organization and outside collaborators From 0e009c34c7232cedef51c5c55036c14e65197bbd Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Wed, 12 Mar 2025 17:48:32 -0700 Subject: [PATCH 02/22] Remove unnecessary whitespace in contributing, environments, and style guide files --- pages/tech/data-science/contributing.md | 1 - pages/tech/data-science/environments.md | 1 - pages/tech/data-science/style.md | 2 -- 3 files changed, 4 deletions(-) diff --git a/pages/tech/data-science/contributing.md b/pages/tech/data-science/contributing.md index 245e6ff..9356308 100644 --- a/pages/tech/data-science/contributing.md +++ b/pages/tech/data-science/contributing.md @@ -23,5 +23,4 @@ This page serves as a general contribution guide for CarbonPlan data science pro export default ({ children }) => ( -{' '}
{children}
) diff --git a/pages/tech/data-science/environments.md b/pages/tech/data-science/environments.md index 862ab75..44e288a 100644 --- a/pages/tech/data-science/environments.md +++ b/pages/tech/data-science/environments.md @@ -8,5 +8,4 @@ import Section from '../../../components/section' export default ({ children }) => ( -{' '}
{children}
) diff --git a/pages/tech/data-science/style.md b/pages/tech/data-science/style.md index 1e7ac26..cd9c313 100644 --- a/pages/tech/data-science/style.md +++ b/pages/tech/data-science/style.md @@ -136,6 +136,4 @@ repos: export default ({ children }) => ( -{' '} -
{children}
) From 860f486860f34e2ef056d77e18c6c63137004011 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Wed, 12 Mar 2025 17:49:43 -0700 Subject: [PATCH 03/22] Remove unnecessary whitespace in data science index file --- pages/tech/data-science/index.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/pages/tech/data-science/index.md b/pages/tech/data-science/index.md index 5a8da04..e372af6 100644 --- a/pages/tech/data-science/index.md +++ b/pages/tech/data-science/index.md @@ -27,6 +27,4 @@ pip install "carbonplan[data,styles]" export default ({ children }) => ( -{' '} -
{children}
) From da9bc3518d58e1f4feaf8f88b1cbaa6510e0f9a2 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Wed, 12 Mar 2025 17:52:33 -0700 Subject: [PATCH 04/22] Fix formatting and improve links in conduct and GitHub documentation --- pages/tech/conduct.md | 2 +- pages/tech/github.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/tech/conduct.md b/pages/tech/conduct.md index 51b4ad6..9258e1e 100644 --- a/pages/tech/conduct.md +++ b/pages/tech/conduct.md @@ -62,7 +62,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -. +[admin@carbonplan.org](admin@carbonplan.org). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/pages/tech/github.md b/pages/tech/github.md index d63f8fa..2e31641 100644 --- a/pages/tech/github.md +++ b/pages/tech/github.md @@ -10,7 +10,7 @@ There are many tutorials (i.e. [here](https://lab.github.com/githubtraining/intr ### Navigating the CarbonPlan Organization -GitHub Organizations are shared accounts that allow organizations, like CarbonPlan, collaborate on multiple projects at once. CarbonPlan's GitHub organization () is the central development location for all of our data-science and front-end projects. Important featurs at the organization level include: +GitHub Organizations are shared accounts that allow organizations, like CarbonPlan, collaborate on multiple projects at once. CarbonPlan's GitHub organization ([https://github.com/carbonplan](https://github.com/carbonplan)) is the central development location for all of our data-science and front-end projects. Important featurs at the organization level include: - [Repositories](https://github.com/orgs/carbonplan/repositories): a listing of our projects (aka repositories) - [People](https://github.com/orgs/carbonplan/people): a listing of members of the CarbonPlan GitHub organization and outside collaborators From 6c27fc1a3b155ca7bc42c93807d163b2244a8f98 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Wed, 12 Mar 2025 18:08:31 -0700 Subject: [PATCH 05/22] Update data science documentation to reflect new tools and installation instructions --- pages/tech/data-science/index.md | 4 +- pages/tech/data-science/style.md | 149 ++++++++++++++++++------------- 2 files changed, 90 insertions(+), 63 deletions(-) diff --git a/pages/tech/data-science/index.md b/pages/tech/data-science/index.md index e372af6..23d5740 100644 --- a/pages/tech/data-science/index.md +++ b/pages/tech/data-science/index.md @@ -2,7 +2,7 @@ import Section from '../../../components/section' # Data Science Overview -Our data science toolset is built on top of the open source Scientific Python ecosystem. We make extensive use of open source frameworks such as the [Pangeo Project](https://pangeo.io/) and open source cloud infrastructure such as [Kubernetes](https://kubernetes.io/). +Our data science toolset is built on top of the open source Scientific Python ecosystem. We make extensive use of open source frameworks such as the [Xarray](https://xarray.dev/) package for working with multi-dimensional arrays, and the [Dask](https://dask.org/) package for parallel computing. We also rely on a number of other open source packages for data analysis and visualization. ## Core projects @@ -15,7 +15,7 @@ We maintain a few core projects that help tie together CarbonPlan's data science All of these projects can be installed from [PyPI](https://pypi.org/search/?q=carbonplan): ``` -pip install "carbonplan[data,styles]" +python -m pip install "carbonplan[data,styles]" ``` ## Guides diff --git a/pages/tech/data-science/style.md b/pages/tech/data-science/style.md index cd9c313..9befe3f 100644 --- a/pages/tech/data-science/style.md +++ b/pages/tech/data-science/style.md @@ -12,47 +12,86 @@ We mostly follow the standard Python style conventions from [PEP8](https://www.p We use a series of code linters to maintain consistent formatting across our projects. Most projects will also use `pre-commit` to automate regular execution of the linters. The linters are also regularly run as part of our continuous integration and [testing](testing) suite. -### Black +### Ruff -[Black](https://black.readthedocs.io/en/stable/index.html) is an opinionated PEP-compliant code formatter. We use Black's default settings with a few minor adjustments: +[Ruff](https://docs.astral.sh/ruff) is a Python linter and code formatter which supports a wide range of linting rules, many of which are derived from the popular tools like [Flake8](https://flake8.pycqa.org/en/latest/) and [isort](https://pycqa.github.io/isort/), [pyupgrade](https://github.com/asottile/pyupgrade) and others. Ruff provides a formatter designed to be used as a drop-in replacement for [Black](https://black.readthedocs.io/en/stable/index.html) -- an opinionated PEP-compliant code formatter. -Example `pyproject.toml`: - -```ini -[tool.black] -line-length = 100 -skip-string-normalization = true -``` - -### Flake8 - -[Flake8](https://flake8.pycqa.org/en/latest/) provides additional code formatting sytle checks. - -Example `setup.cfg`: - -```ini -[flake8] -ignore = E203,E266,E501,W503,E722,E402,C901 -max-line-length = 100 -max-complexity = 18 -select = B,C,E,F,W,T4,B9 -``` - -### isort - -[isort](https://pycqa.github.io/isort/) automatically sorts Python imports. +We use Ruff's default settings with a few minor adjustments: -Example `setup.cfg` +Example `pyproject.toml`: ```ini -[isort] -known_first_party= -known_third_party= -multi_line_output=3 -include_trailing_comma=True -force_grid_wrap=0 -combine_as_imports=True -line_length=100 +[tool.ruff] + extend-include = ["*.ipynb"] + line-length = 100 + target-version = "py310" + + builtins = ["ellipsis"] + # Exclude a variety of commonly ignored directories. + exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + ] +[tool.ruff.lint] + ignore = [ + "E501", # Conflicts with ruff format + "E721", # Comparing types instead of isinstance + "E741", # Ambiguous variable names + ] + per-file-ignores = {} + select = [ + # Pyflakes + "F", + # Pycodestyle + "E", + "W", + # isort + "I", + # Pyupgrade + "UP", + ] + +[tool.ruff.lint.mccabe] + max-complexity = 18 + +[tool.ruff.lint.isort] + combine-as-imports = true + known-first-party = [] + +[tool.ruff.format] + docstring-code-format = true + quote-style = "single" + +[tool.ruff.lint.pydocstyle] + convention = "numpy" + +[tool.ruff.lint.pyupgrade] + # Preserve types, even if a file imports `from __future__ import annotations`. + keep-runtime-typing = true ``` ## Pre-commmit @@ -64,7 +103,7 @@ line_length=100 Before using Pre-commit, a command line utility needs to be added to your development environment. Pre-commit can be [installed](https://pre-commit.com/#installation) using a variety of package managers including PyPI, Homebrew, and Conda. ``` -pip install pre-commit +python -m pip install pre-commit # or conda install -c conda-forge pre-commit # or @@ -98,40 +137,28 @@ The hooks included in the pre-commit script are defined in the `.pre-commit-conf ```yaml repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-docstring-first - id: check-json - id: check-yaml - - id: pretty-format-json - args: ['--autofix', '--indent=2', '--no-sort-keys'] + - id: double-quote-string-fixer + - id: debug-statements + - id: mixed-line-ending - - repo: https://github.com/ambv/black - rev: 21.4b2 - hooks: - - id: black - args: ['--line-length', '100'] - - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.1 - hooks: - - id: flake8 - - - repo: https://github.com/asottile/seed-isort-config - rev: v2.2.0 - hooks: - - id: seed-isort-config - - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.8.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: 'v0.9.9' hooks: - - id: isort + - id: ruff + args: ['--fix'] + - id: ruff-format - - repo: https://github.com/deathbeds/prenotebook - rev: f5bdb72a400f1a56fe88109936c83aa12cc349fa + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v4.0.0-alpha.8 hooks: - - id: prenotebook + - id: prettier ``` export default ({ children }) => ( From 77d1bc9fcb8744c2277e209c1c17c8690d3aca51 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Wed, 12 Mar 2025 19:07:38 -0700 Subject: [PATCH 06/22] Improve documentation by adding email link, correcting guide name, and enhancing testing section with TDD practices --- pages/tech/conduct.md | 2 +- pages/tech/data-science/index.md | 2 +- pages/tech/data-science/style.md | 3 +++ pages/tech/data-science/testing.md | 30 +++++++++++++++++++++++++---- public/all-green-build.png | Bin 0 -> 35390 bytes 5 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 public/all-green-build.png diff --git a/pages/tech/conduct.md b/pages/tech/conduct.md index 9258e1e..83fb1bb 100644 --- a/pages/tech/conduct.md +++ b/pages/tech/conduct.md @@ -62,7 +62,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -[admin@carbonplan.org](admin@carbonplan.org). +[admin@carbonplan.org](mailto:admin@carbonplan.org). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/pages/tech/data-science/index.md b/pages/tech/data-science/index.md index 23d5740..2ad2625 100644 --- a/pages/tech/data-science/index.md +++ b/pages/tech/data-science/index.md @@ -20,7 +20,7 @@ python -m pip install "carbonplan[data,styles]" ## Guides -- [Contribution Guid](data-science/contributing) +- [Contribution Guide](data-science/contributing) - [Style Guide](data-science/style) - [Testing Guide](data-science/testing) - [Python Environments Guide](data-science/environments) diff --git a/pages/tech/data-science/style.md b/pages/tech/data-science/style.md index 9befe3f..af379f9 100644 --- a/pages/tech/data-science/style.md +++ b/pages/tech/data-science/style.md @@ -1,4 +1,5 @@ import Section from '../../../components/section' +import Sidenote from '../../../components/sidenote' # Python Style Guide @@ -130,6 +131,8 @@ pre-commit run [--all-files] The standard execution will only run pre-commit on modified files. Adding the `--all-files` option will run the pre-commit script on all files within the respository. +Note that `pre-commit` can sometimes be difficult to satisfy. You can often get around this by running `git commit --no-verify` to skip the pre-commit hook. However, this is not recommended as it can lead to inconsistent code formatting and style issues. + ### Pre-commit configuration The hooks included in the pre-commit script are defined in the `.pre-commit-config.yaml` file in each repository. Below is an example of a standard pre-commit configuration. diff --git a/pages/tech/data-science/testing.md b/pages/tech/data-science/testing.md index ec74861..5ab81bf 100644 --- a/pages/tech/data-science/testing.md +++ b/pages/tech/data-science/testing.md @@ -10,12 +10,34 @@ Continuous integration (CI) is the practice of regularly merging, deploying, and - [Vercel](https://vercel.com) - [Pre-commit.ci](https://pre-commit.ci/) -## Testing Python code +Most of our projects are set up to run test suites automatically on every push and pull request to the repository. This helps us catch errors early and ensures that our code is always in a releasable state. A pull request will be considered "ready to merge" when you have an 'all green' status on all of the CI services that are enabled for the repository. Below is an example of an 'all green' status on a pull request: -pytest +![all green status](/all-green-build.png) -## CI services +Note that each time you push new commits to GitHub, the CI services will trigger a new build. If the existing build is still running, it will be canceled and a new build will be started. -- dependabot (and other services) +## Test-driven development + +We encourage contributors to embrace [test-driven development (TDD) practices](https://en.wikipedia.org/wiki/Test-driven_development). This development style strongly encourages writing tests before writing code. In other words, a developer writes a test that fails, then writes the code to make the test pass. This approach helps ensure that the code is correct and that it meets the requirements of the test. For scenarios where one is dealing with additional use cases, it is often worth writing different tests for each use case. This helps ensure that the tests are comprehensive and maintainable. + +Our test suites are written using the [pytest](https://docs.pytest.org/en/stable/) framework. Pytest is a powerful and flexible testing framework that makes it easy to write and run tests. It also provides a number of features that make it easy to write tests for complex scenarios. + +## Writing tests + +All tests should go in the `tests` subdirectory of the project. The test files should be named `test_*.py` and the test functions should be named `test_*`. This is a convention that is recognized by pytest and other testing frameworks. + +## Running the test suite + +To run the test suite, you can use the following command from the root of the cloned repository: + +``` +python -m pytest +``` + +Often you will want to run the test suite on a specific file or directory. You can do this by specifying the path to the file or directory as an argument to the `pytest` command: + +``` +python -m pytest path/to/test_file.py +``` export default ({ children }) =>
{children}
diff --git a/public/all-green-build.png b/public/all-green-build.png new file mode 100644 index 0000000000000000000000000000000000000000..72dadbcf42b5668fd3d8736a3f6a65dc2531aefa GIT binary patch literal 35390 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 Date: Thu, 13 Mar 2025 11:26:49 -0700 Subject: [PATCH 07/22] Enhance data science environments documentation with detailed descriptions of local and docker environments, including recommended tools and their purposes. --- pages/tech/data-science/environments.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pages/tech/data-science/environments.md b/pages/tech/data-science/environments.md index 44e288a..eee3054 100644 --- a/pages/tech/data-science/environments.md +++ b/pages/tech/data-science/environments.md @@ -2,9 +2,28 @@ import Section from '../../../components/section' # Environments +We use a variety of environments to support our data science work. These include: + - local environments - docker environments -- testing environments + +These environments are used to support a range of activities, including: + +- local development +- continuous integration +- deployment +- reproducibility +- collaboration + +Each environment has its own set of tools and configurations, and we use them in different ways depending on the task at hand. For example, we might use a local environment for development and testing, while using a docker environment for deployment and reproducibility. + +## Local environments + +We recommend using [mamba](https://mamba.readthedocs.io/en/latest/) for managing local environments. Mamba is a fast, robust, and cross-platform package manager that can be used to create and manage conda environments. It is a drop-in replacement for conda, and it is fully compatible with the conda ecosystem. + +## Docker environments + +We use [Docker](https://www.docker.com/) to create and manage containerized environments. These environments are used for deployment, testing, and reproducibility. We publish our docker images to [Quay.io](https://quay.io/organization/carbonplan). export default ({ children }) => ( From 5458d2ed816c3be4cef1fceff130a1da3a8337d2 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Thu, 13 Mar 2025 11:28:22 -0700 Subject: [PATCH 08/22] Refactor contributing guide to improve structure and clarity --- pages/tech/data-science/contributing.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pages/tech/data-science/contributing.md b/pages/tech/data-science/contributing.md index 9356308..3066952 100644 --- a/pages/tech/data-science/contributing.md +++ b/pages/tech/data-science/contributing.md @@ -7,17 +7,21 @@ This page serves as a general contribution guide for CarbonPlan data science pro - where to start - bug reports and enhancement requests - developing + - link to page on github - creating a development environment - work on a fork/branch - contributing to the documentation (leave as a TODO block) - contributing to the code base + - code standards (link to testing) - code formatting (link to sytle) - test driven development - documenting your code (numpydoc) + - git workflow for committing and pushing your changes, open a pull request - code review + - project versioning - more? From e91be8d77fc74f47c1e67f58412a5521159ecec4 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Thu, 13 Mar 2025 11:58:52 -0700 Subject: [PATCH 09/22] Add links to data science and front-end contributor guides in the index page --- pages/index.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/pages/index.js b/pages/index.js index f16c199..63da8e8 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,6 +1,8 @@ -import { Layout, Row, Column } from '@carbonplan/components' -import Heading from '../components/heading' +import { Column, Layout, Row } from '@carbonplan/components' +import Link from 'next/link' +import { Box, Text } from 'theme-ui' import Card from '../components/card' +import Heading from '../components/heading' const Index = () => { return ( @@ -12,7 +14,21 @@ const Index = () => { > Docs - + + + + + You can also check out our{' '} + + data science + {' '} + and front-end contributor + guides on how to contribute to our projects. + + + + + Date: Thu, 13 Mar 2025 12:55:29 -0700 Subject: [PATCH 10/22] Remove outdated core projects section from data science index page --- pages/tech/data-science/index.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/pages/tech/data-science/index.md b/pages/tech/data-science/index.md index 2ad2625..b4157a0 100644 --- a/pages/tech/data-science/index.md +++ b/pages/tech/data-science/index.md @@ -4,20 +4,6 @@ import Section from '../../../components/section' Our data science toolset is built on top of the open source Scientific Python ecosystem. We make extensive use of open source frameworks such as the [Xarray](https://xarray.dev/) package for working with multi-dimensional arrays, and the [Dask](https://dask.org/) package for parallel computing. We also rely on a number of other open source packages for data analysis and visualization. -## Core projects - -We maintain a few core projects that help tie together CarbonPlan's data science work. - -1. [`carbonplan-python`](https://github.com/carbonplan/carbonplan-python): A lightweight namespace package for Python utilities and subprojects -1. [`carbonplan-data`](https://github.com/carbonplan/data): Cross-org data catalogs and utilities -1. [`carbonplan-styles`](https://github.com/carbonplan/styles): Plotting styles for Altair and Matplotlib - -All of these projects can be installed from [PyPI](https://pypi.org/search/?q=carbonplan): - -``` -python -m pip install "carbonplan[data,styles]" -``` - ## Guides - [Contribution Guide](data-science/contributing) From de28538c55fb725a5f9becbf34fe59707f82605a Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Thu, 13 Mar 2025 12:59:01 -0700 Subject: [PATCH 11/22] remove section per @norlandrhagen suggestion --- pages/tech/data-science/style.md | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/pages/tech/data-science/style.md b/pages/tech/data-science/style.md index af379f9..912136e 100644 --- a/pages/tech/data-science/style.md +++ b/pages/tech/data-science/style.md @@ -135,34 +135,7 @@ Note that `pre-commit` can sometimes be difficult to satisfy. You can often get ### Pre-commit configuration -The hooks included in the pre-commit script are defined in the `.pre-commit-config.yaml` file in each repository. Below is an example of a standard pre-commit configuration. - -```yaml -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-docstring-first - - id: check-json - - id: check-yaml - - id: double-quote-string-fixer - - id: debug-statements - - id: mixed-line-ending - - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.9.9' - hooks: - - id: ruff - args: ['--fix'] - - id: ruff-format - - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v4.0.0-alpha.8 - hooks: - - id: prettier -``` +The hooks included in the pre-commit script are defined in the `.pre-commit-config.yaml` file in each repository. Below is an example of a standard pre-commit configuration. See this example [pre-commit-config.yaml](https://github.com/carbonplan/carbonplan-cookiecutter-python/blob/main/%7B%7Bcookiecutter.project_name%7D%7D/.pre-commit-config.yaml) file for a more complete example. export default ({ children }) => ( From fbd3a9e610181579b9a381c59f82634791c45a3e Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe <13301940+andersy005@users.noreply.github.com> Date: Thu, 13 Mar 2025 14:07:47 -0700 Subject: [PATCH 12/22] Apply suggestions from code review Co-authored-by: Kata Martin --- pages/index.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pages/index.js b/pages/index.js index 63da8e8..fdf1e98 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,5 +1,4 @@ -import { Column, Layout, Row } from '@carbonplan/components' -import Link from 'next/link' +import { Column, Layout, Link, Row } from '@carbonplan/components' import { Box, Text } from 'theme-ui' import Card from '../components/card' import Heading from '../components/heading' @@ -19,11 +18,10 @@ const Index = () => { You can also check out our{' '} - + data science {' '} - and front-end contributor - guides on how to contribute to our projects. + contributor guide on how to contribute to our projects. From c2f33274a48bd50aa1173cc5a6446665e5b4dd99 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Thu, 13 Mar 2025 14:13:41 -0700 Subject: [PATCH 13/22] Remove front-end documentation and update index page for clarity --- components/contents.js | 6 ------ pages/index.js | 6 ++---- pages/tech/front-end/articles.md | 8 -------- pages/tech/front-end/contributing.md | 11 ----------- pages/tech/front-end/deployment.md | 9 --------- pages/tech/front-end/index.md | 9 --------- pages/tech/front-end/style.md | 11 ----------- pages/tech/front-end/testing.md | 8 -------- pages/tech/index.md | 4 +--- 9 files changed, 3 insertions(+), 69 deletions(-) delete mode 100644 pages/tech/front-end/articles.md delete mode 100644 pages/tech/front-end/contributing.md delete mode 100644 pages/tech/front-end/deployment.md delete mode 100644 pages/tech/front-end/index.md delete mode 100644 pages/tech/front-end/style.md delete mode 100644 pages/tech/front-end/testing.md diff --git a/components/contents.js b/components/contents.js index f3d7abf..c1831b4 100644 --- a/components/contents.js +++ b/components/contents.js @@ -10,10 +10,4 @@ export const contents = { Style: '/tech/data-science/style', Testing: '/tech/data-science/testing', }, - 'Front-end': { - Overview: '/tech/front-end', - Articles: '/tech/front-end/articles', - Contributing: '/tech/front-end/contributing', - Deployment: '/tech/front-end/deployment', - }, } diff --git a/pages/index.js b/pages/index.js index fdf1e98..18137a4 100644 --- a/pages/index.js +++ b/pages/index.js @@ -18,10 +18,8 @@ const Index = () => { You can also check out our{' '} - - data science - {' '} - contributor guide on how to contribute to our projects. + data science contributor + guide on how to contribute to our projects. diff --git a/pages/tech/front-end/articles.md b/pages/tech/front-end/articles.md deleted file mode 100644 index e40877b..0000000 --- a/pages/tech/front-end/articles.md +++ /dev/null @@ -1,8 +0,0 @@ -import Section from '../../../components/section' - -# Articles - -- research articles -- blog articles - -export default ({ children }) =>
{children}
diff --git a/pages/tech/front-end/contributing.md b/pages/tech/front-end/contributing.md deleted file mode 100644 index 09fdd47..0000000 --- a/pages/tech/front-end/contributing.md +++ /dev/null @@ -1,11 +0,0 @@ -import Section from '../../../components/section' - -# Contribution Guide - -- versioning -- linking projects during development -- releasing/publishing - -export default ({ children }) => ( -
{children}
-) diff --git a/pages/tech/front-end/deployment.md b/pages/tech/front-end/deployment.md deleted file mode 100644 index a986e93..0000000 --- a/pages/tech/front-end/deployment.md +++ /dev/null @@ -1,9 +0,0 @@ -import Section from '../../../components/section' - -# Deployment Guide - -- Vercel - -export default ({ children }) => ( -
{children}
-) diff --git a/pages/tech/front-end/index.md b/pages/tech/front-end/index.md deleted file mode 100644 index 4571963..0000000 --- a/pages/tech/front-end/index.md +++ /dev/null @@ -1,9 +0,0 @@ -import Section from '../../../components/section' - -# Front-end Overview - -- toolset -- core projects -- references to guides - -export default ({ children }) =>
{children}
diff --git a/pages/tech/front-end/style.md b/pages/tech/front-end/style.md deleted file mode 100644 index 73765bf..0000000 --- a/pages/tech/front-end/style.md +++ /dev/null @@ -1,11 +0,0 @@ -import Section from '../../../components/section' - -# Style Guide - -- eslint or prettier -- doc strings? -- naming conventions? - -export default ({ children }) => ( -
{children}
-) diff --git a/pages/tech/front-end/testing.md b/pages/tech/front-end/testing.md deleted file mode 100644 index 507c472..0000000 --- a/pages/tech/front-end/testing.md +++ /dev/null @@ -1,8 +0,0 @@ -import Section from '../../../components/section' - -# Testing - -- continuous integration -- \{unit, build, integration, deployment\} testing - -export default ({ children }) =>
{children}
diff --git a/pages/tech/index.md b/pages/tech/index.md index eccd770..ae8d9d7 100644 --- a/pages/tech/index.md +++ b/pages/tech/index.md @@ -2,8 +2,6 @@ import Section from '../../components/section' # Developer Docs -At CarbonPlan, we build a projects using a wide varity of open source tools and technologies. This site provides documentation aimed at supporting our core software, data, and science development activities. - -Our work can be roughly divided into two areas, [front-end](/front-end) and [data-science](/data-science). Because these areas often utilize very different toolsets and development approaches, this site provides individudal guides for each area. +At CarbonPlan, we build projects using a wide varity of open source tools and technologies. This site provides documentation aimed at supporting our core software, data, and science development activities. export default ({ children }) =>
{children}
From 710cb266d94f992ab496d422c5b26e40dee28aff Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Thu, 13 Mar 2025 15:54:13 -0700 Subject: [PATCH 14/22] remove data science section --- components/contents.js | 11 +++++------ pages/index.js | 4 ++-- pages/tech/{data-science => }/contributing.md | 2 +- pages/tech/data-science/index.md | 16 ---------------- pages/tech/{data-science => }/environments.md | 2 +- pages/tech/index.md | 11 ++++++++++- pages/tech/{data-science => }/style.md | 4 ++-- pages/tech/{data-science => }/testing.md | 2 +- 8 files changed, 22 insertions(+), 30 deletions(-) rename pages/tech/{data-science => }/contributing.md (94%) delete mode 100644 pages/tech/data-science/index.md rename pages/tech/{data-science => }/environments.md (96%) rename pages/tech/{data-science => }/style.md (98%) rename pages/tech/{data-science => }/testing.md (98%) diff --git a/components/contents.js b/components/contents.js index c1831b4..2107dba 100644 --- a/components/contents.js +++ b/components/contents.js @@ -3,11 +3,10 @@ export const contents = { Conduct: '/tech/conduct', GitHub: '/tech/github', }, - 'Data Science': { - Overview: '/tech/data-science', - Contributing: '/tech/data-science/contributing', - Environments: '/tech/data-science/environments', - Style: '/tech/data-science/style', - Testing: '/tech/data-science/testing', + 'Contributing Guide': { + Intro: '/tech/contributing', + Environments: '/tech/environments', + Style: '/tech/style', + Testing: '/tech/testing', }, } diff --git a/pages/index.js b/pages/index.js index 18137a4..6ed0dde 100644 --- a/pages/index.js +++ b/pages/index.js @@ -18,8 +18,8 @@ const Index = () => { You can also check out our{' '} - data science contributor - guide on how to contribute to our projects. + contributor guide + on how to contribute to our projects. diff --git a/pages/tech/data-science/contributing.md b/pages/tech/contributing.md similarity index 94% rename from pages/tech/data-science/contributing.md rename to pages/tech/contributing.md index 3066952..2f2dda8 100644 --- a/pages/tech/data-science/contributing.md +++ b/pages/tech/contributing.md @@ -1,4 +1,4 @@ -import Section from '../../../components/section' +import Section from '../../components/section' # Contribution Guide diff --git a/pages/tech/data-science/index.md b/pages/tech/data-science/index.md deleted file mode 100644 index b4157a0..0000000 --- a/pages/tech/data-science/index.md +++ /dev/null @@ -1,16 +0,0 @@ -import Section from '../../../components/section' - -# Data Science Overview - -Our data science toolset is built on top of the open source Scientific Python ecosystem. We make extensive use of open source frameworks such as the [Xarray](https://xarray.dev/) package for working with multi-dimensional arrays, and the [Dask](https://dask.org/) package for parallel computing. We also rely on a number of other open source packages for data analysis and visualization. - -## Guides - -- [Contribution Guide](data-science/contributing) -- [Style Guide](data-science/style) -- [Testing Guide](data-science/testing) -- [Python Environments Guide](data-science/environments) - -export default ({ children }) => ( - -
{children}
) diff --git a/pages/tech/data-science/environments.md b/pages/tech/environments.md similarity index 96% rename from pages/tech/data-science/environments.md rename to pages/tech/environments.md index eee3054..caa797e 100644 --- a/pages/tech/data-science/environments.md +++ b/pages/tech/environments.md @@ -1,4 +1,4 @@ -import Section from '../../../components/section' +import Section from '../../components/section' # Environments diff --git a/pages/tech/index.md b/pages/tech/index.md index ae8d9d7..8c33ddd 100644 --- a/pages/tech/index.md +++ b/pages/tech/index.md @@ -2,6 +2,15 @@ import Section from '../../components/section' # Developer Docs -At CarbonPlan, we build projects using a wide varity of open source tools and technologies. This site provides documentation aimed at supporting our core software, data, and science development activities. +At CarbonPlan, we build projects using a wide varity of open source tools and technologies. We make extensive use of open source frameworks such as the [Xarray](https://xarray.dev/) package for working with multi-dimensional arrays, and the [Dask](https://dask.org/) package for parallel computing. We also rely on a number of other open source packages for data analysis and visualization. + +This site provides documentation aimed at supporting our core software, data, and science development activities. + +## Guides + +- [Contribution Guide](/contributing) +- [Style Guide](/style) +- [Testing Guide](/testing) +- [Python Environments Guide](/environments) export default ({ children }) =>
{children}
diff --git a/pages/tech/data-science/style.md b/pages/tech/style.md similarity index 98% rename from pages/tech/data-science/style.md rename to pages/tech/style.md index 912136e..8d89235 100644 --- a/pages/tech/data-science/style.md +++ b/pages/tech/style.md @@ -1,5 +1,5 @@ -import Section from '../../../components/section' -import Sidenote from '../../../components/sidenote' +import Section from '../../components/section' +import Sidenote from '../../components/sidenote' # Python Style Guide diff --git a/pages/tech/data-science/testing.md b/pages/tech/testing.md similarity index 98% rename from pages/tech/data-science/testing.md rename to pages/tech/testing.md index 5ab81bf..3313d96 100644 --- a/pages/tech/data-science/testing.md +++ b/pages/tech/testing.md @@ -1,4 +1,4 @@ -import Section from '../../../components/section' +import Section from '../../components/section' # Testing From 868add0c1fc39a0e2d0f48ec38310f687d58ce4e Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Thu, 13 Mar 2025 16:11:23 -0700 Subject: [PATCH 15/22] Update contribution guide and fix index links for clarity --- pages/tech/contributing.md | 89 ++++++++++++++++++++++++++++++-------- pages/tech/index.md | 8 ++-- 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/pages/tech/contributing.md b/pages/tech/contributing.md index 2f2dda8..9dcc212 100644 --- a/pages/tech/contributing.md +++ b/pages/tech/contributing.md @@ -4,27 +4,80 @@ import Section from '../../components/section' This page serves as a general contribution guide for CarbonPlan data science projects. Some projects within the organization may also provide customized contribution guides. -- where to start -- bug reports and enhancement requests -- developing +## Getting Started - - link to page on github - - creating a development environment - - work on a fork/branch - - contributing to the documentation (leave as a TODO block) - - contributing to the code base +If you're new to contributing to CarbonPlan projects, here are some resources to help you get started: - - code standards (link to testing) - - code formatting (link to sytle) - - test driven development - - documenting your code (numpydoc) +- Familiarize yourself with our [GitHub organization](https://github.com/carbonplan) +- Read our [Code of Conduct](./conduct) +- Check out our [style guide](./style) to understand our coding standards - - git workflow for committing and pushing your changes, open a pull request - - code review +## Reporting Issues -- project versioning -- more? +We use GitHub Issues to track bugs and feature requests. When submitting an issue: -export default ({ children }) => ( +- Use clear, descriptive titles +- Provide detailed steps to reproduce bugs +- For feature requests, explain why the feature would be useful +- Include relevant information about your environment + +## Development Workflow + +### Setting Up Your Environment + +1. Fork the repository on GitHub +2. Clone your fork locally +3. Set up the development environment (project-specific instructions will be in each repository's README) + +### Branch Strategy + +- Work on a feature branch named appropriately (e.g., `feature/add-new-filter` or `fix/resolve-data-loading-issue`) +- Keep your branch focused on a single issue or feature +- Reference our [GitHub workflow](./github) for more details on branches and pull requests + +### Code Contributions + +When contributing code: + +- Follow our [style guide](./style) for code formatting +- Write [tests](./testing) for new functionality +- Update documentation to reflect your changes +- Use [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) style for Python docstrings + +### Documentation Contributions + +Good documentation is as important as good code: -
{children}
) +- Keep language clear and concise +- Update examples to reflect code changes +- Ensure documentation builds without errors +- Preview changes locally before submitting + +### Submitting Changes + +1. Commit your changes with descriptive messages +2. Push to your fork +3. Submit a pull request from your branch to the main repository +4. Address feedback during code review + +## Code Review Process + +All submissions go through a review process: + +- A maintainer will review your PR +- Automated tests must pass +- Changes may be requested before merging +- Be responsive to questions and feedback + +## Project Versioning + +Different projects may have different versioning schemes. However, we generally use both + +- [Calendar Versioning (CalVer)](https://calver.org/). The format is `YYYY.MM.DD` or `YYYY.MM.DD.N` and +- [Semantic Versioning (SemVer)](https://semver.org/). The format is `MAJOR.MINOR.PATCH` (e.g., `1.2.3`) + +The choice of versioning scheme depends on the project and its goals. For example, we use CalVer for projects that are updated frequently (e.g. [offsets-db-data](https://github.com/carbonplan/offsets-db-data)) and SemVer for projects (e.g. [cmip6-downscaling](https://github.com/carbonplan/cmip6-downscaling)) that have a more stable release cycle. + +export default ({ children }) => ( +
{children}
+) diff --git a/pages/tech/index.md b/pages/tech/index.md index 8c33ddd..af74fa2 100644 --- a/pages/tech/index.md +++ b/pages/tech/index.md @@ -8,9 +8,9 @@ This site provides documentation aimed at supporting our core software, data, an ## Guides -- [Contribution Guide](/contributing) -- [Style Guide](/style) -- [Testing Guide](/testing) -- [Python Environments Guide](/environments) +- [Contribution Guide](/tech/contributing) +- [Style Guide](/tech/style) +- [Testing Guide](/tech/testing) +- [Python Environments Guide](/tech/environments) export default ({ children }) =>
{children}
From 6dca3a1c423443a6c1204c9b2ee0edb4b196e078 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Thu, 13 Mar 2025 18:01:42 -0700 Subject: [PATCH 16/22] Clarify versioning schemes in contributing guide with structured details --- pages/tech/contributing.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pages/tech/contributing.md b/pages/tech/contributing.md index 9dcc212..71ccc48 100644 --- a/pages/tech/contributing.md +++ b/pages/tech/contributing.md @@ -71,13 +71,17 @@ All submissions go through a review process: ## Project Versioning -Different projects may have different versioning schemes. However, we generally use both +The choice of versioning scheme depends on the project and its goals. We use two versioning schemes depending on the project type: -- [Calendar Versioning (CalVer)](https://calver.org/). The format is `YYYY.MM.DD` or `YYYY.MM.DD.N` and -- [Semantic Versioning (SemVer)](https://semver.org/). The format is `MAJOR.MINOR.PATCH` (e.g., `1.2.3`) - -The choice of versioning scheme depends on the project and its goals. For example, we use CalVer for projects that are updated frequently (e.g. [offsets-db-data](https://github.com/carbonplan/offsets-db-data)) and SemVer for projects (e.g. [cmip6-downscaling](https://github.com/carbonplan/cmip6-downscaling)) that have a more stable release cycle. +- [Calendar Versioning (CalVer)](https://calver.org/) + - The format is `YYYY.MM.DD` or `YYYY.MM.DD.N` + - Used for: projects with frequent updates (e.g., [offsets-db-data](https://github.com/carbonplan/offsets-db-data)) + - When: release schedule is time-based rather than feature-based +- [Semantic Versioning (SemVer)](https://semver.org/) + - The format is `MAJOR.MINOR.PATCH` (e.g., `1.2.3`) + - Used for: projects with a more stable release cycle (e.g., [cmip6-downscaling](https://github.com/carbonplan/cmip6-downscaling)) + - When: breaking changes need to be communicated clearly and new features are added in a backwards-compatible manner. export default ({ children }) => ( -
{children}
-) + +
{children}
) From 7767d907b20dc7b1c3ea1b905d60863606a0b961 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Thu, 13 Mar 2025 18:27:53 -0700 Subject: [PATCH 17/22] Add guidance on when to use Docker for reproducible research, dependency isolation, and collaboration --- pages/tech/environments.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pages/tech/environments.md b/pages/tech/environments.md index caa797e..9045f71 100644 --- a/pages/tech/environments.md +++ b/pages/tech/environments.md @@ -25,6 +25,18 @@ We recommend using [mamba](https://mamba.readthedocs.io/en/latest/) for managing We use [Docker](https://www.docker.com/) to create and manage containerized environments. These environments are used for deployment, testing, and reproducibility. We publish our docker images to [Quay.io](https://quay.io/organization/carbonplan). +### When to use Docker + +We recommend using Docker in the following scenarios: + +- **Reproducible research**: Docker allows future-proofing your work by encapsulating all dependencies in a container. This ensures that your code will run in the same environment, regardless of changes to the underlying system. +- **Dependecy isolation**: Sometimes, you may need to use a specific version of a library or tool that is not compatabile with your local operating system (e.g. Linux vs. MacOs). Creating a Docker container with the required dependencies can help you avoid conflicts and ensure that your code runs smoothly and consistently across different operating systems. +- **Collaboration**: Our JupyterHub environment allows users to run code in a containerized environment such as containers. Some projects may benefit from having a Docker container that can be shared with other users. This allows others to run your code in the same environment, without having to install all of the dependencies on their local machine. + +### Creating docker images using repo2docker + +There are several approaches to creating Docker images. We typically use [`repo2docker`](https://repo2docker.readthedocs.io/en/latest/) to create Docker images from a GitHub repository and GitHub Actions to build and push the image to Quay.io. This approach allows us to automatically build and publish Docker images whenever we push changes to the repository. An example GitHub Action workflow for building and pushing a Docker image can be found in the [carbonplan/argo-docker repository](https://github.com/carbonplan/argo-docker) + export default ({ children }) => (
{children}
) From de36463a663c190379b52d543d9364f12a61bffc Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Thu, 13 Mar 2025 18:31:33 -0700 Subject: [PATCH 18/22] Refine pre-commit instructions for clarity and remove unnecessary notes on skipping hooks --- pages/tech/style.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pages/tech/style.md b/pages/tech/style.md index 8d89235..7d27dd4 100644 --- a/pages/tech/style.md +++ b/pages/tech/style.md @@ -126,13 +126,11 @@ At this point, future commits to this Git repository will trigger the execution It is often useful to run the pre-commit script during developemnt, even before you are ready to create a Git commit. ``` -pre-commit run [--all-files] +pre-commit run ``` The standard execution will only run pre-commit on modified files. Adding the `--all-files` option will run the pre-commit script on all files within the respository. -Note that `pre-commit` can sometimes be difficult to satisfy. You can often get around this by running `git commit --no-verify` to skip the pre-commit hook. However, this is not recommended as it can lead to inconsistent code formatting and style issues. - ### Pre-commit configuration The hooks included in the pre-commit script are defined in the `.pre-commit-config.yaml` file in each repository. Below is an example of a standard pre-commit configuration. See this example [pre-commit-config.yaml](https://github.com/carbonplan/carbonplan-cookiecutter-python/blob/main/%7B%7Bcookiecutter.project_name%7D%7D/.pre-commit-config.yaml) file for a more complete example. From 9f85cdd0acd2ad494e8819c6e74289313dae025d Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Thu, 13 Mar 2025 18:53:26 -0700 Subject: [PATCH 19/22] Refactor environments documentation and update guide titles for clarity --- components/contents.js | 4 +- pages/tech/environments.md | 80 +++++++++++++++++++++++++++++++------- pages/tech/index.md | 2 +- 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/components/contents.js b/components/contents.js index 2107dba..fe07b9b 100644 --- a/components/contents.js +++ b/components/contents.js @@ -3,8 +3,8 @@ export const contents = { Conduct: '/tech/conduct', GitHub: '/tech/github', }, - 'Contributing Guide': { - Intro: '/tech/contributing', + Guides: { + Contributing: '/tech/contributing', Environments: '/tech/environments', Style: '/tech/style', Testing: '/tech/testing', diff --git a/pages/tech/environments.md b/pages/tech/environments.md index 9045f71..e1adf24 100644 --- a/pages/tech/environments.md +++ b/pages/tech/environments.md @@ -1,25 +1,61 @@ import Section from '../../components/section' -# Environments +# Computational Environments -We use a variety of environments to support our data science work. These include: +## Overview -- local environments -- docker environments +At CarbonPlan, we use different types of computational environments to support our data science work: -These environments are used to support a range of activities, including: +- **Local environments**: For day-to-day development +- **Docker containers**: For reproducibility and deployment -- local development -- continuous integration -- deployment -- reproducibility -- collaboration +These environments support various activities across our workflow: -Each environment has its own set of tools and configurations, and we use them in different ways depending on the task at hand. For example, we might use a local environment for development and testing, while using a docker environment for deployment and reproducibility. +| Activity | Local Environment | Docker | +| --------------- | ------------------- | ------------------------ | +| Development | ✅ Primary choice | ⚠️ Can be slower | +| Testing | ✅ Quick iterations | ✅ CI integration | +| Deployment | ❌ Not recommended | ✅ Best practice | +| Reproducibility | ⚠️ Limited | ✅ Excellent | +| Collaboration | ⚠️ Setup required | ✅ Consistent experience | ## Local environments -We recommend using [mamba](https://mamba.readthedocs.io/en/latest/) for managing local environments. Mamba is a fast, robust, and cross-platform package manager that can be used to create and manage conda environments. It is a drop-in replacement for conda, and it is fully compatible with the conda ecosystem. +We primarily use two tools for managing local environments, depending on project needs. + +### Using Pixi + +[Pixi](https://pixi.sh/latest/) is our recommended tool for managing local environments, especially for projects with complex geospatial dependencies. + +#### Advantages of Pixi + +- Faster dependency resolution than conda/mamba +- Simplified environment specification and isolation +- Compatibility with conda-forge packages (crucial for `GDAL`, `rasterio`, etc.) +- Deterministic builds with lockfiles + +#### Getting Started with Pixi + +Follow the [installation instructions](https://pixi.sh/latest/#installation) to set up Pixi: + +```bash +# Install Pixi +curl -fsSL https://pixi.sh/install.sh | bash + +# Initialize a new project +pixi init + +# Add dependencies (including conda-forge packages) +pixi add numpy pandas xarray +pixi add -c conda-forge gdal rasterio + +# Run commands within the environment +pixi run python my_script.py +``` + +### Alternative: Conda/Mamba + +For projects that benefit from the broader conda ecosystem, you can use conda or its faster alternative, mamba. ## Docker environments @@ -27,15 +63,29 @@ We use [Docker](https://www.docker.com/) to create and manage containerized envi ### When to use Docker -We recommend using Docker in the following scenarios: +Docker is particularly valuable in these scenarios: - **Reproducible research**: Docker allows future-proofing your work by encapsulating all dependencies in a container. This ensures that your code will run in the same environment, regardless of changes to the underlying system. -- **Dependecy isolation**: Sometimes, you may need to use a specific version of a library or tool that is not compatabile with your local operating system (e.g. Linux vs. MacOs). Creating a Docker container with the required dependencies can help you avoid conflicts and ensure that your code runs smoothly and consistently across different operating systems. +- **Dependecy isolation**: Sometimes, you may need to use a specific version of a library or tool that your code runs smoothly and consistently across different operating systems. +- **Cross-platform compatibility**: Works around OS-specific issues (Linux vs. macOS) - **Collaboration**: Our JupyterHub environment allows users to run code in a containerized environment such as containers. Some projects may benefit from having a Docker container that can be shared with other users. This allows others to run your code in the same environment, without having to install all of the dependencies on their local machine. ### Creating docker images using repo2docker -There are several approaches to creating Docker images. We typically use [`repo2docker`](https://repo2docker.readthedocs.io/en/latest/) to create Docker images from a GitHub repository and GitHub Actions to build and push the image to Quay.io. This approach allows us to automatically build and publish Docker images whenever we push changes to the repository. An example GitHub Action workflow for building and pushing a Docker image can be found in the [carbonplan/argo-docker repository](https://github.com/carbonplan/argo-docker) +We typically use [`repo2docker`](https://repo2docker.readthedocs.io/en/latest/) to create Docker images from GitHub repositories. + +1. Setup: Create environment files in your repository: + - `environment.yml` for conda dependencies + - `requirements.txt` for pip dependencies + - `apt.txt` for system dependencies +2. Building locally: + + ```bash + python -m pip install jupyter-repo2docker + repo2docker --no-run path/to/your/repo + ``` + +3. Automated builds: We use GitHub Actions to build and push images to Quay.io when changes are pushed. This approach allows us to automatically build and publish Docker images whenever we push changes to the repository. An example GitHub Action workflow for building and pushing a Docker image can be found in the [carbonplan/argo-docker repository](https://github.com/carbonplan/argo-docker) export default ({ children }) => ( diff --git a/pages/tech/index.md b/pages/tech/index.md index af74fa2..3c755db 100644 --- a/pages/tech/index.md +++ b/pages/tech/index.md @@ -11,6 +11,6 @@ This site provides documentation aimed at supporting our core software, data, an - [Contribution Guide](/tech/contributing) - [Style Guide](/tech/style) - [Testing Guide](/tech/testing) -- [Python Environments Guide](/tech/environments) +- [Computational Environments Guide](/tech/environments) export default ({ children }) =>
{children}
From a7b90d690633b6dd9f051df85e12b14798f71cd3 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Mon, 17 Mar 2025 10:01:16 -0700 Subject: [PATCH 20/22] Remove Code of Conduct document and update contributing guide link to external source --- pages/tech/conduct.md | 132 ------------------------------------- pages/tech/contributing.md | 2 +- 2 files changed, 1 insertion(+), 133 deletions(-) delete mode 100644 pages/tech/conduct.md diff --git a/pages/tech/conduct.md b/pages/tech/conduct.md deleted file mode 100644 index 83fb1bb..0000000 --- a/pages/tech/conduct.md +++ /dev/null @@ -1,132 +0,0 @@ -import Section from '../../components/section' - -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or - advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email - address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[admin@carbonplan.org](mailto:admin@carbonplan.org). -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html) - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at -[https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). - -export default ({ children }) =>
{children}
diff --git a/pages/tech/contributing.md b/pages/tech/contributing.md index 71ccc48..e72c0d4 100644 --- a/pages/tech/contributing.md +++ b/pages/tech/contributing.md @@ -9,7 +9,7 @@ This page serves as a general contribution guide for CarbonPlan data science pro If you're new to contributing to CarbonPlan projects, here are some resources to help you get started: - Familiarize yourself with our [GitHub organization](https://github.com/carbonplan) -- Read our [Code of Conduct](./conduct) +- Read our [Code of Conduct](https://github.com/carbonplan/.github/blob/main/CODE_OF_CONDUCT.md) - Check out our [style guide](./style) to understand our coding standards ## Reporting Issues From e6271a8414bd44db3d598868ad284e21a99dc949 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Mon, 17 Mar 2025 10:09:30 -0700 Subject: [PATCH 21/22] Update Conduct link to point to external Code of Conduct document --- components/contents.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/contents.js b/components/contents.js index fe07b9b..f69a0b9 100644 --- a/components/contents.js +++ b/components/contents.js @@ -1,6 +1,7 @@ export const contents = { Basics: { - Conduct: '/tech/conduct', + Conduct: + 'https://github.com/carbonplan/.github/blob/main/CODE_OF_CONDUCT.md', GitHub: '/tech/github', }, Guides: { From f9ccbad7e03ea5f53199e9d90546fe9f56fcf899 Mon Sep 17 00:00:00 2001 From: Anderson Banihirwe Date: Mon, 17 Mar 2025 10:16:45 -0700 Subject: [PATCH 22/22] Replace environment comparison table with a CarbonPlan component for improved consistency and styling --- pages/tech/environments.md | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/pages/tech/environments.md b/pages/tech/environments.md index e1adf24..52938c9 100644 --- a/pages/tech/environments.md +++ b/pages/tech/environments.md @@ -1,4 +1,5 @@ import Section from '../../components/section' +import { Table } from '@carbonplan/components' # Computational Environments @@ -11,13 +12,29 @@ At CarbonPlan, we use different types of computational environments to support o These environments support various activities across our workflow: -| Activity | Local Environment | Docker | -| --------------- | ------------------- | ------------------------ | -| Development | ✅ Primary choice | ⚠️ Can be slower | -| Testing | ✅ Quick iterations | ✅ CI integration | -| Deployment | ❌ Not recommended | ✅ Best practice | -| Reproducibility | ⚠️ Limited | ✅ Excellent | -| Collaboration | ⚠️ Setup required | ✅ Consistent experience | + ## Local environments