diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54e98b5..a0e67c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,43 +2,32 @@ name: CI on: push: - branches: - - main pull_request: jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v2 - - - uses: actions/setup-python@v2 - with: - python-version: 3.11 - - - name: cache poetry install - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - path: ~/.local - key: poetry-0 + python-version: ${{ matrix.python-version }} + - run: pip install uv + - run: uv venv + - run: uv pip install --requirement pyproject.toml --all-extras + - run: .venv/bin/pytest - - uses: snok/install-poetry@v1 - with: - virtualenvs-create: true - virtualenvs-in-project: true - - - name: cache deps - id: cache-deps - uses: actions/cache@v3 - with: - path: .venv - key: pydeps-${{ hashFiles('**/poetry.lock') }} - - - run: poetry install --no-interaction --no-root - if: steps.cache-deps.outputs.cache-hit != 'true' - - - run: poetry install --no-interaction - - - run: poetry run pytest tests.py - - run: poetry run black mudder.py tests.py - - run: poetry run flake8 mudder.py \ No newline at end of file + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - run: pip install uv + - run: uv venv + - run: uv pip install --requirement pyproject.toml --all-extras + - run: .venv/bin/ruff format --check . + - run: .venv/bin/ruff check . + - run: .venv/bin/mypy . diff --git a/README.md b/README.md index 963a8cd..59b838b 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ From the original readme: > Generate lexicographically-spaced strings between two strings from > pre-defined alphabets. -[1]: https://github.com/fasiha/mudderjs +This technique is also known as _fractional indexing_. +[1]: https://github.com/fasiha/mudderjs ## Example diff --git a/mudder.py b/mudder.py index 878daad..0529883 100644 --- a/mudder.py +++ b/mudder.py @@ -1,5 +1,5 @@ import math -from decimal import Decimal, ROUND_HALF_UP +from decimal import ROUND_HALF_UP, Decimal from functools import partial from itertools import chain, cycle from operator import add @@ -7,10 +7,10 @@ __all__ = [ "SymbolTable", - "decimal", + "alphabet", "base36", "base62", - "alphabet", + "decimal", ] @@ -30,7 +30,7 @@ def is_prefix_code(strings: Iterable[str]) -> bool: class SymbolTable: def __init__( self, symbols: Iterable[str], symbol_map: Optional[Dict[str, int]] = None - ): + ) -> None: symbols = list(symbols) if not symbol_map: symbol_map = dict((c, i) for i, c in enumerate(symbols)) @@ -38,9 +38,8 @@ def __init__( symbol_values = set(symbol_map.values()) for i in range(len(symbols)): if i not in symbol_values: - raise ValueError( - f"{len(symbols)} symbols given but {i} not found in symbol table" - ) + msg = f"{len(symbols)} symbols given but {i} not found in symbol table" + raise ValueError(msg) self.num2sym = symbols self.sym2num = symbol_map @@ -64,10 +63,11 @@ def digits_to_string(self, digits: Iterable[int]) -> str: def string_to_digits(self, string: Iterable[str]) -> List[int]: if isinstance(string, str): if not self.is_prefix_code: - raise ValueError( + msg = ( "Parsing without prefix code is unsupported. " "Pass in array of stringy symbols?" ) + raise ValueError(msg) string = (c for c in string if c in self.sym2num) return [self.sym2num[c] for c in string] @@ -146,15 +146,16 @@ def long_div( return result, remainder -def long_sub_same_len( # noqa: C901 +def long_sub_same_len( a: List[int], b: List[int], base: int, remainder: Optional[Tuple[int, int]] = None, - denominator=0, + denominator: int = 0, ) -> Tuple[List[int], int]: if len(a) != len(b): - raise ValueError("a and b should have same length") + msg = "a and b should have same length" + raise ValueError(msg) a = a.copy() # pre-emptively copy if remainder: @@ -169,7 +170,8 @@ def long_sub_same_len( # noqa: C901 ret[i] = a[i] - b[i] continue if i == 0: - raise ValueError("Cannot go negative") + msg = "Cannot go negative" + raise ValueError(msg) do_break = False # look for a digit to the left to borrow from for j in reversed(range(i)): @@ -189,7 +191,8 @@ def long_sub_same_len( # noqa: C901 break if do_break: continue - raise ValueError("Failed to find digit to borrow from") + msg = "Failed to find digit to borrow from" + raise ValueError(msg) if remainder: # result, remainder return ret[:-1], ret[-1] @@ -200,7 +203,8 @@ def long_add_same_len( a: List[int], b: List[int], base: int, remainder: int, denominator: int ) -> Tuple[List[int], bool, int, int]: if len(a) != len(b): - raise ValueError("a and b should have same length") + msg = "a and b should have same length" + raise ValueError(msg) carry = remainder >= denominator res = b.copy() @@ -230,7 +234,8 @@ def long_linspace( elif len(b) < len(a): b = right_pad(b, len(a)) if a == b: - raise ValueError("Start and end strings are lexicographically inseperable") + msg = "Start and end strings are lexicographically inseparable" + raise ValueError(msg) a_div, a_div_rem = long_div(a, m, base) b_div, b_div_rem = long_div(b, m, base) diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 8784a41..0000000 --- a/mypy.ini +++ /dev/null @@ -1,6 +0,0 @@ -[mypy] -no_implicit_optional = true -show_error_context = true -# show column numbers in error messages -show_column_numbers = true -check_untyped_defs = true diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 21f7aa7..0000000 --- a/poetry.lock +++ /dev/null @@ -1,372 +0,0 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. - -[[package]] -name = "attrs" -version = "22.2.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] - -[[package]] -name = "black" -version = "22.12.0" -description = "The uncompromising code formatter." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, - {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, - {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, - {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, - {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, - {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, - {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, - {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, - {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, - {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, - {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, - {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.1.0" -description = "Backport of PEP 654 (exception groups)" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, - {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "flake8" -version = "6.0.0" -description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" -optional = false -python-versions = ">=3.8.1" -files = [ - {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, - {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.10.0,<2.11.0" -pyflakes = ">=3.0.0,<3.1.0" - -[[package]] -name = "flake8-bugbear" -version = "23.1.20" -description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "flake8-bugbear-23.1.20.tar.gz", hash = "sha256:55902ab5a48c5ea53d8689ecd146eda548e72f2724192b9c1d68f6d975d13c06"}, - {file = "flake8_bugbear-23.1.20-py3-none-any.whl", hash = "sha256:04a115e5f9c8e87c38bdbbcdf9f58223ffe05469c07c9a7bd8633330bc4d078b"}, -] - -[package.dependencies] -attrs = ">=19.2.0" -flake8 = ">=3.0.0" - -[package.extras] -dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", "tox"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "isort" -version = "5.11.4" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"}, - {file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"}, -] - -[package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mypy" -version = "0.991" -description = "Optional static typing for Python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, - {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, - {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, - {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, - {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, - {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, - {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, - {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, - {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, - {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, - {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, - {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, - {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, - {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, - {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, - {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, - {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, - {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, - {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, - {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, - {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, - {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, - {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, - {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, -] - -[package.dependencies] -mypy-extensions = ">=0.4.3" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] - -[[package]] -name = "packaging" -version = "23.0" -description = "Core utilities for Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, -] - -[[package]] -name = "pathspec" -version = "0.10.3" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, - {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, -] - -[[package]] -name = "platformdirs" -version = "2.6.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, - {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, -] - -[package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pycodestyle" -version = "2.10.0" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, - {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, -] - -[[package]] -name = "pyflakes" -version = "3.0.1" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, - {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, -] - -[[package]] -name = "pytest" -version = "7.2.1" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, - {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, -] - -[package.dependencies] -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, -] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.8.1,<4" -content-hash = "21ea26832ba15afb7ce4ad782cac682b88fa24178c92bd71374411b61b77d18b" diff --git a/pyproject.toml b/pyproject.toml index f4840de..660f08e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,27 +1,52 @@ -[tool.poetry] +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project] name = "mudder" -version = "0.2.0" +dynamic = ["version"] description = "Python port of mudderjs" readme = "README.md" -authors = ["Patrick Gingras <775.pg.12@gmail.com>"] -license = "BSD-3-Clause" -homepage = "https://github.com/fellowinsights/mudder-py" -repository = "https://github.com/fellowinsights/mudder-py" +requires-python = ">=3.8" +authors = [{ name = "Patrick Gingras", email = "775.pg.12@gmail.com" }] +license = { text = "BSD-3-Clause" } -[tool.poetry.dependencies] -python = ">=3.8.1,<4" +[project.urls] +Homepage = "https://github.com/fellowinsights/mudder-py" +Repository = "https://github.com/fellowinsights/mudder-py" -[tool.poetry.group.dev.dependencies] -mypy = "^0.991" -black = "^22.12.0" -isort = "^5.11.4" -pytest = "^7.2.1" -flake8 = "^6.0.0" -flake8-bugbear = "^23.1.20" +[project.optional-dependencies] +dev = ["mypy~=1.9", "pytest~=8.1", "ruff~=0.4"] -[tool.black] -max-line-length = 88 +[tool.setuptools_scm] -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[tool.ruff.lint] +select = [ + "ANN", + "B", + "COM", + "E", + "EM", + "F", + "I", + "I", + "N", + "PT", + "RSE", + "RUF", + "SIM", + "UP", + "W", +] +ignore = ["COM812"] +preview = true + +[tool.ruff.format] +preview = true + +[tool.mypy] +strict = true +no_implicit_optional = true +show_error_context = true +show_column_numbers = true +check_untyped_defs = true diff --git a/tests.py b/test_mudder.py similarity index 76% rename from tests.py rename to test_mudder.py index 21f8e88..067cf22 100644 --- a/tests.py +++ b/test_mudder.py @@ -1,29 +1,30 @@ from itertools import tee +from typing import Iterable -import pytest # type: ignore +import pytest from mudder import SymbolTable, alphabet, base36, base62, decimal # from itertools docs -def pairwise(iterable): +def pairwise(iterable: Iterable[str]) -> Iterable[tuple[str, str]]: "s -> (s0,s1), (s1,s2), (s2, s3), ..." a, b = tee(iterable) next(b, None) return zip(a, b) -@pytest.fixture -def hex_(): +@pytest.fixture() +def hex_() -> SymbolTable: return SymbolTable("0123456789abcdef") -def test_reasonable_values(): +def test_reasonable_values() -> None: res = decimal.mudder("1", "2") assert res[0] == "15" -def test_reversing_start_end_reverses_outputs__controlled_cases(): +def test_reversing_start_end_reverses_outputs__controlled_cases() -> None: for num in range(1, 13): fwd = decimal.mudder("1", "2", num) rev = decimal.mudder("2", "1", num) @@ -32,7 +33,7 @@ def test_reversing_start_end_reverses_outputs__controlled_cases(): assert all(a > b for a, b in pairwise(rev)), "rev all decreasing" -def test_constructor_with_objects_and_maps(): +def test_constructor_with_objects_and_maps() -> None: arr = "_,I,II,III,IV,V".split(",") map_ = { "_": 0, @@ -52,13 +53,13 @@ def test_constructor_with_objects_and_maps(): assert roman.mudder(["I"], ["II"], 10) == roman.mudder(["i"], ["ii"], 10) -def test_matches_parseint_and_tostring(hex_): +def test_matches_parseint_and_tostring(hex_: SymbolTable) -> None: # no built-in way to convert an int to base36, we'll use base 16 instead assert "0x" + hex_.number_to_string(123) == hex(123) assert hex_.string_to_number("7b") == int("7b", 16) -def test_fixes_1__repeated_recursive_subdivision(): +def test_fixes_1__repeated_recursive_subdivision() -> None: right = "z" for _i in range(50): newr = alphabet.mudder("a", right)[0] @@ -67,26 +68,30 @@ def test_fixes_1__repeated_recursive_subdivision(): right = newr -def test_fixes_2__throws_when_fed_lexicographically_adjacent_strings(): +def test_fixes_2__throws_when_fed_lexicographically_adjacent_strings() -> None: for i in range(2, 10): - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="Start and end strings are lexicographically inseparable" + ): alphabet.mudder("x" + "a" * i, "xa") - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="Start and end strings are lexicographically inseparable" + ): alphabet.mudder("xa", "x" + "a" * i) -def test_fixes_3__allow_calling_mudder_with_just_number(): +def test_fixes_3__allow_calling_mudder_with_just_number() -> None: for abc in (alphabet.mudder(100), base36.mudder(100), base62.mudder(100)): assert all(a < b for a, b in pairwise(abc)) assert alphabet.mudder() -def test_more_3__no_need_to_define_start_or_end(): +def test_more_3__no_need_to_define_start_or_end() -> None: assert base36.mudder("", "foo", 30) assert base36.mudder("foo", "", 30) -def test_fixes_7__specify_number_of_divisions(): +def test_fixes_7__specify_number_of_divisions() -> None: fine = decimal.mudder("9", num_strings=100) partial_fine = decimal.mudder("9", num_strings=5, num_divisions=101) coarse = decimal.mudder("9", num_strings=5) @@ -110,5 +115,5 @@ def test_fixes_7__specify_number_of_divisions(): assert fine[:4] == partial_fine[:4] -def test_fix_8__better_default_end(): +def test_fix_8__better_default_end() -> None: assert base36.mudder("z" * 10)[0] != base36.mudder("z" * 15)[0]