Skip to content

Commit

Permalink
[feat]: support conversion from YDS back to French
Browse files Browse the repository at this point in the history
Signed-off-by: GitHub <noreply@github.com>
  • Loading branch information
ilias-ant committed Jan 30, 2022
1 parent 4f19800 commit b36b8e3
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 84 deletions.
8 changes: 5 additions & 3 deletions README.md
Expand Up @@ -21,17 +21,19 @@ pip install pyclimb
import pyclimb


pyclimb.convert(grade='6a+', to='YDS')
pyclimb.convert(grade='6a+', grade_system='French', to='YDS')
// '5.10b'
pyclimb.convert(grade='9c', to='YDS')
pyclimb.convert(grade='9c', grade_system='French', to='YDS')
// '5.15d'
pyclimb.convert(grade='5.12a', grade_system='YDS', to='French')
// '7a+'
```

## Note

This is a package under active development. Currently, only the following conversions are being supported:

- [sport climbing] conversion from the French grading system to the YDS ([Yosemite Decimal System](https://en.wikipedia.org/wiki/Yosemite_Decimal_System)).
- [sport climbing] conversion between French grading system and the YDS ([Yosemite Decimal System](https://en.wikipedia.org/wiki/Yosemite_Decimal_System)).

Other conversions and different types of climbing will be included soon. These changes may drastically change the user-facing API, so do consult the semantic versioning of this package before upgrading to a newer version.

Expand Down
2 changes: 1 addition & 1 deletion pyclimb/__init__.py
@@ -1,3 +1,3 @@
from .converter import convert

__version__ = "0.1.0"
__version__ = "0.2.0"
38 changes: 26 additions & 12 deletions pyclimb/converter.py
@@ -1,17 +1,19 @@
from pyclimb import exceptions, mappers
from pyclimb.grading_systems import GradingSystem
from pyclimb import exceptions
from pyclimb.grading_system import GradingSystem
from pyclimb.mapper import get_conversion, mapper


def convert(grade: str, to: str) -> str:
def convert(grade: str, grade_system: str, to: str) -> str:
"""Converts a climbing grade to a specified grading system.
Example:
>>> pyclimb.convert(grade='6a+', to='YDS')
>>> pyclimb.convert(grade='5.12a', to='French')
>>> pyclimb.convert(grade='6a+', grade_system='French', to='YDS')
>>> pyclimb.convert(grade='5.12a', grade_system='YDS', to='French')
Args:
grade: The grade to be converted.
to: The grading system onto which the grade will be converted.
grade_system: The grade system to which `grade` belongs.
to: The grading system onto which the `grade` will be converted.
Returns:
The converted grade.
Expand All @@ -20,17 +22,29 @@ def convert(grade: str, to: str) -> str:
GradeConversionError: Error raised when the grade conversion fails.
"""
try:
grad_system = GradingSystem(to)
input_grading = GradingSystem(grade_system)

except ValueError:
raise exceptions.GradeConversionError(
"Grade could not be converted: `to` is not a recognized grading system."
)
f"Grade could not be converted: {grade_system} is not a recognized grading system."
) from None

try:
return mappers.french2yds[grade]
output_grading = GradingSystem(to)

except ValueError:
raise exceptions.GradeConversionError(
f"Grade could not be converted: {to} is not a recognized grading system."
) from None

conversion = get_conversion(
from_system=input_grading.value, to_system=output_grading.value
)

try:
return mapper[conversion][grade]

except KeyError:
raise exceptions.GradeConversionError(
"Grade could not be converted: `grade` not recognized."
)
f"Grade could not be converted: {grade} not recognized."
) from None
1 change: 1 addition & 0 deletions pyclimb/grading_systems.py → pyclimb/grading_system.py
Expand Up @@ -3,4 +3,5 @@

class GradingSystem(enum.Enum):

french = "French"
yds = "YDS"
109 changes: 109 additions & 0 deletions pyclimb/mapper.py
@@ -0,0 +1,109 @@
"""
The grade conversion implementation.
Each item of the mapper (dictionary) is a conversion path with:
- key following the naming convention "A2B" (`A` is the input grading, `B` is the output grading)
- value being the mapping from an `A` grade to a `B` grade.
Note that an "A2B" mapping is not bijective.
"""

mapper = {
"French2YDS": {
"1a": "N/A",
"1a+": "N/A",
"1b": "N/A",
"1b+": "N/A",
"1c": "5.0",
"1c+": "5.0",
"2a": "5.1",
"2a+": "5.1",
"2b": "5.1",
"2b+": "5.2",
"2c": "5.2",
"2c+": "5.3",
"3a": "5.3",
"3a+": "5.4",
"3b": "5.4",
"3b+": "5.4",
"3c": "5.5",
"3c+": "5.5",
"4a": "5.6",
"4a+": "5.6",
"4b": "5.6",
"4b+": "5.7",
"4c": "5.7",
"4c+": "5.8",
"5a": "5.8",
"5a+": "5.8",
"5b": "5.9",
"5b+": "5.9",
"5c": "5.10a",
"5c+": "5.10a",
"6a": "5.10a",
"6a+": "5.10b",
"6b": "5.10c",
"6b+": "5.10d",
"6c": "5.11b",
"6c+": "5.11c",
"7a": "5.11d",
"7a+": "5.12a",
"7b": "5.12b",
"7b+": "5.12c",
"7c": "5.12d",
"7c+": "5.13a",
"8a": "5.13b",
"8a+": "5.13c",
"8b": "5.13d",
"8b+": "5.14a",
"8c": "5.14b",
"8c+": "5.14c",
"9a": "5.14d",
"9a+": "5.15a",
"9b": "5.15b",
"9b+": "5.15c",
"9c": "5.15d",
},
"YDS2French": {
"5.0": "1c+",
"5.1": "2a+",
"5.2": "2b+",
"5.3": "3a",
"5.4": "3b",
"5.5": "3c+",
"5.6": "4a+",
"5.7": "4c",
"5.8": "5a",
"5.9": "5b+",
"5.10a": "5c+",
"5.10b": "6a+",
"5.10c": "6b",
"5.10d": "6b+",
"5.11a": "6c",
"5.11b": "6c+",
"5.11c": "6c+",
"5.11d": "7a",
"5.12a": "7a+",
"5.12b": "7b",
"5.12c": "7b+",
"5.12d": "7c",
"5.13a": "7c+",
"5.13b": "8a",
"5.13c": "8a+",
"5.13d": "8b",
"5.14a": "8b+",
"5.14b": "8c",
"5.14c": "8c+",
"5.14d": "9a",
"5.15a": "9a+",
"5.15b": "9b",
"5.15c": "9b+",
"5.15d": "9c",
},
}


def get_conversion(from_system: str, to_system: str) -> str:
"""Heuristic method to obtain conversion path."""
return f"{from_system}2{to_system}"
55 changes: 0 additions & 55 deletions pyclimb/mappers.py

This file was deleted.

2 changes: 1 addition & 1 deletion pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyclimb"
version = "0.1.0"
version = "0.2.0"
description = "A library to easily convert climbing route grades between different grading systems."
authors = ["ilias-ant <ilias.antonopoulos@yahoo.gr>"]
readme = "README.md"
Expand Down
84 changes: 72 additions & 12 deletions tests/test_converter.py
Expand Up @@ -4,6 +4,29 @@


class TestConverter:
def test_convert_with_unrecognized_output_grading_system(self):

with pytest.raises(pyclimb.exceptions.GradeConversionError) as exc:
pyclimb.convert("6a+", grade_system="French", to="bar")

assert "bar is not a recognized grading system" in str(exc.value)

def test_convert_with_unrecognized_input_grading_system(self):

with pytest.raises(pyclimb.exceptions.GradeConversionError) as exc:
pyclimb.convert("6a+", grade_system="foo", to="YDS")

assert "foo is not a recognized grading system" in str(exc.value)

def test_convert_with_unrecognized_grade(self):

with pytest.raises(pyclimb.exceptions.GradeConversionError) as exc:
pyclimb.convert(grade="foo", grade_system="French", to="YDS")

assert "foo not recognized" in str(exc.value)


class TestConverterFrenchToYDS:

french2yds_map = [
("1a", "N/A"),
Expand Down Expand Up @@ -61,21 +84,58 @@ class TestConverter:
("9c", "5.15d"),
]

def test_convert_with_unrecognized_grading_system(self):
@pytest.mark.parametrize("grade, converted_grade", french2yds_map)
def test_convert_french_to_yds(self, grade, converted_grade):

with pytest.raises(pyclimb.exceptions.GradeConversionError) as exc:
pyclimb.convert("6a+", to="foo")
assert (
pyclimb.convert(grade=grade, grade_system="French", to="YDS")
== converted_grade
)

assert "`to` is not a recognized grading system" in str(exc.value)

def test_convert_with_unrecognized_grade(self):
class TestConverterYDSToFrench:

with pytest.raises(pyclimb.exceptions.GradeConversionError) as exc:
pyclimb.convert(grade="foo bar", to="YDS")

assert "`grade` not recognized" in str(exc.value)
yds2french_map = [
("5.0", "1c+"),
("5.1", "2a+"),
("5.2", "2b+"),
("5.3", "3a"),
("5.4", "3b"),
("5.5", "3c+"),
("5.6", "4a+"),
("5.7", "4c"),
("5.8", "5a"),
("5.9", "5b+"),
("5.10a", "5c+"),
("5.10b", "6a+"),
("5.10c", "6b"),
("5.10d", "6b+"),
("5.11a", "6c"),
("5.11b", "6c+"),
("5.11c", "6c+"),
("5.11d", "7a"),
("5.12a", "7a+"),
("5.12b", "7b"),
("5.12c", "7b+"),
("5.12d", "7c"),
("5.13a", "7c+"),
("5.13b", "8a"),
("5.13c", "8a+"),
("5.13d", "8b"),
("5.14a", "8b+"),
("5.14b", "8c"),
("5.14c", "8c+"),
("5.14d", "9a"),
("5.15a", "9a+"),
("5.15b", "9b"),
("5.15c", "9b+"),
("5.15d", "9c"),
]

@pytest.mark.parametrize("grade, converted_grade", french2yds_map)
def test_convert_french_to_yds(self, grade, converted_grade):
@pytest.mark.parametrize("grade, converted_grade", yds2french_map)
def test_convert_yds_to_french(self, grade, converted_grade):

assert pyclimb.convert(grade=grade, to="YDS") == converted_grade
assert (
pyclimb.convert(grade=grade, grade_system="YDS", to="French")
== converted_grade
)

0 comments on commit b36b8e3

Please sign in to comment.