Skip to content

Commit

Permalink
add bump-recipe command (#188)
Browse files Browse the repository at this point in the history
* add bump-recipe command

* fix type error

* add test

* fix failing tests

* Update conda_recipe_manager/commands/bump_recipe.py

Co-authored-by: Schuyler Martin <schuylermartin45@gmail.com>

* add some suggestions from the code review

* more changes based on previous code review

* check if the build number was actually incremented

* parametrize the test

* Update tests/commands/test_bump_recipe.py

Co-authored-by: Schuyler Martin <schuylermartin45@gmail.com>

* Update tests/commands/test_bump_recipe.py

Co-authored-by: Schuyler Martin <schuylermartin45@gmail.com>

* Update tests/commands/test_bump_recipe.py

Co-authored-by: Schuyler Martin <schuylermartin45@gmail.com>

* Update tests/commands/test_bump_recipe.py

Co-authored-by: Schuyler Martin <schuylermartin45@gmail.com>

* Update tests/commands/test_bump_recipe.py

Co-authored-by: Schuyler Martin <schuylermartin45@gmail.com>

* Update `convert` to properly handle complex Jinja expressions (#176)

* Add tilde to _JINJA_VAR_FUNCTION_PATTERN

* Create V0_FORBIDDEN_JINJA constant and check for strings containing them when converting recipes

* Add exception for when Jinja syntax that is too complex to convert is detected

* Make the linter happy

* Change name of V0_UNSUPPORTED_JINJA constant

* Log 'complex Jinja' warning with the MessageTable instance instead of throwing an exception

* Add more unsupported Jinja to regression_jinja_sub.yaml test recipe file to catch new warning in test

* Simplify warning message and update related test

* Fixes #186: Parser cannot handle addition in JINJA replacements (#191)

* Fixes #186 and adds some support for JINJA addition/concatenation

* Adds unit tests for JINJA addition/concatentation evaluations

* Python code Dependency Scanner Prototype (#180)

* Initial blueprint for dependency scanning module

* Starts to adapt work from abandonned dependency CLI branch

* Finishes initial work on pulling module names as dependencies

* Adds local module filtering

* Adds dependency type support to PythonDependencyScanner

* Adds MessageTable instance to base dependency class

* Adds missing docs

* Minor fixes

* Adds note about scanning performance

* Addresses linter and analyzer issues

* Adds initial dependency scanning unit test

* Adds ignore line to dummy project file

* Improves unit test coverage

* Updates recipe file to ignore test data directory

* Adds support for multiple imports on one line

* Add __init__ files to all test directories (#192)

* v0.3.0 commit (#193)

* Forgot to include some minor changes in the v0.3.0 release commit (#194)

* Updates the recipe doc link (#195)

* Fixes #197 and adds regression tests. Partially fixes #190 (#199)

* Removes missed debug statement (#200)

* 🤖 updated file(s) (#201)

* v0.3.1 (#202)

* Removes conda-forge channel (#204)

* Test the `MessageTable` class (#196)

* Add test for all methods in MessageTable class from types.py

* Split up/parametrize MessageTable unit tests

* Parametrize the MessageTable class tests and expand test cases

* Correct doc string formatting

* 207 Refactor `RecipeParserDeps` (#209)

* Renames RecipeParserDeps -> RecipeReaderDeps

* Renames RecipeParserDeps -> RecipeReaderDeps

* Fleshes-out initial utility functions in the new RecipeParserDeps class

* Improves SelectorParser capabilities

* Adds selector management to the add_dependency() function

* Adds unit test for selector rendering

* Added SelectorParser type variant to test_add_selector()

* Adds initial RecipeParserDeps unit tests

* Adds initial multi-output add_dependency() test variants

* Adds unit tests for remove_dependency()

* Adds invalid path tests to test_add_dependency()

* Adds alternative DependencyMode unit tests to test_add_dependency()

* Adds missing invalid path checks for test_add_dependency()

* Adds more edge-case unit tests to test_add_dependency()

* Fixes patch-add append edge case

* Adds missing unit test for adding a dependency to a non-existent dependency section

* Adds another missing unit test for adding a dependency to a non-existent dependency section

* v0.3.2 (#210)

* Fixes `numpy {{numpy}}` dependency bug (#214)

* Fixes resilience issue with dependency_data_from_str() function also makes function names more consistent

* Adds missing unit tests for dependency module

* Removes outdated TODO comment

* Adds regression testing for the numpy dependency parsing issue

* v0.3.3 (#216)

* v0.3.4 (#217)

* Improves pytest config filter to be less aggressive (#223)

* 🤖 updated file(s) (#226)

* Incorporating user documentation feedback and simplifies the `Makefile` (#227)

* Ignores another pytest deprecation

* Simplifications and deprecations for the Makefile. The preferred method for installing CRM is through conda/conda-forge

* Overhauls existing README documentation

* Updates README TOC

* More README doc work

* More README improvements

* Filling more doc gaps

* Simplifies and automates the API documentaiton process

* Update README.md

Co-authored-by: Mahe Iram Khan <65779580+ForgottenProgramme@users.noreply.github.com>

* Update README.md

Co-authored-by: Mahe Iram Khan <65779580+ForgottenProgramme@users.noreply.github.com>

* Update README.md

Co-authored-by: Mahe Iram Khan <65779580+ForgottenProgramme@users.noreply.github.com>

* Adds a contribution file

* Update CONTRIBUTING.md

---------

Co-authored-by: Mahe Iram Khan <65779580+ForgottenProgramme@users.noreply.github.com>

* remove dangling else statements

---------

Co-authored-by: Schuyler Martin <schuylermartin45@gmail.com>
Co-authored-by: Bianca Henderson <bhenderson@anaconda.com>
Co-authored-by: conda-bot <18747875+conda-bot@users.noreply.github.com>
  • Loading branch information
4 people authored Nov 6, 2024
1 parent 278099f commit d8dec77
Show file tree
Hide file tree
Showing 10 changed files with 437 additions and 0 deletions.
67 changes: 67 additions & 0 deletions conda_recipe_manager/commands/bump_recipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
:Description: CLI for bumping build number in recipe files.
"""

from __future__ import annotations

import sys
from pathlib import Path
from typing import cast

import click

from conda_recipe_manager.commands.utils.print import print_err
from conda_recipe_manager.commands.utils.types import ExitCode
from conda_recipe_manager.parser.recipe_parser import RecipeParser
from conda_recipe_manager.types import JsonPatchType


# TODO Improve. In order for `click` to play nice with `pyfakefs`, we set `path_type=str` and delay converting to a
# `Path` instance. This is caused by how `click` uses decorators. See these links for more detail:
# - https://pytest-pyfakefs.readthedocs.io/en/latest/troubleshooting.html#pathlib-path-objects-created-outside-of-tests
# - https://github.com/pytest-dev/pyfakefs/discussions/605
@click.command(short_help="Bumps a recipe file to a new version.")
@click.argument("recipe_file_path", type=click.Path(exists=True, path_type=str))
@click.option(
"--build-num",
is_flag=True,
help="Bump the build number by 1.",
)
def bump_recipe(recipe_file_path: str, build_num: bool) -> None:
"""
Bumps a recipe to a new version.
RECIPE_FILE_PATH: Path to the target recipe file
"""
try:
contents_recipe = Path(recipe_file_path).read_text(encoding="utf-8")
except IOError:
print_err(f"Couldn't read the given recipe file: {recipe_file_path}")
sys.exit(ExitCode.IO_ERROR)

try:
recipe_parser = RecipeParser(contents_recipe)
except Exception: # pylint: disable=broad-except
print_err("An error occurred while parsing the recipe file contents.")
sys.exit(ExitCode.PARSE_EXCEPTION)

if build_num:
try:
build_number = recipe_parser.get_value("/build/number")
except KeyError:
print_err("`/build/number` key could not be found in the recipe.")
sys.exit(ExitCode.ILLEGAL_OPERATION)

if not isinstance(build_number, int):
print_err("Build number is not an integer.")
sys.exit(ExitCode.ILLEGAL_OPERATION)

required_patch_blob = cast(JsonPatchType, {"op": "replace", "path": "/build/number", "value": build_number + 1})

if not recipe_parser.patch(required_patch_blob):
print_err(f"Couldn't perform the patch: {required_patch_blob}.")
sys.exit(ExitCode.PARSE_EXCEPTION)

Path(recipe_file_path).write_text(recipe_parser.render(), encoding="utf-8")
sys.exit(ExitCode.SUCCESS)
print_err("Sorry, the default bump behaviour has not been implemented yet.")
2 changes: 2 additions & 0 deletions conda_recipe_manager/commands/conda_recipe_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import click

from conda_recipe_manager.commands.bump_recipe import bump_recipe
from conda_recipe_manager.commands.convert import convert
from conda_recipe_manager.commands.graph import graph
from conda_recipe_manager.commands.patch import patch
Expand All @@ -24,6 +25,7 @@ def conda_recipe_manager() -> None:
conda_recipe_manager.add_command(graph)
conda_recipe_manager.add_command(rattler_bulk_build)
conda_recipe_manager.add_command(patch)
conda_recipe_manager.add_command(bump_recipe)


if __name__ == "__main__":
Expand Down
82 changes: 82 additions & 0 deletions tests/commands/test_bump_recipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""
:Description: Tests the `bump-recipe` CLI
"""

import pytest
from click.testing import CliRunner
from pyfakefs.fake_filesystem import FakeFilesystem

from conda_recipe_manager.commands import bump_recipe
from conda_recipe_manager.commands.utils.types import ExitCode
from conda_recipe_manager.parser.recipe_parser import RecipeParser
from tests.file_loading import get_test_path, load_recipe
from tests.smoke_testing import assert_cli_usage


def test_usage() -> None:
"""
Smoke test that ensures rendering of the help menu
"""
assert_cli_usage(bump_recipe.bump_recipe)


@pytest.mark.parametrize(
"recipe_file, expected_recipe_file",
[
("simple-recipe.yaml", "bump_recipe/build_num_1.yaml"),
("bump_recipe/build_num_1.yaml", "bump_recipe/build_num_2.yaml"),
("bump_recipe/build_num_42.yaml", "bump_recipe/build_num_43.yaml"),
("bump_recipe/build_num_-1.yaml", "simple-recipe.yaml"),
],
)
def test_bump_recipe_cli(fs: FakeFilesystem, recipe_file: str, expected_recipe_file: str) -> None:
"""
Test for the case when build number is successfully incremented by 1.
:param fs: `pyfakefs` Fixture used to replace the file system
"""
runner = CliRunner()
fs.add_real_directory(get_test_path(), read_only=False)

recipe_file_path = get_test_path() / recipe_file
expected_recipe_file_path = get_test_path() / expected_recipe_file

result = runner.invoke(bump_recipe.bump_recipe, ["--build-num", str(recipe_file_path)])

parser = load_recipe(recipe_file_path, RecipeParser)
expected_parser = load_recipe(expected_recipe_file_path, RecipeParser)

assert parser == expected_parser
assert result.exit_code == ExitCode.SUCCESS


def test_bump_build_num_not_int(fs: FakeFilesystem) -> None:
"""
Test that the command fails gracefully case when the build number is not an integer,
and we are trying to increment it.
:param fs: `pyfakefs` Fixture used to replace the file system
"""

runner = CliRunner()
fs.add_real_directory(get_test_path(), read_only=False)

recipe_file_path = get_test_path() / "bump_recipe/non_int_build_num.yaml"

result = runner.invoke(bump_recipe.bump_recipe, ["--build-num", str(recipe_file_path)])
assert result.exit_code == ExitCode.ILLEGAL_OPERATION


def test_bump_build_num_key_not_found(fs: FakeFilesystem) -> None:
"""
Test that the command fails gracefully when the build number key is missing and we try to increment it's value.
:param fs: `pyfakefs` Fixture used to replace the file system
"""

runner = CliRunner()
fs.add_real_directory(get_test_path(), read_only=False)

recipe_file_path = get_test_path() / "bump_recipe/no_build_num.yaml"
result = runner.invoke(bump_recipe.bump_recipe, ["--build-num", str(recipe_file_path)])
assert result.exit_code == ExitCode.ILLEGAL_OPERATION
53 changes: 53 additions & 0 deletions tests/test_aux_files/bump_recipe/build_num_-1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{% set zz_non_alpha_first = 42 %}
{% set name = "types-toml" %}
{% set version = "0.10.8.6" %}

package:
name: {{ name|lower }} # [unix]

build:
number: -1
skip: true # [py<37]
is_true: true

# Comment above a top-level structure
requirements:
empty_field1:
host:
- setuptools # [unix]
- fakereq # [unix] selector with comment
empty_field2: # [unix and win] # selector with comment with comment symbol
run:
- python # not a selector
empty_field3:

about:
summary: This is a small recipe for testing
description: |
This is a PEP '561 type stub package for the toml package.
It can be used by type-checking tools like mypy, pyright,
pytype, PyCharm, etc. to check code that uses toml.
license: Apache-2.0 AND MIT

multi_level:
list_1:
- foo
# Ensure a comment in a list is supported
- bar
list_2:
- cat
- bat
- mat
list_3:
- ls
- sl
- cowsay

test_var_usage:
foo: {{ version }}
bar:
- baz
- {{ zz_non_alpha_first }}
- blah
- This {{ name }} is silly
- last
53 changes: 53 additions & 0 deletions tests/test_aux_files/bump_recipe/build_num_1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{% set zz_non_alpha_first = 42 %}
{% set name = "types-toml" %}
{% set version = "0.10.8.6" %}

package:
name: {{ name|lower }} # [unix]

build:
number: 1
skip: true # [py<37]
is_true: true

# Comment above a top-level structure
requirements:
empty_field1:
host:
- setuptools # [unix]
- fakereq # [unix] selector with comment
empty_field2: # [unix and win] # selector with comment with comment symbol
run:
- python # not a selector
empty_field3:

about:
summary: This is a small recipe for testing
description: |
This is a PEP '561 type stub package for the toml package.
It can be used by type-checking tools like mypy, pyright,
pytype, PyCharm, etc. to check code that uses toml.
license: Apache-2.0 AND MIT

multi_level:
list_1:
- foo
# Ensure a comment in a list is supported
- bar
list_2:
- cat
- bat
- mat
list_3:
- ls
- sl
- cowsay

test_var_usage:
foo: {{ version }}
bar:
- baz
- {{ zz_non_alpha_first }}
- blah
- This {{ name }} is silly
- last
53 changes: 53 additions & 0 deletions tests/test_aux_files/bump_recipe/build_num_2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{% set zz_non_alpha_first = 42 %}
{% set name = "types-toml" %}
{% set version = "0.10.8.6" %}

package:
name: {{ name|lower }} # [unix]

build:
number: 2
skip: true # [py<37]
is_true: true

# Comment above a top-level structure
requirements:
empty_field1:
host:
- setuptools # [unix]
- fakereq # [unix] selector with comment
empty_field2: # [unix and win] # selector with comment with comment symbol
run:
- python # not a selector
empty_field3:

about:
summary: This is a small recipe for testing
description: |
This is a PEP '561 type stub package for the toml package.
It can be used by type-checking tools like mypy, pyright,
pytype, PyCharm, etc. to check code that uses toml.
license: Apache-2.0 AND MIT

multi_level:
list_1:
- foo
# Ensure a comment in a list is supported
- bar
list_2:
- cat
- bat
- mat
list_3:
- ls
- sl
- cowsay

test_var_usage:
foo: {{ version }}
bar:
- baz
- {{ zz_non_alpha_first }}
- blah
- This {{ name }} is silly
- last
53 changes: 53 additions & 0 deletions tests/test_aux_files/bump_recipe/build_num_42.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{% set zz_non_alpha_first = 42 %}
{% set name = "types-toml" %}
{% set version = "0.10.8.6" %}

package:
name: {{ name|lower }} # [unix]

build:
number: 42
skip: true # [py<37]
is_true: true

# Comment above a top-level structure
requirements:
empty_field1:
host:
- setuptools # [unix]
- fakereq # [unix] selector with comment
empty_field2: # [unix and win] # selector with comment with comment symbol
run:
- python # not a selector
empty_field3:

about:
summary: This is a small recipe for testing
description: |
This is a PEP '561 type stub package for the toml package.
It can be used by type-checking tools like mypy, pyright,
pytype, PyCharm, etc. to check code that uses toml.
license: Apache-2.0 AND MIT

multi_level:
list_1:
- foo
# Ensure a comment in a list is supported
- bar
list_2:
- cat
- bat
- mat
list_3:
- ls
- sl
- cowsay

test_var_usage:
foo: {{ version }}
bar:
- baz
- {{ zz_non_alpha_first }}
- blah
- This {{ name }} is silly
- last
Loading

0 comments on commit d8dec77

Please sign in to comment.