New upstream snapshot.
Debian Janitor
1 year, 4 months ago
0 | [report] | |
1 | # Regexes for lines to exclude from consideration | |
2 | exclude_lines = | |
3 | # Have to re-enable the standard pragma | |
4 | pragma: no cover | |
5 | ||
6 | # Don't complain if tests don't hit defensive assertion code: | |
7 | raise AssertionError | |
8 | raise NotImplementedError | |
9 | raise$ | |
10 | ||
11 | # Don't complain if non-runnable code isn't run: | |
12 | if 0: | |
13 | if __name__ == .__main__.: | |
14 | ||
15 | ignore_errors = True |
0 | --- | |
1 | name: Bug report | |
2 | about: Report unexpected behavior, a crash, or incorrect results | |
3 | ||
4 | --- | |
5 | ||
6 | **Describe the bug** | |
7 | A clear and concise description of what the bug is. | |
8 | ||
9 | **Expected behavior** | |
10 | A clear and concise description of what you expected to happen. | |
11 | ||
12 | **Environment (please complete the following information):** | |
13 | - Python Version: [e.g. 3.6] | |
14 | - OS [e.g. Windows, Fedora] | |
15 | - If the bug involves `LOCALE` or `humansorted`: | |
16 | - Is `PyICU` installed? | |
17 | - Do you have a locale set? If so, to what? | |
18 | ||
19 | **To Reproduce** | |
20 | Include a Minimum, Complete, Verifiable Example. If there is a traceback (or error message), **please** include the *entire* traceback (or error message), even if you think it is too big. | |
21 | ||
22 | See https://stackoverflow.com/help/mcve for an explanation. |
0 | --- | |
1 | name: Feature request | |
2 | about: Suggest or request an enhancement | |
3 | ||
4 | --- | |
5 | ||
6 | **Describe the feature or enhancement** | |
7 | Be as descriptive and precise as possible. | |
8 | ||
9 | **Provide a concrete example of how the feature or enhancement will improve `natsort`** | |
10 | Code examples are an excellent way to show how this feature or enhancement will help. To make your case stronger, show the current workaround due to the lack of the feature. What is the return-on-investment for including the feature or enhancement? | |
11 | ||
12 | **Would you be willing to submit a Pull Request for this feature?** | |
13 | Extra help is *always* welcome. |
0 | --- | |
1 | name: Question | |
2 | about: Inquiry about natsort | |
3 | ||
4 | --- | |
5 | ||
6 | - [ ] I have read the [`natsort` documentation](https://natsort.readthedocs.io/en/master/) and the [README](https://github.com/SethMMorton/natsort#natsort), and my question is still not answered |
0 | name: Code Quality | |
1 | ||
2 | # Only run on branches (e.g. not tags) | |
3 | on: | |
4 | push: | |
5 | branches: | |
6 | - "*" | |
7 | pull_request: | |
8 | branches: | |
9 | - "*" | |
10 | ||
11 | jobs: | |
12 | formatting: | |
13 | name: Formatting | |
14 | runs-on: ubuntu-latest | |
15 | steps: | |
16 | - name: Checkout code | |
17 | uses: actions/checkout@v2 | |
18 | ||
19 | - name: Set up Python | |
20 | uses: actions/setup-python@v2 | |
21 | with: | |
22 | python-version: '3.6' | |
23 | ||
24 | - name: Install black | |
25 | run: pip install black | |
26 | ||
27 | - name: Run black | |
28 | run: black --quiet --check --diff . | |
29 | ||
30 | static-analysis: | |
31 | name: Static Analysis | |
32 | runs-on: ubuntu-latest | |
33 | steps: | |
34 | - name: Checkout code | |
35 | uses: actions/checkout@v2 | |
36 | ||
37 | - name: Set up Python | |
38 | uses: actions/setup-python@v2 | |
39 | with: | |
40 | python-version: '3.6' | |
41 | ||
42 | - name: Install Flake8 | |
43 | run: pip install flake8 flake8-import-order flake8-bugbear pep8-naming | |
44 | ||
45 | - name: Run Flake8 | |
46 | run: flake8 | |
47 | ||
48 | type-checking: | |
49 | name: Type Checking | |
50 | runs-on: ubuntu-latest | |
51 | steps: | |
52 | - name: Checkout code | |
53 | uses: actions/checkout@v2 | |
54 | ||
55 | - name: Set up Python | |
56 | uses: actions/setup-python@v2 | |
57 | with: | |
58 | python-version: '3.6' | |
59 | ||
60 | - name: Install MyPy | |
61 | run: pip install mypy hypothesis pytest pytest-mock fastnumbers | |
62 | ||
63 | - name: Run MyPy | |
64 | run: mypy --strict natsort tests | |
65 | ||
66 | package-validation: | |
67 | name: Package Validation | |
68 | runs-on: ubuntu-latest | |
69 | steps: | |
70 | - name: Checkout code | |
71 | uses: actions/checkout@v2 | |
72 | ||
73 | - name: Set up Python | |
74 | uses: actions/setup-python@v2 | |
75 | with: | |
76 | python-version: '3.6' | |
77 | ||
78 | - name: Install Validators | |
79 | run: pip install twine check-manifest | |
80 | ||
81 | - name: Run Validation | |
82 | run: | | |
83 | check-manifest --ignore ".github*,*.md,.coveragerc" | |
84 | python setup.py sdist | |
85 | twine check dist/* |
0 | name: Deploy | |
1 | ||
2 | # Only run on tagged commits | |
3 | on: | |
4 | push: | |
5 | tags: | |
6 | - "*" | |
7 | ||
8 | jobs: | |
9 | deploy: | |
10 | name: Deploy | |
11 | runs-on: ubuntu-latest | |
12 | steps: | |
13 | - name: Checkout code | |
14 | uses: actions/checkout@v2 | |
15 | ||
16 | - name: Set up Python | |
17 | uses: actions/setup-python@v2 | |
18 | with: | |
19 | python-version: 3.9 | |
20 | ||
21 | - name: Build Source Distribution and Wheel | |
22 | run: | | |
23 | pip install wheel | |
24 | python setup.py sdist --format=gztar bdist_wheel | |
25 | ||
26 | - name: Publish to PyPI | |
27 | uses: pypa/gh-action-pypi-publish@master | |
28 | with: | |
29 | user: __token__ | |
30 | password: ${{ secrets.pypi_token_password }} |
0 | name: Tests | |
1 | ||
2 | # Only run on branches (e.g. not tags) | |
3 | on: | |
4 | push: | |
5 | branches: | |
6 | - "*" | |
7 | pull_request: | |
8 | branches: | |
9 | - "*" | |
10 | ||
11 | jobs: | |
12 | tests: | |
13 | name: Tests | |
14 | runs-on: ${{ matrix.os }} | |
15 | strategy: | |
16 | matrix: | |
17 | python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] | |
18 | os: [ubuntu-latest] | |
19 | extras: [false] | |
20 | include: | |
21 | - {python-version: 3.9, os: windows-latest, extras: false} | |
22 | - {python-version: 3.9, os: macos-latest, extras: false} | |
23 | - {python-version: 3.9, os: ubuntu-latest, extras: true} | |
24 | ||
25 | steps: | |
26 | - name: Checkout code | |
27 | uses: actions/checkout@v2 | |
28 | ||
29 | - name: Set up Python ${{ matrix.python-version }} | |
30 | uses: actions/setup-python@v2 | |
31 | with: | |
32 | python-version: ${{ matrix.python-version }} | |
33 | ||
34 | - name: Install Locales | |
35 | if: matrix.os == 'ubuntu-latest' | |
36 | run: | | |
37 | sudo apt-get update | |
38 | sudo apt-get install language-pack-de language-pack-en language-pack-cs | |
39 | ||
40 | - name: Install ICU | |
41 | if: matrix.extras | |
42 | run: sudo apt-get install libicu-dev | |
43 | ||
44 | - name: Install Dependencies | |
45 | run: | | |
46 | python -m pip install --upgrade pip | |
47 | python -m pip install tox tox-gh-actions codecov | |
48 | ||
49 | - name: Set Extras Environment | |
50 | if: matrix.extras | |
51 | run: echo WITH_EXTRAS=fast,icu >> $GITHUB_ENV | |
52 | ||
53 | - name: Run Tests | |
54 | run: tox | |
55 | ||
56 | - name: Generate Coverage Report | |
57 | run: coverage xml | |
58 | ||
59 | - name: Upload to CodeCov | |
60 | uses: codecov/codecov-action@v1 |
0 | *.py[co] | |
1 | ||
2 | # Packages | |
3 | *.egg | |
4 | *.eggs | |
5 | *.egg-info | |
6 | dist | |
7 | build | |
8 | eggs | |
9 | parts | |
10 | bin | |
11 | var | |
12 | sdist | |
13 | develop-eggs | |
14 | .installed.cfg | |
15 | .python-version | |
16 | ||
17 | # We are using MANIFEST.in instead | |
18 | MANIFEST | |
19 | ||
20 | # Installer logs | |
21 | pip-log.txt | |
22 | ||
23 | # Unit test / coverage reports | |
24 | .hypothesis | |
25 | .coverage | |
26 | .tox | |
27 | .cache | |
28 | .pytest_cache | |
29 | .pytest | |
30 | .envrc | |
31 | .venv | |
32 | ||
33 | #Translations | |
34 | *.mo | |
35 | ||
36 | #Mr Developer | |
37 | .mr.developer.cfg | |
38 | ||
39 | # PyCharm | |
40 | .idea | |
41 | ||
42 | # VSCode | |
43 | .vscode |
0 | 0 | Unreleased |
1 | 1 | --- |
2 | 2 | |
3 | ### Removed | |
4 | ||
5 | - Support for EOL Python 3.6 | |
6 | ||
7 | [8.2.0] - 2022-09-01 | |
8 | --- | |
9 | ||
10 | ### Changed | |
11 | - Auto-coerce `pathlib.Path` objects to `str` since it is the least astonishing | |
12 | behavior ([@Gilthans](https://github.com/Gilthans), issues #152, #153) | |
13 | - Reduce strictness of type hints to avoid over-constraining client code | |
14 | (issues #154, #155) | |
15 | ||
16 | [8.1.0] - 2022-01-30 | |
17 | --- | |
18 | ||
19 | ### Changed | |
20 | - When using `ns.PATH`, only split off a maximum of two suffixes from | |
21 | a file name (issues #145, #146). | |
22 | ||
3 | 23 | [8.0.2] - 2021-12-14 |
4 | 24 | --- |
5 | 25 | |
6 | 26 | ### Fixed |
7 | - Bug where sorting paths fail if one of the paths is '.'. | |
27 | - Bug where sorting paths fail if one of the paths is '.' (issues #142, #143) | |
8 | 28 | |
9 | 29 | [8.0.1] - 2021-12-10 |
10 | 30 | --- |
11 | 31 | |
12 | 32 | ### Fixed |
13 | 33 | - Compose unicode characters when using locale to ensure sorting is correct |
14 | across all locales. | |
34 | across all locales (issues #140, #141) | |
15 | 35 | |
16 | 36 | [8.0.0] - 2021-11-03 |
17 | 37 | --- |
51 | 71 | |
52 | 72 | ### Changed |
53 | 73 | - MacOS unit tests run on native Python |
54 | - Treate `None` like `NaN` internally to avoid `TypeError` (issue #117) | |
74 | - Treat `None` like `NaN` internally to avoid `TypeError` (issue #117) | |
55 | 75 | - No longer fail tests every time a new Python version is released (issue #122) |
56 | 76 | |
57 | 77 | ### Fixed |
75 | 95 | - Release checklist in `RELEASING.md` ([@hugovk](https://github.com/hugovk), issue #106) |
76 | 96 | |
77 | 97 | ### Changed |
78 | - Updated auxillary shell scripts to be written in python, and added | |
98 | - Updated auxiliary shell scripts to be written in python, and added | |
79 | 99 | ability to call these from `tox` |
80 | 100 | - Improved Travis-CI experience |
81 | 101 | - Update testing dependency versions |
280 | 300 | because the new factory function paradigm eliminates most `if` branches |
281 | 301 | during execution). For the most cases, the code is 30-40% faster than version 4.0.4. |
282 | 302 | If using `ns.LOCALE` or `humansorted`, the code is 1100% faster than version 4.0.4 |
283 | - Improved clarity of documentaion with regards to locale-aware sorting | |
303 | - Improved clarity of documentation with regards to locale-aware sorting | |
284 | 304 | |
285 | 305 | ### Deprecated |
286 | 306 | - `ns.TYPESAFE` option as it is now always on (due to a new |
426 | 446 | - `reverse` option to `natsorted` & co. to make it's API more |
427 | 447 | similar to the builtin 'sorted' |
428 | 448 | - More unit tests |
429 | - Auxillary test code that helps in profiling and stress-testing | |
449 | - Auxiliary test code that helps in profiling and stress-testing | |
430 | 450 | - Support for coveralls.io |
431 | 451 | |
432 | 452 | ### Changed |
573 | 593 | --- |
574 | 594 | |
575 | 595 | ### Added |
576 | - Tests into the natsort.py file iteself | |
596 | - Tests into the natsort.py file itself | |
577 | 597 | |
578 | 598 | ### Changed |
579 | 599 | - Reorganized directory structure |
589 | 609 | - Sorting algorithm to support floats (including exponentials) and basic version number support |
590 | 610 | |
591 | 611 | <!---Comparison links--> |
612 | [8.2.0]: https://github.com/SethMMorton/natsort/compare/8.1.0...8.2.0 | |
613 | [8.1.0]: https://github.com/SethMMorton/natsort/compare/8.0.2...8.1.0 | |
592 | 614 | [8.0.2]: https://github.com/SethMMorton/natsort/compare/8.0.1...8.0.2 |
593 | 615 | [8.0.1]: https://github.com/SethMMorton/natsort/compare/8.0.0...8.0.1 |
594 | 616 | [8.0.0]: https://github.com/SethMMorton/natsort/compare/7.2.0...8.0.0 |
0 | # Contributor Covenant Code of Conduct | |
1 | ||
2 | ## Our Pledge | |
3 | ||
4 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. | |
5 | ||
6 | ## Our Standards | |
7 | ||
8 | Examples of behavior that contributes to creating a positive environment include: | |
9 | ||
10 | * Using welcoming and inclusive language | |
11 | * Being respectful of differing viewpoints and experiences | |
12 | * Gracefully accepting constructive criticism | |
13 | * Focusing on what is best for the community | |
14 | * Showing empathy towards other community members | |
15 | ||
16 | Examples of unacceptable behavior by participants include: | |
17 | ||
18 | * The use of sexualized language or imagery and unwelcome sexual attention or advances | |
19 | * Trolling, insulting/derogatory comments, and personal or political attacks | |
20 | * Public or private harassment | |
21 | * Publishing others' private information, such as a physical or electronic address, without explicit permission | |
22 | * Other conduct which could reasonably be considered inappropriate in a professional setting | |
23 | ||
24 | ## Our Responsibilities | |
25 | ||
26 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. | |
27 | ||
28 | Project maintainers 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, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. | |
29 | ||
30 | ## Scope | |
31 | ||
32 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. | |
33 | ||
34 | ## Enforcement | |
35 | ||
36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at drtuba78@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. | |
37 | ||
38 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. | |
39 | ||
40 | ## Attribution | |
41 | ||
42 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html][version] | |
43 | ||
44 | [homepage]: https://www.contributor-covenant.org/ | |
45 | [version]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html |
0 | # Contributing | |
1 | ||
2 | If you have an idea for how to improve `natsort`, please contribute! It can | |
3 | be as simple as a bug fix or documentation update, or as complicated as a more | |
4 | robust algorithm. Contributions that change the public API of | |
5 | `natsort` will have to ensure that the library does not become | |
6 | less usable after the contribution and is backwards-compatible (unless there is | |
7 | a good reason not to be). | |
8 | ||
9 | I do not have strong opinions on how one should contribute, so | |
10 | I have copy/pasted some text verbatim from the | |
11 | [Contributor's Guide](http://docs.python-requests.org/en/latest/dev/contributing/) section of | |
12 | [Kenneth Reitz's](http://docs.python-requests.org/en/latest/dev/contributing/) | |
13 | excellent [requests](https://github.com/kennethreitz/requests) library in | |
14 | lieu of coming up with my own. | |
15 | ||
16 | > ### Steps for Submitting Code | |
17 | ||
18 | > When contributing code, you'll want to follow this checklist: | |
19 | ||
20 | > - Fork the repository on GitHub. | |
21 | > - Run the tests to confirm they all pass on your system. | |
22 | If they don't, you'll need to investigate why they fail. | |
23 | If you're unable to diagnose this yourself, | |
24 | raise it as a bug report. | |
25 | > - Write tests that demonstrate your bug or feature. Ensure that they fail. | |
26 | > - Make your change. | |
27 | > - Run the entire test suite again, confirming that all tests pass including the | |
28 | ones you just added. | |
29 | > - Send a GitHub Pull Request to the main repository's master branch. | |
30 | GitHub Pull Requests are the expected method of code collaboration on this project. | |
31 | ||
32 | > ### Documentation Contributions | |
33 | > Documentation improvements are always welcome! The documentation files live in the | |
34 | docs/ directory of the codebase. They're written in | |
35 | [reStructuredText](http://docutils.sourceforge.net/rst.html), and use | |
36 | [Sphinx](http://sphinx-doc.org/index.html) | |
37 | to generate the full suite of documentation. | |
38 | ||
39 | > When contributing documentation, please do your best to follow the style of the | |
40 | documentation files. This means a soft-limit of 79 characters wide in your text | |
41 | files and a semi-formal, yet friendly and approachable, prose style. | |
42 | ||
43 | > When presenting Python code, use single-quoted strings ('hello' instead of "hello"). |
1 | 1 | include CHANGELOG.md |
2 | 2 | include tox.ini |
3 | 3 | include RELEASING.md |
4 | recursive-include mypy_stubs *.pyi | |
4 | 5 | graft dev |
5 | 6 | graft docs |
6 | 7 | graft natsort |
0 | Metadata-Version: 2.1 | |
1 | Name: natsort | |
2 | Version: 8.2.0 | |
3 | Summary: Simple yet flexible natural sorting in Python. | |
4 | Home-page: https://github.com/SethMMorton/natsort | |
5 | Author: Seth M. Morton | |
6 | Author-email: drtuba78@gmail.com | |
7 | License: MIT | |
8 | Classifier: Development Status :: 5 - Production/Stable | |
9 | Classifier: Intended Audience :: Developers | |
10 | Classifier: Intended Audience :: Science/Research | |
11 | Classifier: Intended Audience :: System Administrators | |
12 | Classifier: Intended Audience :: Information Technology | |
13 | Classifier: Intended Audience :: Financial and Insurance Industry | |
14 | Classifier: Operating System :: OS Independent | |
15 | Classifier: License :: OSI Approved :: MIT License | |
16 | Classifier: Natural Language :: English | |
17 | Classifier: Programming Language :: Python | |
18 | Classifier: Programming Language :: Python :: 3 | |
19 | Classifier: Programming Language :: Python :: 3.7 | |
20 | Classifier: Programming Language :: Python :: 3.8 | |
21 | Classifier: Programming Language :: Python :: 3.9 | |
22 | Classifier: Programming Language :: Python :: 3.10 | |
23 | Classifier: Topic :: Scientific/Engineering :: Information Analysis | |
24 | Classifier: Topic :: Utilities | |
25 | Classifier: Topic :: Text Processing | |
26 | Requires-Python: >=3.7 | |
27 | Description-Content-Type: text/x-rst | |
28 | Provides-Extra: fast | |
29 | Provides-Extra: icu | |
30 | License-File: LICENSE | |
31 | ||
32 | natsort | |
33 | ======= | |
34 | ||
35 | .. image:: https://img.shields.io/pypi/v/natsort.svg | |
36 | :target: https://pypi.org/project/natsort/ | |
37 | ||
38 | .. image:: https://img.shields.io/pypi/pyversions/natsort.svg | |
39 | :target: https://pypi.org/project/natsort/ | |
40 | ||
41 | .. image:: https://img.shields.io/pypi/l/natsort.svg | |
42 | :target: https://github.com/SethMMorton/natsort/blob/master/LICENSE | |
43 | ||
44 | .. image:: https://github.com/SethMMorton/natsort/workflows/Tests/badge.svg | |
45 | :target: https://github.com/SethMMorton/natsort/actions | |
46 | ||
47 | .. image:: https://codecov.io/gh/SethMMorton/natsort/branch/master/graph/badge.svg | |
48 | :target: https://codecov.io/gh/SethMMorton/natsort | |
49 | ||
50 | Simple yet flexible natural sorting in Python. | |
51 | ||
52 | - Source Code: https://github.com/SethMMorton/natsort | |
53 | - Downloads: https://pypi.org/project/natsort/ | |
54 | - Documentation: https://natsort.readthedocs.io/ | |
55 | ||
56 | - `Examples and Recipes <https://natsort.readthedocs.io/en/master/examples.html>`_ | |
57 | - `How Does Natsort Work? <https://natsort.readthedocs.io/en/master/howitworks.html>`_ | |
58 | - `API <https://natsort.readthedocs.io/en/master/api.html>`_ | |
59 | ||
60 | - `Quick Description`_ | |
61 | - `Quick Examples`_ | |
62 | - `FAQ`_ | |
63 | - `Requirements`_ | |
64 | - `Optional Dependencies`_ | |
65 | - `Installation`_ | |
66 | - `How to Run Tests`_ | |
67 | - `How to Build Documentation`_ | |
68 | - `Dropped Deprecated APIs`_ | |
69 | - `History`_ | |
70 | ||
71 | **NOTE**: Please see the `Dropped Deprecated APIs`_ section for changes. | |
72 | ||
73 | Quick Description | |
74 | ----------------- | |
75 | ||
76 | When you try to sort a list of strings that contain numbers, the normal python | |
77 | sort algorithm sorts lexicographically, so you might not get the results that | |
78 | you expect: | |
79 | ||
80 | .. code-block:: pycon | |
81 | ||
82 | >>> a = ['2 ft 7 in', '1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '7 ft 6 in'] | |
83 | >>> sorted(a) | |
84 | ['1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '2 ft 7 in', '7 ft 6 in'] | |
85 | ||
86 | Notice that it has the order ('1', '10', '2') - this is because the list is | |
87 | being sorted in lexicographical order, which sorts numbers like you would | |
88 | letters (i.e. 'b', 'ba', 'c'). | |
89 | ||
90 | ``natsort`` provides a function ``natsorted`` that helps sort lists | |
91 | "naturally" ("naturally" is rather ill-defined, but in general it means | |
92 | sorting based on meaning and not computer code point). | |
93 | Using ``natsorted`` is simple: | |
94 | ||
95 | .. code-block:: pycon | |
96 | ||
97 | >>> from natsort import natsorted | |
98 | >>> a = ['2 ft 7 in', '1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '7 ft 6 in'] | |
99 | >>> natsorted(a) | |
100 | ['1 ft 5 in', '2 ft 7 in', '2 ft 11 in', '7 ft 6 in', '10 ft 2 in'] | |
101 | ||
102 | ``natsorted`` identifies numbers anywhere in a string and sorts them | |
103 | naturally. Below are some other things you can do with ``natsort`` | |
104 | (also see the `examples <https://natsort.readthedocs.io/en/master/examples.html>`_ | |
105 | for a quick start guide, or the | |
106 | `api <https://natsort.readthedocs.io/en/master/api.html>`_ for complete details). | |
107 | ||
108 | **Note**: ``natsorted`` is designed to be a drop-in replacement for the | |
109 | built-in ``sorted`` function. Like ``sorted``, ``natsorted`` | |
110 | `does not sort in-place`. To sort a list and assign the output to the same | |
111 | variable, you must explicitly assign the output to a variable: | |
112 | ||
113 | .. code-block:: pycon | |
114 | ||
115 | >>> a = ['2 ft 7 in', '1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '7 ft 6 in'] | |
116 | >>> natsorted(a) | |
117 | ['1 ft 5 in', '2 ft 7 in', '2 ft 11 in', '7 ft 6 in', '10 ft 2 in'] | |
118 | >>> print(a) # 'a' was not sorted; "natsorted" simply returned a sorted list | |
119 | ['2 ft 7 in', '1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '7 ft 6 in'] | |
120 | >>> a = natsorted(a) # Now 'a' will be sorted because the sorted list was assigned to 'a' | |
121 | >>> print(a) | |
122 | ['1 ft 5 in', '2 ft 7 in', '2 ft 11 in', '7 ft 6 in', '10 ft 2 in'] | |
123 | ||
124 | Please see `Generating a Reusable Sorting Key and Sorting In-Place`_ for | |
125 | an alternate way to sort in-place naturally. | |
126 | ||
127 | Quick Examples | |
128 | -------------- | |
129 | ||
130 | - `Sorting Versions`_ | |
131 | - `Sort Paths Like My File Browser (e.g. Windows Explorer on Windows)`_ | |
132 | - `Sorting by Real Numbers (i.e. Signed Floats)`_ | |
133 | - `Locale-Aware Sorting (or "Human Sorting")`_ | |
134 | - `Further Customizing Natsort`_ | |
135 | - `Sorting Mixed Types`_ | |
136 | - `Handling Bytes`_ | |
137 | - `Generating a Reusable Sorting Key and Sorting In-Place`_ | |
138 | - `Other Useful Things`_ | |
139 | ||
140 | Sorting Versions | |
141 | ++++++++++++++++ | |
142 | ||
143 | ``natsort`` does not actually *comprehend* version numbers. | |
144 | It just so happens that the most common versioning schemes are designed to | |
145 | work with standard natural sorting techniques; these schemes include | |
146 | ``MAJOR.MINOR``, ``MAJOR.MINOR.PATCH``, ``YEAR.MONTH.DAY``. If your data | |
147 | conforms to a scheme like this, then it will work out-of-the-box with | |
148 | ``natsorted`` (as of ``natsort`` version >= 4.0.0): | |
149 | ||
150 | .. code-block:: pycon | |
151 | ||
152 | >>> a = ['version-1.9', 'version-2.0', 'version-1.11', 'version-1.10'] | |
153 | >>> natsorted(a) | |
154 | ['version-1.9', 'version-1.10', 'version-1.11', 'version-2.0'] | |
155 | ||
156 | If you need to versions that use a more complicated scheme, please see | |
157 | `these examples <https://natsort.readthedocs.io/en/master/examples.html#rc-sorting>`_. | |
158 | ||
159 | Sort Paths Like My File Browser (e.g. Windows Explorer on Windows) | |
160 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
161 | ||
162 | Prior to ``natsort`` version 7.1.0, it was a common request to be able to | |
163 | sort paths like Windows Explorer. As of ``natsort`` 7.1.0, the function | |
164 | ``os_sorted`` has been added to provide users the ability to sort | |
165 | in the order that their file browser might sort (e.g Windows Explorer on | |
166 | Windows, Finder on MacOS, Dolphin/Nautilus/Thunar/etc. on Linux). | |
167 | ||
168 | .. code-block:: python | |
169 | ||
170 | import os | |
171 | from natsort import os_sorted | |
172 | print(os_sorted(os.listdir())) | |
173 | # The directory sorted like your file browser might show | |
174 | ||
175 | Output will be different depending on the operating system you are on. | |
176 | ||
177 | For users **not** on Windows (e.g. MacOS/Linux) it is **strongly** recommended | |
178 | to also install `PyICU <https://pypi.org/project/PyICU>`_, which will help | |
179 | ``natsort`` give results that match most file browsers. If this is not installed, | |
180 | it will fall back on Python's built-in ``locale`` module and will give good | |
181 | results for most input, but will give poor results for special characters. | |
182 | ||
183 | Sorting by Real Numbers (i.e. Signed Floats) | |
184 | ++++++++++++++++++++++++++++++++++++++++++++ | |
185 | ||
186 | This is useful in scientific data analysis (and was | |
187 | the default behavior of ``natsorted`` for ``natsort`` | |
188 | version < 4.0.0). Use the ``realsorted`` function: | |
189 | ||
190 | .. code-block:: pycon | |
191 | ||
192 | >>> from natsort import realsorted, ns | |
193 | >>> # Note that when interpreting as signed floats, the below numbers are | |
194 | >>> # +5.10, -3.00, +5.30, +2.00 | |
195 | >>> a = ['position5.10.data', 'position-3.data', 'position5.3.data', 'position2.data'] | |
196 | >>> natsorted(a) | |
197 | ['position2.data', 'position5.3.data', 'position5.10.data', 'position-3.data'] | |
198 | >>> natsorted(a, alg=ns.REAL) | |
199 | ['position-3.data', 'position2.data', 'position5.10.data', 'position5.3.data'] | |
200 | >>> realsorted(a) # shortcut for natsorted with alg=ns.REAL | |
201 | ['position-3.data', 'position2.data', 'position5.10.data', 'position5.3.data'] | |
202 | ||
203 | Locale-Aware Sorting (or "Human Sorting") | |
204 | +++++++++++++++++++++++++++++++++++++++++ | |
205 | ||
206 | This is where the non-numeric characters are also ordered based on their | |
207 | meaning, not on their ordinal value, and a locale-dependent thousands | |
208 | separator and decimal separator is accounted for in the number. | |
209 | This can be achieved with the ``humansorted`` function: | |
210 | ||
211 | .. code-block:: pycon | |
212 | ||
213 | >>> a = ['Apple', 'apple15', 'Banana', 'apple14,689', 'banana'] | |
214 | >>> natsorted(a) | |
215 | ['Apple', 'Banana', 'apple14,689', 'apple15', 'banana'] | |
216 | >>> import locale | |
217 | >>> locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') | |
218 | 'en_US.UTF-8' | |
219 | >>> natsorted(a, alg=ns.LOCALE) | |
220 | ['apple15', 'apple14,689', 'Apple', 'banana', 'Banana'] | |
221 | >>> from natsort import humansorted | |
222 | >>> humansorted(a) # shortcut for natsorted with alg=ns.LOCALE | |
223 | ['apple15', 'apple14,689', 'Apple', 'banana', 'Banana'] | |
224 | ||
225 | You may find you need to explicitly set the locale to get this to work | |
226 | (as shown in the example). | |
227 | Please see `locale issues <https://natsort.readthedocs.io/en/master/locale_issues.html>`_ and the | |
228 | `Optional Dependencies`_ section below before using the ``humansorted`` function. | |
229 | ||
230 | Further Customizing Natsort | |
231 | +++++++++++++++++++++++++++ | |
232 | ||
233 | If you need to combine multiple algorithm modifiers (such as ``ns.REAL``, | |
234 | ``ns.LOCALE``, and ``ns.IGNORECASE``), you can combine the options using the | |
235 | bitwise OR operator (``|``). For example, | |
236 | ||
237 | .. code-block:: pycon | |
238 | ||
239 | >>> a = ['Apple', 'apple15', 'Banana', 'apple14,689', 'banana'] | |
240 | >>> natsorted(a, alg=ns.REAL | ns.LOCALE | ns.IGNORECASE) | |
241 | ['Apple', 'apple15', 'apple14,689', 'Banana', 'banana'] | |
242 | >>> # The ns enum provides long and short forms for each option. | |
243 | >>> ns.LOCALE == ns.L | |
244 | True | |
245 | >>> # You can also customize the convenience functions, too. | |
246 | >>> natsorted(a, alg=ns.REAL | ns.LOCALE | ns.IGNORECASE) == realsorted(a, alg=ns.L | ns.IC) | |
247 | True | |
248 | >>> natsorted(a, alg=ns.REAL | ns.LOCALE | ns.IGNORECASE) == humansorted(a, alg=ns.R | ns.IC) | |
249 | True | |
250 | ||
251 | All of the available customizations can be found in the documentation for | |
252 | `the ns enum <https://natsort.readthedocs.io/en/master/api.html#natsort.ns>`_. | |
253 | ||
254 | You can also add your own custom transformation functions with the ``key`` | |
255 | argument. These can be used with ``alg`` if you wish. | |
256 | ||
257 | .. code-block:: pycon | |
258 | ||
259 | >>> a = ['apple2.50', '2.3apple'] | |
260 | >>> natsorted(a, key=lambda x: x.replace('apple', ''), alg=ns.REAL) | |
261 | ['2.3apple', 'apple2.50'] | |
262 | ||
263 | Sorting Mixed Types | |
264 | +++++++++++++++++++ | |
265 | ||
266 | You can mix and match ``int``, ``float``, and ``str`` (or ``unicode``) types | |
267 | when you sort: | |
268 | ||
269 | .. code-block:: pycon | |
270 | ||
271 | >>> a = ['4.5', 6, 2.0, '5', 'a'] | |
272 | >>> natsorted(a) | |
273 | [2.0, '4.5', '5', 6, 'a'] | |
274 | >>> # sorted(a) would raise an "unorderable types" TypeError | |
275 | ||
276 | Handling Bytes | |
277 | ++++++++++++++ | |
278 | ||
279 | ``natsort`` does not officially support the `bytes` type, but | |
280 | convenience functions are provided that help you decode to `str` first: | |
281 | ||
282 | .. code-block:: pycon | |
283 | ||
284 | >>> from natsort import as_utf8 | |
285 | >>> a = [b'a', 14.0, 'b'] | |
286 | >>> # natsorted(a) would raise a TypeError (bytes() < str()) | |
287 | >>> natsorted(a, key=as_utf8) == [14.0, b'a', 'b'] | |
288 | True | |
289 | >>> a = [b'a56', b'a5', b'a6', b'a40'] | |
290 | >>> # natsorted(a) would return the same results as sorted(a) | |
291 | >>> natsorted(a, key=as_utf8) == [b'a5', b'a6', b'a40', b'a56'] | |
292 | True | |
293 | ||
294 | Generating a Reusable Sorting Key and Sorting In-Place | |
295 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
296 | ||
297 | Under the hood, ``natsorted`` works by generating a custom sorting | |
298 | key using ``natsort_keygen`` and then passes that to the built-in | |
299 | ``sorted``. You can use the ``natsort_keygen`` function yourself to | |
300 | generate a custom sorting key to sort in-place using the ``list.sort`` | |
301 | method. | |
302 | ||
303 | .. code-block:: pycon | |
304 | ||
305 | >>> from natsort import natsort_keygen | |
306 | >>> natsort_key = natsort_keygen() | |
307 | >>> a = ['2 ft 7 in', '1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '7 ft 6 in'] | |
308 | >>> natsorted(a) == sorted(a, key=natsort_key) | |
309 | True | |
310 | >>> a.sort(key=natsort_key) | |
311 | >>> a | |
312 | ['1 ft 5 in', '2 ft 7 in', '2 ft 11 in', '7 ft 6 in', '10 ft 2 in'] | |
313 | ||
314 | All of the algorithm customizations mentioned in the | |
315 | `Further Customizing Natsort`_ section can also be applied to | |
316 | ``natsort_keygen`` through the *alg* keyword option. | |
317 | ||
318 | Other Useful Things | |
319 | +++++++++++++++++++ | |
320 | ||
321 | - recursively descend into lists of lists | |
322 | - automatic unicode normalization of input data | |
323 | - `controlling the case-sensitivity <https://natsort.readthedocs.io/en/master/examples.html#case-sort>`_ | |
324 | - `sorting file paths correctly <https://natsort.readthedocs.io/en/master/examples.html#path-sort>`_ | |
325 | - `allow custom sorting keys <https://natsort.readthedocs.io/en/master/examples.html#custom-sort>`_ | |
326 | - `accounting for units <https://natsort.readthedocs.io/en/master/examples.html#accounting-for-units-when-sorting>`_ | |
327 | ||
328 | FAQ | |
329 | --- | |
330 | ||
331 | How do I debug ``natsort.natsorted()``? | |
332 | The best way to debug ``natsorted()`` is to generate a key using ``natsort_keygen()`` | |
333 | with the same options being passed to ``natsorted``. One can take a look at | |
334 | exactly what is being done with their input using this key - it is highly | |
335 | recommended | |
336 | to `look at this issue describing how to debug <https://github.com/SethMMorton/natsort/issues/13#issuecomment-50422375>`_ | |
337 | for *how* to debug, and also to review the | |
338 | `How Does Natsort Work? <https://natsort.readthedocs.io/en/master/howitworks.html>`_ | |
339 | page for *why* ``natsort`` is doing that to your data. | |
340 | ||
341 | If you are trying to sort custom classes and running into trouble, please | |
342 | take a look at https://github.com/SethMMorton/natsort/issues/60. In short, | |
343 | custom classes are not likely to be sorted correctly if one relies | |
344 | on the behavior of ``__lt__`` and the other rich comparison operators in | |
345 | their custom class - it is better to use a ``key`` function with | |
346 | ``natsort``, or use the ``natsort`` key as part of your rich comparison | |
347 | operator definition. | |
348 | ||
349 | ``natsort`` gave me results I didn't expect, and it's a terrible library! | |
350 | Did you try to debug using the above advice? If so, and you still cannot figure out | |
351 | the error, then please `file an issue <https://github.com/SethMMorton/natsort/issues/new>`_. | |
352 | ||
353 | How *does* ``natsort`` work? | |
354 | If you don't want to read `How Does Natsort Work? <https://natsort.readthedocs.io/en/master/howitworks.html>`_, | |
355 | here is a quick primer. | |
356 | ||
357 | ``natsort`` provides a `key function <https://docs.python.org/3/howto/sorting.html#key-functions>`_ | |
358 | that can be passed to `list.sort() <https://docs.python.org/3/library/stdtypes.html#list.sort>`_ | |
359 | or `sorted() <https://docs.python.org/3/library/functions.html#sorted>`_ in order to | |
360 | modify the default sorting behavior. This key is generated on-demand with | |
361 | the key generator ``natsort.natsort_keygen()``. ``natsort.natsorted()`` | |
362 | is essentially a wrapper for the following code: | |
363 | ||
364 | .. code-block:: pycon | |
365 | ||
366 | >>> from natsort import natsort_keygen | |
367 | >>> natsort_key = natsort_keygen() | |
368 | >>> sorted(['1', '10', '2'], key=natsort_key) | |
369 | ['1', '2', '10'] | |
370 | ||
371 | Users can further customize ``natsort`` sorting behavior with the ``key`` | |
372 | and/or ``alg`` options (see details in the `Further Customizing Natsort`_ | |
373 | section). | |
374 | ||
375 | The key generated by ``natsort_keygen`` *always* returns a ``tuple``. It | |
376 | does so in the following way (*some details omitted for clarity*): | |
377 | ||
378 | 1. Assume the input is a string, and attempt to split it into numbers and | |
379 | non-numbers using regular expressions. Numbers are then converted into | |
380 | either ``int`` or ``float``. | |
381 | 2. If the above fails because the input is not a string, assume the input | |
382 | is some other sequence (e.g. ``list`` or ``tuple``), and recursively | |
383 | apply the key to each element of the sequence. | |
384 | 3. If the above fails because the input is not iterable, assume the input | |
385 | is an ``int`` or ``float``, and just return the input in a ``tuple``. | |
386 | ||
387 | Because a ``tuple`` is always returned, a ``TypeError`` should not be common | |
388 | unless one tries to do something odd like sort an ``int`` against a ``list``. | |
389 | ||
390 | Shell script | |
391 | ------------ | |
392 | ||
393 | ``natsort`` comes with a shell script called ``natsort``, or can also be called | |
394 | from the command line with ``python -m natsort``. | |
395 | ||
396 | Requirements | |
397 | ------------ | |
398 | ||
399 | ``natsort`` requires Python 3.7 or greater. | |
400 | ||
401 | Optional Dependencies | |
402 | --------------------- | |
403 | ||
404 | fastnumbers | |
405 | +++++++++++ | |
406 | ||
407 | The most efficient sorting can occur if you install the | |
408 | `fastnumbers <https://pypi.org/project/fastnumbers>`_ package | |
409 | (version >=2.0.0); it helps with the string to number conversions. | |
410 | ``natsort`` will still run (efficiently) without the package, but if you need | |
411 | to squeeze out that extra juice it is recommended you include this as a | |
412 | dependency. ``natsort`` will not require (or check) that | |
413 | `fastnumbers <https://pypi.org/project/fastnumbers>`_ is installed | |
414 | at installation. | |
415 | ||
416 | PyICU | |
417 | +++++ | |
418 | ||
419 | It is recommended that you install `PyICU <https://pypi.org/project/PyICU>`_ | |
420 | if you wish to sort in a locale-dependent manner, see | |
421 | https://natsort.readthedocs.io/en/master/locale_issues.html for an explanation why. | |
422 | ||
423 | Installation | |
424 | ------------ | |
425 | ||
426 | Use ``pip``! | |
427 | ||
428 | .. code-block:: console | |
429 | ||
430 | $ pip install natsort | |
431 | ||
432 | If you want to install the `Optional Dependencies`_, you can use the | |
433 | `"extras" notation <https://packaging.python.org/tutorials/installing-packages/#installing-setuptools-extras>`_ | |
434 | at installation time to install those dependencies as well - use ``fast`` for | |
435 | `fastnumbers <https://pypi.org/project/fastnumbers>`_ and ``icu`` for | |
436 | `PyICU <https://pypi.org/project/PyICU>`_. | |
437 | ||
438 | .. code-block:: console | |
439 | ||
440 | # Install both optional dependencies. | |
441 | $ pip install natsort[fast,icu] | |
442 | # Install just fastnumbers | |
443 | $ pip install natsort[fast] | |
444 | ||
445 | How to Run Tests | |
446 | ---------------- | |
447 | ||
448 | Please note that ``natsort`` is NOT set-up to support ``python setup.py test``. | |
449 | ||
450 | The recommended way to run tests is with `tox <https://tox.readthedocs.io/en/latest/>`_. | |
451 | After installing ``tox``, running tests is as simple as executing the following | |
452 | in the ``natsort`` directory: | |
453 | ||
454 | .. code-block:: console | |
455 | ||
456 | $ tox | |
457 | ||
458 | ``tox`` will create virtual a virtual environment for your tests and install | |
459 | all the needed testing requirements for you. You can specify a particular | |
460 | python version with the ``-e`` flag, e.g. ``tox -e py36``. Static analysis | |
461 | is done with ``tox -e flake8``. You can see all available testing environments | |
462 | with ``tox --listenvs``. | |
463 | ||
464 | How to Build Documentation | |
465 | -------------------------- | |
466 | ||
467 | If you want to build the documentation for ``natsort``, it is recommended to | |
468 | use ``tox``: | |
469 | ||
470 | .. code-block:: console | |
471 | ||
472 | $ tox -e docs | |
473 | ||
474 | This will place the documentation in ``build/sphinx/html``. | |
475 | ||
476 | Dropped Deprecated APIs | |
477 | ----------------------- | |
478 | ||
479 | In ``natsort`` version 6.0.0, the following APIs and functions were removed | |
480 | ||
481 | - ``number_type`` keyword argument (deprecated since 3.4.0) | |
482 | - ``signed`` keyword argument (deprecated since 3.4.0) | |
483 | - ``exp`` keyword argument (deprecated since 3.4.0) | |
484 | - ``as_path`` keyword argument (deprecated since 3.4.0) | |
485 | - ``py3_safe`` keyword argument (deprecated since 3.4.0) | |
486 | - ``ns.TYPESAFE`` (deprecated since version 5.0.0) | |
487 | - ``ns.DIGIT`` (deprecated since version 5.0.0) | |
488 | - ``ns.VERSION`` (deprecated since version 5.0.0) | |
489 | - ``versorted()`` (discouraged since version 4.0.0, | |
490 | officially deprecated since version 5.5.0) | |
491 | - ``index_versorted()`` (discouraged since version 4.0.0, | |
492 | officially deprecated since version 5.5.0) | |
493 | ||
494 | In general, if you want to determine if you are using deprecated APIs you | |
495 | can run your code with the following flag | |
496 | ||
497 | .. code-block:: console | |
498 | ||
499 | $ python -Wdefault::DeprecationWarning my-code.py | |
500 | ||
501 | By default ``DeprecationWarnings`` are not shown, but this will cause them | |
502 | to be shown. Alternatively, you can just set the environment variable | |
503 | ``PYTHONWARNINGS`` to "default::DeprecationWarning" and then run your code. | |
504 | ||
505 | Author | |
506 | ------ | |
507 | ||
508 | Seth M. Morton | |
509 | ||
510 | History | |
511 | ------- | |
512 | ||
513 | Please visit the changelog | |
514 | `on GitHub <https://github.com/SethMMorton/natsort/blob/master/CHANGELOG.md>`_ or | |
515 | `in the documentation <https://natsort.readthedocs.io/en/master/changelog.html>`_. |
33 | 33 | - `Installation`_ |
34 | 34 | - `How to Run Tests`_ |
35 | 35 | - `How to Build Documentation`_ |
36 | - `Deprecation Schedule`_ | |
36 | - `Dropped Deprecated APIs`_ | |
37 | 37 | - `History`_ |
38 | 38 | |
39 | **NOTE**: Please see the `Deprecation Schedule`_ section for changes in | |
40 | ``natsort`` version 7.0.0. | |
39 | **NOTE**: Please see the `Dropped Deprecated APIs`_ section for changes. | |
41 | 40 | |
42 | 41 | Quick Description |
43 | 42 | ----------------- |
102 | 101 | - `Locale-Aware Sorting (or "Human Sorting")`_ |
103 | 102 | - `Further Customizing Natsort`_ |
104 | 103 | - `Sorting Mixed Types`_ |
105 | - `Handling Bytes on Python 3`_ | |
104 | - `Handling Bytes`_ | |
106 | 105 | - `Generating a Reusable Sorting Key and Sorting In-Place`_ |
107 | 106 | - `Other Useful Things`_ |
108 | 107 | |
240 | 239 | >>> a = ['4.5', 6, 2.0, '5', 'a'] |
241 | 240 | >>> natsorted(a) |
242 | 241 | [2.0, '4.5', '5', 6, 'a'] |
243 | >>> # On Python 2, sorted(a) would return [2.0, 6, '4.5', '5', 'a'] | |
244 | >>> # On Python 3, sorted(a) would raise an "unorderable types" TypeError | |
245 | ||
246 | Handling Bytes on Python 3 | |
247 | ++++++++++++++++++++++++++ | |
248 | ||
249 | ``natsort`` does not officially support the `bytes` type on Python 3, but | |
242 | >>> # sorted(a) would raise an "unorderable types" TypeError | |
243 | ||
244 | Handling Bytes | |
245 | ++++++++++++++ | |
246 | ||
247 | ``natsort`` does not officially support the `bytes` type, but | |
250 | 248 | convenience functions are provided that help you decode to `str` first: |
251 | 249 | |
252 | 250 | .. code-block:: pycon |
253 | 251 | |
254 | 252 | >>> from natsort import as_utf8 |
255 | 253 | >>> a = [b'a', 14.0, 'b'] |
256 | >>> # On Python 2, natsorted(a) would would work as expected. | |
257 | >>> # On Python 3, natsorted(a) would raise a TypeError (bytes() < str()) | |
254 | >>> # natsorted(a) would raise a TypeError (bytes() < str()) | |
258 | 255 | >>> natsorted(a, key=as_utf8) == [14.0, b'a', 'b'] |
259 | 256 | True |
260 | 257 | >>> a = [b'a56', b'a5', b'a6', b'a40'] |
261 | >>> # On Python 2, natsorted(a) would would work as expected. | |
262 | >>> # On Python 3, natsorted(a) would return the same results as sorted(a) | |
258 | >>> # natsorted(a) would return the same results as sorted(a) | |
263 | 259 | >>> natsorted(a, key=as_utf8) == [b'a5', b'a6', b'a40', b'a56'] |
264 | 260 | True |
265 | 261 | |
368 | 364 | Requirements |
369 | 365 | ------------ |
370 | 366 | |
371 | ``natsort`` requires Python 3.6 or greater. | |
367 | ``natsort`` requires Python 3.7 or greater. | |
372 | 368 | |
373 | 369 | Optional Dependencies |
374 | 370 | --------------------- |
445 | 441 | |
446 | 442 | This will place the documentation in ``build/sphinx/html``. |
447 | 443 | |
448 | Deprecation Schedule | |
449 | -------------------- | |
450 | ||
451 | Dropped Python 3.4 and Python 3.5 Support | |
452 | +++++++++++++++++++++++++++++++++++++++++ | |
453 | ||
454 | ``natsort`` version 8.0.0 dropped support for Python < 3.6. | |
455 | ||
456 | Dropped Python 2.7 Support | |
457 | ++++++++++++++++++++++++++ | |
458 | ||
459 | ``natsort`` version 7.0.0 dropped support for Python 2.7. | |
460 | ||
461 | The version 6.X branch will remain as a "long term support" branch where bug | |
462 | fixes are applied so that users who cannot update from Python 2.7 will not be | |
463 | forced to use a buggy ``natsort`` version (bug fixes will need to be requested; | |
464 | by default only the 7.X branch will be updated). | |
465 | New features would not be added to version 6.X, only bug fixes. | |
466 | ||
467 | 444 | Dropped Deprecated APIs |
468 | +++++++++++++++++++++++ | |
445 | ----------------------- | |
469 | 446 | |
470 | 447 | In ``natsort`` version 6.0.0, the following APIs and functions were removed |
471 | 448 |
0 | natsort (8.2.0+git20221218.1.837a387-1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream snapshot. | |
3 | ||
4 | -- Debian Janitor <janitor@jelmer.uk> Mon, 19 Dec 2022 06:11:09 -0000 | |
5 | ||
0 | 6 | natsort (8.0.2-2) unstable; urgency=medium |
1 | 7 | |
2 | 8 | [ Debian Janitor ] |
82 | 82 | |
83 | 83 | .. _bytes_help: |
84 | 84 | |
85 | Help With Bytes On Python 3 | |
86 | +++++++++++++++++++++++++++ | |
85 | Help With Bytes | |
86 | +++++++++++++++ | |
87 | 87 | |
88 | 88 | The official stance of :mod:`natsort` is to not support `bytes` for |
89 | 89 | sorting; there is just too much that can go wrong when trying to automate |
120 | 120 | Help With Type Hinting |
121 | 121 | ++++++++++++++++++++++ |
122 | 122 | |
123 | If you need to explictly specify the types that natsort accepts or returns | |
123 | If you need to explicitly specify the types that natsort accepts or returns | |
124 | 124 | in your code, the following types have been exposed for your convenience. |
125 | 125 | |
126 | 126 | +--------------------------------+----------------------------------------------------------------------------------------+ |
58 | 58 | # built documents. |
59 | 59 | # |
60 | 60 | # The full version, including alpha/beta/rc tags. |
61 | release = "8.0.2" | |
61 | release = "8.2.0" | |
62 | 62 | # The short X.Y version. |
63 | 63 | version = ".".join(release.split(".")[0:2]) |
64 | 64 |
157 | 157 | >>> natsorted(a, alg=ns.IGNORECASE) |
158 | 158 | ['Apple', 'apple', 'Banana', 'banana', 'corn', 'Corn'] |
159 | 159 | |
160 | Note thats since Python's sorting is stable, the order of equivalent | |
160 | Note that's since Python's sorting is stable, the order of equivalent | |
161 | 161 | elements after lowering the case is the same order they appear in the |
162 | 162 | original list. |
163 | 163 | |
348 | 348 | >>> natsorted(a, reverse=True) |
349 | 349 | ['a10', 'a9', 'a4', 'a2', 'a1'] |
350 | 350 | |
351 | Sorting Bytes on Python 3 | |
352 | ------------------------- | |
353 | ||
354 | Python 3 is rather strict about comparing strings and bytes, and this | |
351 | Sorting Bytes | |
352 | ------------- | |
353 | ||
354 | Python is rather strict about comparing strings and bytes, and this | |
355 | 355 | can make it difficult to deal with collections of both. Because of the |
356 | 356 | challenge of guessing which encoding should be used to decode a bytes |
357 | 357 | array to a string, :mod:`natsort` does *not* try to guess and automatically |
367 | 367 | |
368 | 368 | >>> from natsort import as_ascii |
369 | 369 | >>> a = [b'a', 14.0, 'b'] |
370 | >>> # On Python 2, natsorted(a) would would work as expected. | |
371 | >>> # On Python 3, natsorted(a) would raise a TypeError (bytes() < str()) | |
370 | >>> # natsorted(a) would raise a TypeError (bytes() < str()) | |
372 | 371 | >>> natsorted(a, key=as_ascii) == [14.0, b'a', 'b'] |
373 | 372 | True |
374 | 373 |
412 | 412 | >>> a > b |
413 | 413 | True |
414 | 414 | |
415 | Comparing Different Types on Python 3 | |
416 | +++++++++++++++++++++++++++++++++++++ | |
415 | .. note:: | |
416 | ||
417 | The actual :meth:`decompose_path_into_components`-equivalent function in | |
418 | :mod:`natsort` actually has a few more heuristics than shown here so that | |
419 | it is not over-zealous in what it defines as a path suffix, but this has | |
420 | been omitted in this how-to for clarity. | |
421 | ||
422 | Comparing Different Types | |
423 | +++++++++++++++++++++++++ | |
417 | 424 | |
418 | 425 | `The second major special case I encountered was sorting of different types`_. |
419 | If you are on Python 2 (i.e. legacy Python), this mostly doesn't matter *too* | |
426 | On Python 2 (i.e. legacy Python), this mostly didnt't matter *too* | |
420 | 427 | much since it uses an arbitrary heuristic to allow traditionally un-comparable |
421 | 428 | types to be compared (such as comparing ``'a'`` to ``1``). However, on Python 3 |
422 | 429 | (i.e. Python) it simply won't let you perform such nonsense, raising a |
661 | 668 | #. :mod:`locale` is a thin wrapper over your operating system's *locale* |
662 | 669 | library, so if *that* is broken (like it is on BSD and OSX) then |
663 | 670 | :mod:`locale` is broken in Python. |
664 | #. Because of a bug in legacy Python (i.e. Python 2), there is no uniform | |
671 | #. Because of a bug in legacy Python (i.e. Python 2), there was no uniform | |
665 | 672 | way to use the :mod:`locale` sorting functionality between legacy Python |
666 | and Python 3. | |
673 | and Python (luckily this is no longer an issue now that Python 2 is EOL). | |
667 | 674 | #. People have differing opinions of how capitalization should affect word |
668 | 675 | order. |
669 | 676 | #. There is no built-in way to handle locale-dependent thousands separators |
714 | 721 | >>> sorted(a, key=lambda x: x.swapcase()) |
715 | 722 | ['apple', 'banana', 'corn', 'Apple', 'Banana', 'Corn'] |
716 | 723 | |
717 | The last (i call it *IGNORECASE*) should be super easy, right? | |
718 | Simply call :meth:`str.lowercase` on the input. This will work but may | |
719 | not always give the correct answer on non-latin character sets. It's | |
720 | a good thing that in Python 3.3 | |
721 | :meth:`str.casefold` was introduced, which does a better job of removing | |
722 | all case information from unicode characters in | |
723 | non-latin alphabets. | |
724 | ||
725 | .. code-block:: pycon | |
726 | ||
727 | >>> def remove_case(x): | |
728 | ... try: | |
729 | ... return x.casefold() | |
730 | ... except AttributeError: # Legacy Python backwards compatibility | |
731 | ... return x.lowercase() | |
732 | ... | |
733 | >>> sorted(a, key=remove_case) | |
724 | The last (i call it *IGNORECASE*) is pretty easy. | |
725 | Simply call :meth:`str.casefold` on the input (it's like :meth:`std.lowercase` | |
726 | but does a better job on non-latin character sets). | |
727 | ||
728 | .. code-block:: pycon | |
729 | ||
730 | >>> sorted(a, key=lambda x: x.casefold()) | |
734 | 731 | ['Apple', 'apple', 'Banana', 'banana', 'corn', 'Corn'] |
735 | 732 | |
736 | 733 | The middle case (I call it *GROUPLETTERS*) is less straightforward. |
741 | 738 | |
742 | 739 | >>> import itertools |
743 | 740 | >>> def groupletters(x): |
744 | ... return ''.join(itertools.chain.from_iterable((remove_case(y), y) for y in x)) | |
741 | ... return ''.join(itertools.chain.from_iterable((y.casefold(), y) for y in x)) | |
745 | 742 | ... |
746 | 743 | >>> groupletters('Apple') |
747 | 744 | 'aAppppllee' |
903 | 900 | be applied as part of the :func:`coerce_to_int`/:func:`coerce_to_float` |
904 | 901 | functions in a manner similar to :func:`groupletters`. |
905 | 902 | |
906 | As you might have guessed, there is a small problem. | |
907 | It turns out the there is a bug in the legacy Python implementation of | |
908 | :func:`locale.strxfrm` that causes it to outright fail for :func:`unicode` | |
909 | input (https://bugs.python.org/issue2481). :func:`locale.strcoll` works, | |
910 | but is intended for use with ``cmp``, which does not exist in current Python | |
911 | implementations. Luckily, the :func:`functools.cmp_to_key` function | |
912 | makes :func:`locale.strcoll` behave like :func:`locale.strxfrm`. | |
903 | Unicode Support With Local | |
904 | ++++++++++++++++++++++++++ | |
905 | ||
906 | Remember how in the `Basic Unicode Support`_ section I mentioned that we | |
907 | use the "decompressed" Unicode normalization form (e.g. NFD) on all inputs | |
908 | to ensure the order is as expected? | |
909 | ||
910 | If you have been following along so far, you probably expect that it is not | |
911 | that easy. You would be correct. | |
912 | ||
913 | It turns out that some locales (but not all) expect the input to be in | |
914 | "compressed form" (e.g. NFC) or the ordering is not as you might expect. | |
915 | `Check out this issue for a real-world example`_. Here's a relevant | |
916 | snippet of code | |
917 | ||
918 | .. code-block:: pycon | |
919 | ||
920 | In [1]: import locale, unicodedata | |
921 | ||
922 | In [2]: a = ['Aš', 'Cheb', 'Česko', 'Cibulov', 'Znojmo', 'Žilina'] | |
923 | ||
924 | In [3]: locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') | |
925 | Out[3]: 'en_US.UTF-8' | |
926 | ||
927 | In [4]: sorted(a, key=locale.strxfrm) | |
928 | Out[4]: ['Aš', 'Česko', 'Cheb', 'Cibulov', 'Žilina', 'Znojmo'] | |
929 | ||
930 | In [5]: sorted(a, key=lambda x: locale.strxfrm(unicodedata.normalize("NFD", x))) | |
931 | Out[5]: ['Aš', 'Česko', 'Cheb', 'Cibulov', 'Žilina', 'Znojmo'] | |
932 | ||
933 | In [6]: sorted(a, key=lambda x: locale.strxfrm(unicodedata.normalize("NFC", x))) | |
934 | Out[6]: ['Aš', 'Česko', 'Cheb', 'Cibulov', 'Žilina', 'Znojmo'] | |
935 | ||
936 | In [7]: locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8') | |
937 | Out[7]: 'de_DE.UTF-8' | |
938 | ||
939 | In [8]: sorted(a, key=locale.strxfrm) | |
940 | Out[8]: ['Aš', 'Česko', 'Cheb', 'Cibulov', 'Žilina', 'Znojmo'] | |
941 | ||
942 | In [9]: sorted(a, key=lambda x: locale.strxfrm(unicodedata.normalize("NFD", x))) | |
943 | Out[9]: ['Aš', 'Česko', 'Cheb', 'Cibulov', 'Žilina', 'Znojmo'] | |
944 | ||
945 | In [10]: sorted(a, key=lambda x: locale.strxfrm(unicodedata.normalize("NFC", x))) | |
946 | Out[10]: ['Aš', 'Česko', 'Cheb', 'Cibulov', 'Žilina', 'Znojmo'] | |
947 | ||
948 | In [11]: locale.setlocale(locale.LC_ALL, 'cs_CZ.UTF-8') | |
949 | Out[11]: 'cs_CZ.UTF-8' | |
950 | ||
951 | In [12]: sorted(a, key=locale.strxfrm) | |
952 | Out[12]: ['Aš', 'Cibulov', 'Česko', 'Cheb', 'Znojmo', 'Žilina'] | |
953 | ||
954 | In [13]: sorted(a, key=lambda x: locale.strxfrm(unicodedata.normalize("NFD", x))) | |
955 | Out[13]: ['Aš', 'Česko', 'Cibulov', 'Cheb', 'Žilina', 'Znojmo'] | |
956 | ||
957 | In [14]: sorted(a, key=lambda x: locale.strxfrm(unicodedata.normalize("NFC", x))) | |
958 | Out[14]: ['Aš', 'Cibulov', 'Česko', 'Cheb', 'Znojmo', 'Žilina'] | |
959 | ||
960 | Two out of three locales sort the same data in the same order no matter how the unicode | |
961 | input was normalized, but Czech seems to care how the input is formatted! | |
962 | ||
963 | So, everthing mentioned in `Basic Unicode Support`_ is conditional on whether | |
964 | or not the user wants to use the :mod:`locale` library or not. If not, then | |
965 | "NFD" normalization is used. If they do, "NFC" normalization is used. | |
913 | 966 | |
914 | 967 | Handling Broken Locale On OSX |
915 | 968 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1120 | 1173 | .. _really good: https://hypothesis.readthedocs.io/en/latest/ |
1121 | 1174 | .. _testing strategy: https://docs.pytest.org/en/latest/ |
1122 | 1175 | .. _check out some official Unicode documentation: https://unicode.org/reports/tr15/ |
1176 | .. _Check out this issue for a real-world example: https://github.com/SethMMorton/natsort/issues/140⏎ |
22 | 22 | |
23 | 23 | When :func:`~natsort.natsort_keygen` is called it returns a key function that |
24 | 24 | hard-codes the provided settings. This means that the key returned when |
25 | ``ns.LOCALE`` is used contains the settings specifed by the locale | |
25 | ``ns.LOCALE`` is used contains the settings specified by the locale | |
26 | 26 | *loaded at the time the key is generated*. If you change the locale, |
27 | 27 | you should regenerate the key to account for the new locale. |
28 | 28 |
0 | from typing import overload | |
1 | ||
2 | @overload | |
3 | def Locale() -> str: ... | |
4 | @overload | |
5 | def Locale(x: str) -> str: ... | |
6 | ||
7 | class UCollAttribute: | |
8 | NUMERIC_COLLATION: int | |
9 | ||
10 | class UCollAttributeValue: | |
11 | ON: int | |
12 | ||
13 | class DecimalFormatSymbols: | |
14 | kGroupingSeparatorSymbol: int | |
15 | kDecimalSeparatorSymbol: int | |
16 | def __init__(self, locale: str) -> None: ... | |
17 | def getSymbol(self, symbol: int) -> str: ... | |
18 | ||
19 | class Collator: | |
20 | @classmethod | |
21 | def createInstance(cls, locale: str) -> Collator: ... | |
22 | def getSortKey(self, source: str) -> bytes: ... | |
23 | def setAttribute(self, attr: int, value: int) -> None: ... |
22 | 22 | from natsort.ns_enum import NSType, ns |
23 | 23 | from natsort.utils import KeyType, NatsortInType, NatsortOutType, chain_functions |
24 | 24 | |
25 | __version__ = "8.0.2" | |
25 | __version__ = "8.2.0" | |
26 | 26 | |
27 | 27 | __all__ = [ |
28 | 28 | "natsort_key", |
37 | 37 | |
38 | 38 | # If using icu, get the locale from the current global locale, |
39 | 39 | def get_icu_locale() -> str: |
40 | try: | |
41 | return cast(str, icu.Locale(".".join(getlocale()))) | |
42 | except TypeError: # pragma: no cover | |
43 | return cast(str, icu.Locale()) | |
40 | language_code, encoding = getlocale() | |
41 | if language_code is None or encoding is None: # pragma: no cover | |
42 | return icu.Locale() | |
43 | return icu.Locale(f"{language_code}.{encoding}") | |
44 | 44 | |
45 | 45 | def get_strxfrm() -> TrxfmFunc: |
46 | return cast(TrxfmFunc, icu.Collator.createInstance(get_icu_locale()).getSortKey) | |
46 | return icu.Collator.createInstance(get_icu_locale()).getSortKey | |
47 | 47 | |
48 | 48 | def get_thousands_sep() -> str: |
49 | 49 | sep = icu.DecimalFormatSymbols.kGroupingSeparatorSymbol |
50 | return cast(str, icu.DecimalFormatSymbols(get_icu_locale()).getSymbol(sep)) | |
50 | return icu.DecimalFormatSymbols(get_icu_locale()).getSymbol(sep) | |
51 | 51 | |
52 | 52 | def get_decimal_point() -> str: |
53 | 53 | sep = icu.DecimalFormatSymbols.kDecimalSeparatorSymbol |
54 | return cast(str, icu.DecimalFormatSymbols(get_icu_locale()).getSymbol(sep)) | |
54 | return icu.DecimalFormatSymbols(get_icu_locale()).getSymbol(sep) | |
55 | 55 | |
56 | 56 | except ImportError: |
57 | 57 | import locale |
74 | 74 | # characters are incorrectly blank. Here is a lookup table of the |
75 | 75 | # corrections I am aware of. |
76 | 76 | if dumb_sort(): |
77 | try: | |
78 | loc = ".".join(locale.getlocale()) | |
79 | except TypeError: # No locale loaded, default to ',' | |
77 | language_code, encoding = locale.getlocale() | |
78 | if language_code is None or encoding is None: | |
79 | # No locale loaded, default to ',' | |
80 | 80 | return "," |
81 | loc = f"{language_code}.{encoding}" | |
81 | 82 | return { |
82 | 83 | "de_DE.ISO8859-15": ".", |
83 | 84 | "es_ES.ISO8859-1": ".", |
17 | 17 | Optional, |
18 | 18 | Sequence, |
19 | 19 | Tuple, |
20 | Union, | |
20 | TypeVar, | |
21 | 21 | cast, |
22 | overload, | |
23 | 22 | ) |
24 | 23 | |
25 | 24 | import natsort.compat.locale |
26 | 25 | from natsort import utils |
27 | 26 | from natsort.ns_enum import NSType, NS_DUMB, ns |
28 | from natsort.utils import ( | |
29 | KeyType, | |
30 | MaybeKeyType, | |
31 | NatsortInType, | |
32 | NatsortOutType, | |
33 | StrBytesNum, | |
34 | StrBytesPathNum, | |
35 | ) | |
27 | from natsort.utils import NatsortInType, NatsortOutType | |
36 | 28 | |
37 | 29 | # Common input and output types |
38 | Iter_ns = Iterable[NatsortInType] | |
39 | Iter_any = Iterable[Any] | |
40 | List_ns = List[NatsortInType] | |
41 | List_any = List[Any] | |
42 | List_int = List[int] | |
30 | T = TypeVar("T") | |
31 | NatsortInTypeT = TypeVar("NatsortInTypeT", bound=NatsortInType) | |
43 | 32 | |
44 | 33 | # The type that natsort_key returns |
45 | 34 | NatsortKeyType = Callable[[NatsortInType], NatsortOutType] |
46 | 35 | |
47 | 36 | # Types for os_sorted |
48 | OSSortInType = Iterable[Optional[StrBytesPathNum]] | |
49 | OSSortOutType = Tuple[Union[StrBytesNum, Tuple[StrBytesNum, ...]], ...] | |
50 | OSSortKeyType = Callable[[Optional[StrBytesPathNum]], OSSortOutType] | |
51 | Iter_path = Iterable[Optional[StrBytesPathNum]] | |
52 | List_path = List[StrBytesPathNum] | |
53 | ||
54 | ||
55 | def decoder(encoding: str) -> Callable[[NatsortInType], NatsortInType]: | |
37 | OSSortKeyType = Callable[[NatsortInType], NatsortOutType] | |
38 | ||
39 | ||
40 | def decoder(encoding: str) -> Callable[[Any], Any]: | |
56 | 41 | """ |
57 | 42 | Return a function that can be used to decode bytes to unicode. |
58 | 43 | |
93 | 78 | return partial(utils.do_decoding, encoding=encoding) |
94 | 79 | |
95 | 80 | |
96 | def as_ascii(s: NatsortInType) -> NatsortInType: | |
81 | def as_ascii(s: Any) -> Any: | |
97 | 82 | """ |
98 | 83 | Function to decode an input with the ASCII codec, or return as-is. |
99 | 84 | |
116 | 101 | return utils.do_decoding(s, "ascii") |
117 | 102 | |
118 | 103 | |
119 | def as_utf8(s: NatsortInType) -> NatsortInType: | |
104 | def as_utf8(s: Any) -> Any: | |
120 | 105 | """ |
121 | 106 | Function to decode an input with the UTF-8 codec, or return as-is. |
122 | 107 | |
140 | 125 | |
141 | 126 | |
142 | 127 | def natsort_keygen( |
143 | key: MaybeKeyType = None, alg: NSType = ns.DEFAULT | |
144 | ) -> NatsortKeyType: | |
128 | key: Optional[Callable[[Any], NatsortInType]] = None, alg: NSType = ns.DEFAULT | |
129 | ) -> Callable[[Any], NatsortOutType]: | |
145 | 130 | """ |
146 | 131 | Generate a key to sort strings and numbers naturally. |
147 | 132 | |
251 | 236 | """ |
252 | 237 | |
253 | 238 | |
254 | @overload | |
255 | 239 | def natsorted( |
256 | seq: Iter_ns, key: None = None, reverse: bool = False, alg: NSType = ns.DEFAULT | |
257 | ) -> List_ns: | |
258 | ... | |
259 | ||
260 | ||
261 | @overload | |
262 | def natsorted( | |
263 | seq: Iter_any, key: KeyType, reverse: bool = False, alg: NSType = ns.DEFAULT | |
264 | ) -> List_any: | |
265 | ... | |
266 | ||
267 | ||
268 | def natsorted( | |
269 | seq: Iter_any, | |
270 | key: MaybeKeyType = None, | |
240 | seq: Iterable[T], | |
241 | key: Optional[Callable[[T], NatsortInType]] = None, | |
271 | 242 | reverse: bool = False, |
272 | 243 | alg: NSType = ns.DEFAULT, |
273 | ) -> List_any: | |
244 | ) -> List[T]: | |
274 | 245 | """ |
275 | 246 | Sorts an iterable naturally. |
276 | 247 | |
318 | 289 | return sorted(seq, reverse=reverse, key=natsort_keygen(key, alg)) |
319 | 290 | |
320 | 291 | |
321 | @overload | |
322 | 292 | def humansorted( |
323 | seq: Iter_ns, key: None = None, reverse: bool = False, alg: NSType = ns.DEFAULT | |
324 | ) -> List_ns: | |
325 | ... | |
326 | ||
327 | ||
328 | @overload | |
329 | def humansorted( | |
330 | seq: Iter_any, key: KeyType, reverse: bool = False, alg: NSType = ns.DEFAULT | |
331 | ) -> List_any: | |
332 | ... | |
333 | ||
334 | ||
335 | def humansorted( | |
336 | seq: Iter_any, | |
337 | key: MaybeKeyType = None, | |
293 | seq: Iterable[T], | |
294 | key: Optional[Callable[[T], NatsortInType]] = None, | |
338 | 295 | reverse: bool = False, |
339 | 296 | alg: NSType = ns.DEFAULT, |
340 | ) -> List_any: | |
297 | ) -> List[T]: | |
341 | 298 | """ |
342 | 299 | Convenience function to properly sort non-numeric characters. |
343 | 300 | |
389 | 346 | return natsorted(seq, key, reverse, alg | ns.LOCALE) |
390 | 347 | |
391 | 348 | |
392 | @overload | |
393 | 349 | def realsorted( |
394 | seq: Iter_ns, key: None = None, reverse: bool = False, alg: NSType = ns.DEFAULT | |
395 | ) -> List_ns: | |
396 | ... | |
397 | ||
398 | ||
399 | @overload | |
400 | def realsorted( | |
401 | seq: Iter_any, key: KeyType, reverse: bool = False, alg: NSType = ns.DEFAULT | |
402 | ) -> List_any: | |
403 | ... | |
404 | ||
405 | ||
406 | def realsorted( | |
407 | seq: Iter_any, | |
408 | key: MaybeKeyType = None, | |
350 | seq: Iterable[T], | |
351 | key: Optional[Callable[[T], NatsortInType]] = None, | |
409 | 352 | reverse: bool = False, |
410 | 353 | alg: NSType = ns.DEFAULT, |
411 | ) -> List_any: | |
354 | ) -> List[T]: | |
412 | 355 | """ |
413 | 356 | Convenience function to properly sort signed floats. |
414 | 357 | |
461 | 404 | return natsorted(seq, key, reverse, alg | ns.REAL) |
462 | 405 | |
463 | 406 | |
464 | @overload | |
465 | 407 | def index_natsorted( |
466 | seq: Iter_ns, key: None = None, reverse: bool = False, alg: NSType = ns.DEFAULT | |
467 | ) -> List_int: | |
468 | ... | |
469 | ||
470 | ||
471 | @overload | |
472 | def index_natsorted( | |
473 | seq: Iter_any, key: KeyType, reverse: bool = False, alg: NSType = ns.DEFAULT | |
474 | ) -> List_int: | |
475 | ... | |
476 | ||
477 | ||
478 | def index_natsorted( | |
479 | seq: Iter_any, | |
480 | key: MaybeKeyType = None, | |
408 | seq: Iterable[T], | |
409 | key: Optional[Callable[[T], NatsortInType]] = None, | |
481 | 410 | reverse: bool = False, |
482 | 411 | alg: NSType = ns.DEFAULT, |
483 | ) -> List_int: | |
412 | ) -> List[int]: | |
484 | 413 | """ |
485 | 414 | Determine the list of the indexes used to sort the input sequence. |
486 | 415 | |
536 | 465 | ['baz', 'foo', 'bar'] |
537 | 466 | |
538 | 467 | """ |
539 | newkey: KeyType | |
468 | newkey: Callable[[Tuple[int, T]], NatsortInType] | |
540 | 469 | if key is None: |
541 | 470 | newkey = itemgetter(1) |
542 | 471 | else: |
543 | 472 | |
544 | def newkey(x: Any) -> NatsortInType: | |
545 | return cast(KeyType, key)(itemgetter(1)(x)) | |
473 | def newkey(x: Tuple[int, T]) -> NatsortInType: | |
474 | return cast(Callable[[T], NatsortInType], key)(itemgetter(1)(x)) | |
546 | 475 | |
547 | 476 | # Pair the index and sequence together, then sort by element |
548 | 477 | index_seq_pair = [(x, y) for x, y in enumerate(seq)] |
550 | 479 | return [x for x, _ in index_seq_pair] |
551 | 480 | |
552 | 481 | |
553 | @overload | |
554 | 482 | def index_humansorted( |
555 | seq: Iter_ns, key: None = None, reverse: bool = False, alg: NSType = ns.DEFAULT | |
556 | ) -> List_int: | |
557 | ... | |
558 | ||
559 | ||
560 | @overload | |
561 | def index_humansorted( | |
562 | seq: Iter_any, key: KeyType, reverse: bool = False, alg: NSType = ns.DEFAULT | |
563 | ) -> List_int: | |
564 | ... | |
565 | ||
566 | ||
567 | def index_humansorted( | |
568 | seq: Iter_any, | |
569 | key: MaybeKeyType = None, | |
483 | seq: Iterable[T], | |
484 | key: Optional[Callable[[T], NatsortInType]] = None, | |
570 | 485 | reverse: bool = False, |
571 | 486 | alg: NSType = ns.DEFAULT, |
572 | ) -> List_int: | |
487 | ) -> List[int]: | |
573 | 488 | """ |
574 | 489 | This is a wrapper around ``index_natsorted(seq, alg=ns.LOCALE)``. |
575 | 490 | |
618 | 533 | return index_natsorted(seq, key, reverse, alg | ns.LOCALE) |
619 | 534 | |
620 | 535 | |
621 | @overload | |
622 | 536 | def index_realsorted( |
623 | seq: Iter_ns, key: None = None, reverse: bool = False, alg: NSType = ns.DEFAULT | |
624 | ) -> List_int: | |
625 | ... | |
626 | ||
627 | ||
628 | @overload | |
629 | def index_realsorted( | |
630 | seq: Iter_any, key: KeyType, reverse: bool = False, alg: NSType = ns.DEFAULT | |
631 | ) -> List_int: | |
632 | ... | |
633 | ||
634 | ||
635 | def index_realsorted( | |
636 | seq: Iter_any, | |
637 | key: MaybeKeyType = None, | |
537 | seq: Iterable[T], | |
538 | key: Optional[Callable[[T], NatsortInType]] = None, | |
638 | 539 | reverse: bool = False, |
639 | 540 | alg: NSType = ns.DEFAULT, |
640 | ) -> List_int: | |
541 | ) -> List[int]: | |
641 | 542 | """ |
642 | 543 | This is a wrapper around ``index_natsorted(seq, alg=ns.REAL)``. |
643 | 544 | |
682 | 583 | return index_natsorted(seq, key, reverse, alg | ns.REAL) |
683 | 584 | |
684 | 585 | |
685 | # noinspection PyShadowingBuiltins,PyUnresolvedReferences | |
686 | 586 | def order_by_index( |
687 | 587 | seq: Sequence[Any], index: Iterable[int], iter: bool = False |
688 | ) -> Iter_any: | |
588 | ) -> Iterable[Any]: | |
689 | 589 | """ |
690 | 590 | Order a given sequence by an index sequence. |
691 | 591 | |
763 | 663 | return utils.regex_chooser(alg).pattern[1:-1] |
764 | 664 | |
765 | 665 | |
766 | def _split_apply(v: Any, key: MaybeKeyType = None) -> Iterator[str]: | |
666 | def _split_apply( | |
667 | v: Any, key: Optional[Callable[[T], NatsortInType]] = None | |
668 | ) -> Iterator[str]: | |
767 | 669 | if key is not None: |
768 | 670 | v = key(v) |
769 | 671 | return utils.path_splitter(str(v)) |
780 | 682 | _windows_sort_cmp.restype = wintypes.INT |
781 | 683 | _winsort_key = cmp_to_key(_windows_sort_cmp) |
782 | 684 | |
783 | def os_sort_keygen(key: MaybeKeyType = None) -> OSSortKeyType: | |
685 | def os_sort_keygen( | |
686 | key: Optional[Callable[[Any], NatsortInType]] = None | |
687 | ) -> Callable[[Any], NatsortOutType]: | |
784 | 688 | return cast( |
785 | OSSortKeyType, lambda x: tuple(map(_winsort_key, _split_apply(x, key))) | |
689 | Callable[[Any], NatsortOutType], | |
690 | lambda x: tuple(map(_winsort_key, _split_apply(x, key))), | |
786 | 691 | ) |
787 | 692 | |
788 | 693 | else: |
801 | 706 | |
802 | 707 | except ImportError: |
803 | 708 | # No ICU installed |
804 | def os_sort_keygen(key: MaybeKeyType = None) -> OSSortKeyType: | |
805 | return cast( | |
806 | OSSortKeyType, | |
807 | natsort_keygen(key=key, alg=ns.LOCALE | ns.PATH | ns.IGNORECASE), | |
808 | ) | |
709 | def os_sort_keygen( | |
710 | key: Optional[Callable[[Any], NatsortInType]] = None | |
711 | ) -> Callable[[Any], NatsortOutType]: | |
712 | return natsort_keygen(key=key, alg=ns.LOCALE | ns.PATH | ns.IGNORECASE) | |
809 | 713 | |
810 | 714 | else: |
811 | 715 | # ICU installed |
812 | def os_sort_keygen(key: MaybeKeyType = None) -> OSSortKeyType: | |
716 | def os_sort_keygen( | |
717 | key: Optional[Callable[[Any], NatsortInType]] = None | |
718 | ) -> Callable[[Any], NatsortOutType]: | |
813 | 719 | loc = natsort.compat.locale.get_icu_locale() |
814 | 720 | collator = icu.Collator.createInstance(loc) |
815 | 721 | collator.setAttribute( |
856 | 762 | """ |
857 | 763 | |
858 | 764 | |
859 | @overload | |
860 | def os_sorted(seq: Iter_path, key: None = None, reverse: bool = False) -> List_path: | |
861 | ... | |
862 | ||
863 | ||
864 | @overload | |
865 | def os_sorted(seq: Iter_any, key: KeyType, reverse: bool = False) -> List_any: | |
866 | ... | |
867 | ||
868 | ||
869 | 765 | def os_sorted( |
870 | seq: Iter_any, key: MaybeKeyType = None, reverse: bool = False | |
871 | ) -> List_any: | |
766 | seq: Iterable[T], | |
767 | key: Optional[Callable[[T], NatsortInType]] = None, | |
768 | reverse: bool = False, | |
769 | ) -> List[T]: | |
872 | 770 | """ |
873 | 771 | Sort elements in the same order as your operating system's file browser |
874 | 772 | |
875 | 773 | .. warning:: |
876 | 774 | |
877 | 775 | The resulting function will generate results that will be |
878 | differnt depending on your platform. This is intentional. | |
776 | different depending on your platform. This is intentional. | |
879 | 777 | |
880 | 778 | On Windows, this will sort with the same order as Windows Explorer. |
881 | 779 | |
891 | 789 | special characters this will give correct results, but once |
892 | 790 | special characters are added you should lower your expectations. |
893 | 791 | |
894 | It is *strongly* reccommended to have :mod:`pyicu` installed on | |
792 | It is *strongly* recommended to have :mod:`pyicu` installed on | |
895 | 793 | MacOS/Linux if you want correct sort results. |
896 | 794 | |
897 | 795 | It does *not* take into account if a path is a directory or a file |
23 | 23 | # The digit characters are a subset of the numerals. |
24 | 24 | digit_chars = [a for a in numeric_chars if unicodedata.digit(a, None) is not None] |
25 | 25 | |
26 | # The decimal characters are a subset of the numberals | |
26 | # The decimal characters are a subset of the numerals | |
27 | 27 | # (probably of the digits, but let's be safe). |
28 | 28 | decimal_chars = [a for a in numeric_chars if unicodedata.decimal(a, None) is not None] |
29 | 29 |
52 | 52 | Match, |
53 | 53 | Optional, |
54 | 54 | Pattern, |
55 | TYPE_CHECKING, | |
55 | 56 | Tuple, |
56 | 57 | Union, |
57 | 58 | cast, |
69 | 70 | from natsort.ns_enum import NSType, NS_DUMB, ns |
70 | 71 | from natsort.unicode_numbers import digits_no_decimals, numeric_no_decimals |
71 | 72 | |
73 | if TYPE_CHECKING: | |
74 | from typing_extensions import Protocol | |
75 | else: | |
76 | Protocol = object | |
77 | ||
72 | 78 | # |
73 | 79 | # Pre-define a slew of aggregate types which makes the type hinting below easier |
74 | 80 | # |
81 | ||
82 | ||
83 | class SupportsDunderLT(Protocol): | |
84 | def __lt__(self, __other: Any) -> bool: | |
85 | ... | |
86 | ||
87 | ||
88 | class SupportsDunderGT(Protocol): | |
89 | def __gt__(self, __other: Any) -> bool: | |
90 | ... | |
91 | ||
92 | ||
93 | Sortable = Union[SupportsDunderLT, SupportsDunderGT] | |
94 | ||
75 | 95 | StrToStr = Callable[[str], str] |
76 | 96 | AnyCall = Callable[[Any], Any] |
77 | 97 | |
82 | 102 | BytesTransformer = Callable[[bytes], BytesTransform] |
83 | 103 | |
84 | 104 | # For the number transform factory |
85 | NumType = Union[float, int] | |
86 | MaybeNumType = Optional[NumType] | |
87 | NumTuple = Tuple[StrOrBytes, NumType] | |
88 | NestedNumTuple = Tuple[NumTuple] | |
89 | StrNumTuple = Tuple[Tuple[str], NumTuple] | |
90 | NestedStrNumTuple = Tuple[StrNumTuple] | |
91 | MaybeNumTransform = Union[NumTuple, NestedNumTuple, StrNumTuple, NestedStrNumTuple] | |
92 | MaybeNumTransformer = Callable[[MaybeNumType], MaybeNumTransform] | |
105 | BasicTuple = Tuple[Any, ...] | |
106 | NestedAnyTuple = Tuple[BasicTuple, ...] | |
107 | AnyTuple = Union[BasicTuple, NestedAnyTuple] | |
108 | NumTransform = AnyTuple | |
109 | NumTransformer = Callable[[Any], NumTransform] | |
93 | 110 | |
94 | 111 | # For the string component transform factory |
95 | 112 | StrBytesNum = Union[str, bytes, float, int] |
96 | 113 | StrTransformer = Callable[[str], StrBytesNum] |
97 | 114 | |
98 | 115 | # For the final data transform factory |
99 | TwoBlankTuple = Tuple[Tuple[()], Tuple[()]] | |
100 | TupleOfAny = Tuple[Any, ...] | |
101 | TupleOfStrAnyPair = Tuple[Tuple[str], TupleOfAny] | |
102 | FinalTransform = Union[TwoBlankTuple, TupleOfAny, TupleOfStrAnyPair] | |
116 | FinalTransform = AnyTuple | |
103 | 117 | FinalTransformer = Callable[[Iterable[Any], str], FinalTransform] |
118 | ||
119 | PathArg = Union[str, PurePath] | |
120 | MatchFn = Callable[[str], Optional[Match]] | |
104 | 121 | |
105 | 122 | # For the string parsing factory |
106 | 123 | StrSplitter = Callable[[str], Iterable[str]] |
107 | StrParser = Callable[[str], FinalTransform] | |
108 | ||
109 | # For the path splitter | |
110 | PathArg = Union[str, PurePath] | |
111 | MatchFn = Callable[[str], Optional[Match]] | |
124 | StrParser = Callable[[PathArg], FinalTransform] | |
112 | 125 | |
113 | 126 | # For the path parsing factory |
114 | 127 | PathSplitter = Callable[[PathArg], Tuple[FinalTransform, ...]] |
115 | 128 | |
116 | 129 | # For the natsort key |
117 | StrBytesPathNum = Union[str, bytes, float, int, PurePath] | |
118 | NatsortInType = Union[ | |
119 | Optional[StrBytesPathNum], Iterable[Union[Optional[StrBytesPathNum], Iterable[Any]]] | |
120 | ] | |
121 | NatsortOutType = Tuple[ | |
122 | Union[StrBytesNum, Tuple[Union[StrBytesNum, Tuple[Any, ...]], ...]], ... | |
123 | ] | |
130 | NatsortInType = Optional[Sortable] | |
131 | NatsortOutType = Tuple[Sortable, ...] | |
124 | 132 | KeyType = Callable[[Any], NatsortInType] |
125 | 133 | MaybeKeyType = Optional[KeyType] |
126 | 134 | |
259 | 267 | key: None, |
260 | 268 | string_func: Union[StrParser, PathSplitter], |
261 | 269 | bytes_func: BytesTransformer, |
262 | num_func: MaybeNumTransformer, | |
270 | num_func: NumTransformer, | |
263 | 271 | ) -> NatsortOutType: |
264 | 272 | ... |
265 | 273 | |
270 | 278 | key: KeyType, |
271 | 279 | string_func: Union[StrParser, PathSplitter], |
272 | 280 | bytes_func: BytesTransformer, |
273 | num_func: MaybeNumTransformer, | |
281 | num_func: NumTransformer, | |
274 | 282 | ) -> NatsortOutType: |
275 | 283 | ... |
276 | 284 | |
280 | 288 | key: MaybeKeyType, |
281 | 289 | string_func: Union[StrParser, PathSplitter], |
282 | 290 | bytes_func: BytesTransformer, |
283 | num_func: MaybeNumTransformer, | |
291 | num_func: NumTransformer, | |
284 | 292 | ) -> NatsortOutType: |
285 | 293 | """ |
286 | 294 | Key to sort strings and numbers naturally. |
347 | 355 | |
348 | 356 | # If that failed, it must be a number. |
349 | 357 | except TypeError: |
350 | return num_func(cast(NumType, val)) | |
358 | return num_func(val) | |
351 | 359 | |
352 | 360 | |
353 | 361 | def parse_bytes_factory(alg: NSType) -> BytesTransformer: |
385 | 393 | |
386 | 394 | def parse_number_or_none_factory( |
387 | 395 | alg: NSType, sep: StrOrBytes, pre_sep: str |
388 | ) -> MaybeNumTransformer: | |
396 | ) -> NumTransformer: | |
389 | 397 | """ |
390 | 398 | Create a function that will format a number (or None) into a tuple. |
391 | 399 | |
417 | 425 | nan_replace = float("+inf") if alg & ns.NANLAST else float("-inf") |
418 | 426 | |
419 | 427 | def func( |
420 | val: MaybeNumType, _nan_replace: float = nan_replace, _sep: StrOrBytes = sep | |
421 | ) -> NumTuple: | |
428 | val: Any, _nan_replace: float = nan_replace, _sep: StrOrBytes = sep | |
429 | ) -> BasicTuple: | |
422 | 430 | """Given a number, place it in a tuple with a leading null string.""" |
423 | 431 | return _sep, (_nan_replace if val != val or val is None else val) |
424 | 432 | |
492 | 500 | normalize_input = _normalize_input_factory(alg) |
493 | 501 | compose_input = _compose_input_factory(alg) if alg & ns.LOCALEALPHA else _no_op |
494 | 502 | |
495 | def func(x: str) -> FinalTransform: | |
503 | def func(x: PathArg) -> FinalTransform: | |
504 | if isinstance(x, PurePath): | |
505 | # While paths are technically not strings, it is natural for them | |
506 | # to be treated the same. | |
507 | x = str(x) | |
496 | 508 | # Apply string input transformation function and return to x. |
497 | 509 | # Original function is usually a no-op, but some algorithms require it |
498 | 510 | # to also be the transformation function. |
724 | 736 | """ |
725 | 737 | if alg & ns.UNGROUPLETTERS and alg & ns.LOCALEALPHA: |
726 | 738 | swap = alg & NS_DUMB and alg & ns.LOWERCASEFIRST |
727 | transform = cast(StrToStr, methodcaller("swapcase")) if swap else _no_op | |
739 | transform = cast(StrToStr, methodcaller("swapcase") if swap else _no_op) | |
728 | 740 | |
729 | 741 | def func( |
730 | 742 | split_val: Iterable[NatsortInType], |
830 | 842 | |
831 | 843 | |
832 | 844 | @overload |
833 | def do_decoding(s: NatsortInType, encoding: str) -> NatsortInType: | |
845 | def do_decoding(s: Any, encoding: str) -> Any: | |
834 | 846 | ... |
835 | 847 | |
836 | 848 | |
837 | def do_decoding(s: NatsortInType, encoding: str) -> NatsortInType: | |
849 | def do_decoding(s: Any, encoding: str) -> Any: | |
838 | 850 | """ |
839 | 851 | Helper to decode a *bytes* object, or return the object as-is. |
840 | 852 | |
892 | 904 | path_parts = [] |
893 | 905 | base = str(s) |
894 | 906 | |
895 | # Now, split off the file extensions until we reach a decimal number at | |
896 | # the beginning of the suffix or there are no more extensions. | |
897 | suffixes = PurePath(base).suffixes | |
898 | try: | |
899 | digit_index = next(i for i, x in enumerate(reversed(suffixes)) if _d_match(x)) | |
900 | except StopIteration: | |
901 | pass | |
902 | else: | |
903 | digit_index = len(suffixes) - digit_index | |
904 | suffixes = suffixes[digit_index:] | |
905 | ||
907 | # Now, split off the file extensions until | |
908 | # - we reach a decimal number at the beginning of the suffix | |
909 | # - more than two suffixes have been seen | |
910 | # - a suffix is more than five characters (including leading ".") | |
911 | # - there are no more extensions | |
912 | suffixes = [] | |
913 | for i, suffix in enumerate(reversed(PurePath(base).suffixes)): | |
914 | if _d_match(suffix) or i > 1 or len(suffix) > 5: | |
915 | break | |
916 | suffixes.append(suffix) | |
917 | suffixes.reverse() | |
918 | ||
919 | # Remove the suffixes from the base component | |
906 | 920 | base = base.replace("".join(suffixes), "") |
907 | return filter(None, ichain(path_parts, [base], suffixes)) | |
921 | base_component = [base] if base else [] | |
922 | ||
923 | # Join all path comonents in an iterator | |
924 | return filter(None, ichain(path_parts, base_component, suffixes)) |
0 | Metadata-Version: 2.1 | |
1 | Name: natsort | |
2 | Version: 8.2.0 | |
3 | Summary: Simple yet flexible natural sorting in Python. | |
4 | Home-page: https://github.com/SethMMorton/natsort | |
5 | Author: Seth M. Morton | |
6 | Author-email: drtuba78@gmail.com | |
7 | License: MIT | |
8 | Classifier: Development Status :: 5 - Production/Stable | |
9 | Classifier: Intended Audience :: Developers | |
10 | Classifier: Intended Audience :: Science/Research | |
11 | Classifier: Intended Audience :: System Administrators | |
12 | Classifier: Intended Audience :: Information Technology | |
13 | Classifier: Intended Audience :: Financial and Insurance Industry | |
14 | Classifier: Operating System :: OS Independent | |
15 | Classifier: License :: OSI Approved :: MIT License | |
16 | Classifier: Natural Language :: English | |
17 | Classifier: Programming Language :: Python | |
18 | Classifier: Programming Language :: Python :: 3 | |
19 | Classifier: Programming Language :: Python :: 3.7 | |
20 | Classifier: Programming Language :: Python :: 3.8 | |
21 | Classifier: Programming Language :: Python :: 3.9 | |
22 | Classifier: Programming Language :: Python :: 3.10 | |
23 | Classifier: Topic :: Scientific/Engineering :: Information Analysis | |
24 | Classifier: Topic :: Utilities | |
25 | Classifier: Topic :: Text Processing | |
26 | Requires-Python: >=3.7 | |
27 | Description-Content-Type: text/x-rst | |
28 | Provides-Extra: fast | |
29 | Provides-Extra: icu | |
30 | License-File: LICENSE | |
31 | ||
32 | natsort | |
33 | ======= | |
34 | ||
35 | .. image:: https://img.shields.io/pypi/v/natsort.svg | |
36 | :target: https://pypi.org/project/natsort/ | |
37 | ||
38 | .. image:: https://img.shields.io/pypi/pyversions/natsort.svg | |
39 | :target: https://pypi.org/project/natsort/ | |
40 | ||
41 | .. image:: https://img.shields.io/pypi/l/natsort.svg | |
42 | :target: https://github.com/SethMMorton/natsort/blob/master/LICENSE | |
43 | ||
44 | .. image:: https://github.com/SethMMorton/natsort/workflows/Tests/badge.svg | |
45 | :target: https://github.com/SethMMorton/natsort/actions | |
46 | ||
47 | .. image:: https://codecov.io/gh/SethMMorton/natsort/branch/master/graph/badge.svg | |
48 | :target: https://codecov.io/gh/SethMMorton/natsort | |
49 | ||
50 | Simple yet flexible natural sorting in Python. | |
51 | ||
52 | - Source Code: https://github.com/SethMMorton/natsort | |
53 | - Downloads: https://pypi.org/project/natsort/ | |
54 | - Documentation: https://natsort.readthedocs.io/ | |
55 | ||
56 | - `Examples and Recipes <https://natsort.readthedocs.io/en/master/examples.html>`_ | |
57 | - `How Does Natsort Work? <https://natsort.readthedocs.io/en/master/howitworks.html>`_ | |
58 | - `API <https://natsort.readthedocs.io/en/master/api.html>`_ | |
59 | ||
60 | - `Quick Description`_ | |
61 | - `Quick Examples`_ | |
62 | - `FAQ`_ | |
63 | - `Requirements`_ | |
64 | - `Optional Dependencies`_ | |
65 | - `Installation`_ | |
66 | - `How to Run Tests`_ | |
67 | - `How to Build Documentation`_ | |
68 | - `Dropped Deprecated APIs`_ | |
69 | - `History`_ | |
70 | ||
71 | **NOTE**: Please see the `Dropped Deprecated APIs`_ section for changes. | |
72 | ||
73 | Quick Description | |
74 | ----------------- | |
75 | ||
76 | When you try to sort a list of strings that contain numbers, the normal python | |
77 | sort algorithm sorts lexicographically, so you might not get the results that | |
78 | you expect: | |
79 | ||
80 | .. code-block:: pycon | |
81 | ||
82 | >>> a = ['2 ft 7 in', '1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '7 ft 6 in'] | |
83 | >>> sorted(a) | |
84 | ['1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '2 ft 7 in', '7 ft 6 in'] | |
85 | ||
86 | Notice that it has the order ('1', '10', '2') - this is because the list is | |
87 | being sorted in lexicographical order, which sorts numbers like you would | |
88 | letters (i.e. 'b', 'ba', 'c'). | |
89 | ||
90 | ``natsort`` provides a function ``natsorted`` that helps sort lists | |
91 | "naturally" ("naturally" is rather ill-defined, but in general it means | |
92 | sorting based on meaning and not computer code point). | |
93 | Using ``natsorted`` is simple: | |
94 | ||
95 | .. code-block:: pycon | |
96 | ||
97 | >>> from natsort import natsorted | |
98 | >>> a = ['2 ft 7 in', '1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '7 ft 6 in'] | |
99 | >>> natsorted(a) | |
100 | ['1 ft 5 in', '2 ft 7 in', '2 ft 11 in', '7 ft 6 in', '10 ft 2 in'] | |
101 | ||
102 | ``natsorted`` identifies numbers anywhere in a string and sorts them | |
103 | naturally. Below are some other things you can do with ``natsort`` | |
104 | (also see the `examples <https://natsort.readthedocs.io/en/master/examples.html>`_ | |
105 | for a quick start guide, or the | |
106 | `api <https://natsort.readthedocs.io/en/master/api.html>`_ for complete details). | |
107 | ||
108 | **Note**: ``natsorted`` is designed to be a drop-in replacement for the | |
109 | built-in ``sorted`` function. Like ``sorted``, ``natsorted`` | |
110 | `does not sort in-place`. To sort a list and assign the output to the same | |
111 | variable, you must explicitly assign the output to a variable: | |
112 | ||
113 | .. code-block:: pycon | |
114 | ||
115 | >>> a = ['2 ft 7 in', '1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '7 ft 6 in'] | |
116 | >>> natsorted(a) | |
117 | ['1 ft 5 in', '2 ft 7 in', '2 ft 11 in', '7 ft 6 in', '10 ft 2 in'] | |
118 | >>> print(a) # 'a' was not sorted; "natsorted" simply returned a sorted list | |
119 | ['2 ft 7 in', '1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '7 ft 6 in'] | |
120 | >>> a = natsorted(a) # Now 'a' will be sorted because the sorted list was assigned to 'a' | |
121 | >>> print(a) | |
122 | ['1 ft 5 in', '2 ft 7 in', '2 ft 11 in', '7 ft 6 in', '10 ft 2 in'] | |
123 | ||
124 | Please see `Generating a Reusable Sorting Key and Sorting In-Place`_ for | |
125 | an alternate way to sort in-place naturally. | |
126 | ||
127 | Quick Examples | |
128 | -------------- | |
129 | ||
130 | - `Sorting Versions`_ | |
131 | - `Sort Paths Like My File Browser (e.g. Windows Explorer on Windows)`_ | |
132 | - `Sorting by Real Numbers (i.e. Signed Floats)`_ | |
133 | - `Locale-Aware Sorting (or "Human Sorting")`_ | |
134 | - `Further Customizing Natsort`_ | |
135 | - `Sorting Mixed Types`_ | |
136 | - `Handling Bytes`_ | |
137 | - `Generating a Reusable Sorting Key and Sorting In-Place`_ | |
138 | - `Other Useful Things`_ | |
139 | ||
140 | Sorting Versions | |
141 | ++++++++++++++++ | |
142 | ||
143 | ``natsort`` does not actually *comprehend* version numbers. | |
144 | It just so happens that the most common versioning schemes are designed to | |
145 | work with standard natural sorting techniques; these schemes include | |
146 | ``MAJOR.MINOR``, ``MAJOR.MINOR.PATCH``, ``YEAR.MONTH.DAY``. If your data | |
147 | conforms to a scheme like this, then it will work out-of-the-box with | |
148 | ``natsorted`` (as of ``natsort`` version >= 4.0.0): | |
149 | ||
150 | .. code-block:: pycon | |
151 | ||
152 | >>> a = ['version-1.9', 'version-2.0', 'version-1.11', 'version-1.10'] | |
153 | >>> natsorted(a) | |
154 | ['version-1.9', 'version-1.10', 'version-1.11', 'version-2.0'] | |
155 | ||
156 | If you need to versions that use a more complicated scheme, please see | |
157 | `these examples <https://natsort.readthedocs.io/en/master/examples.html#rc-sorting>`_. | |
158 | ||
159 | Sort Paths Like My File Browser (e.g. Windows Explorer on Windows) | |
160 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
161 | ||
162 | Prior to ``natsort`` version 7.1.0, it was a common request to be able to | |
163 | sort paths like Windows Explorer. As of ``natsort`` 7.1.0, the function | |
164 | ``os_sorted`` has been added to provide users the ability to sort | |
165 | in the order that their file browser might sort (e.g Windows Explorer on | |
166 | Windows, Finder on MacOS, Dolphin/Nautilus/Thunar/etc. on Linux). | |
167 | ||
168 | .. code-block:: python | |
169 | ||
170 | import os | |
171 | from natsort import os_sorted | |
172 | print(os_sorted(os.listdir())) | |
173 | # The directory sorted like your file browser might show | |
174 | ||
175 | Output will be different depending on the operating system you are on. | |
176 | ||
177 | For users **not** on Windows (e.g. MacOS/Linux) it is **strongly** recommended | |
178 | to also install `PyICU <https://pypi.org/project/PyICU>`_, which will help | |
179 | ``natsort`` give results that match most file browsers. If this is not installed, | |
180 | it will fall back on Python's built-in ``locale`` module and will give good | |
181 | results for most input, but will give poor results for special characters. | |
182 | ||
183 | Sorting by Real Numbers (i.e. Signed Floats) | |
184 | ++++++++++++++++++++++++++++++++++++++++++++ | |
185 | ||
186 | This is useful in scientific data analysis (and was | |
187 | the default behavior of ``natsorted`` for ``natsort`` | |
188 | version < 4.0.0). Use the ``realsorted`` function: | |
189 | ||
190 | .. code-block:: pycon | |
191 | ||
192 | >>> from natsort import realsorted, ns | |
193 | >>> # Note that when interpreting as signed floats, the below numbers are | |
194 | >>> # +5.10, -3.00, +5.30, +2.00 | |
195 | >>> a = ['position5.10.data', 'position-3.data', 'position5.3.data', 'position2.data'] | |
196 | >>> natsorted(a) | |
197 | ['position2.data', 'position5.3.data', 'position5.10.data', 'position-3.data'] | |
198 | >>> natsorted(a, alg=ns.REAL) | |
199 | ['position-3.data', 'position2.data', 'position5.10.data', 'position5.3.data'] | |
200 | >>> realsorted(a) # shortcut for natsorted with alg=ns.REAL | |
201 | ['position-3.data', 'position2.data', 'position5.10.data', 'position5.3.data'] | |
202 | ||
203 | Locale-Aware Sorting (or "Human Sorting") | |
204 | +++++++++++++++++++++++++++++++++++++++++ | |
205 | ||
206 | This is where the non-numeric characters are also ordered based on their | |
207 | meaning, not on their ordinal value, and a locale-dependent thousands | |
208 | separator and decimal separator is accounted for in the number. | |
209 | This can be achieved with the ``humansorted`` function: | |
210 | ||
211 | .. code-block:: pycon | |
212 | ||
213 | >>> a = ['Apple', 'apple15', 'Banana', 'apple14,689', 'banana'] | |
214 | >>> natsorted(a) | |
215 | ['Apple', 'Banana', 'apple14,689', 'apple15', 'banana'] | |
216 | >>> import locale | |
217 | >>> locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') | |
218 | 'en_US.UTF-8' | |
219 | >>> natsorted(a, alg=ns.LOCALE) | |
220 | ['apple15', 'apple14,689', 'Apple', 'banana', 'Banana'] | |
221 | >>> from natsort import humansorted | |
222 | >>> humansorted(a) # shortcut for natsorted with alg=ns.LOCALE | |
223 | ['apple15', 'apple14,689', 'Apple', 'banana', 'Banana'] | |
224 | ||
225 | You may find you need to explicitly set the locale to get this to work | |
226 | (as shown in the example). | |
227 | Please see `locale issues <https://natsort.readthedocs.io/en/master/locale_issues.html>`_ and the | |
228 | `Optional Dependencies`_ section below before using the ``humansorted`` function. | |
229 | ||
230 | Further Customizing Natsort | |
231 | +++++++++++++++++++++++++++ | |
232 | ||
233 | If you need to combine multiple algorithm modifiers (such as ``ns.REAL``, | |
234 | ``ns.LOCALE``, and ``ns.IGNORECASE``), you can combine the options using the | |
235 | bitwise OR operator (``|``). For example, | |
236 | ||
237 | .. code-block:: pycon | |
238 | ||
239 | >>> a = ['Apple', 'apple15', 'Banana', 'apple14,689', 'banana'] | |
240 | >>> natsorted(a, alg=ns.REAL | ns.LOCALE | ns.IGNORECASE) | |
241 | ['Apple', 'apple15', 'apple14,689', 'Banana', 'banana'] | |
242 | >>> # The ns enum provides long and short forms for each option. | |
243 | >>> ns.LOCALE == ns.L | |
244 | True | |
245 | >>> # You can also customize the convenience functions, too. | |
246 | >>> natsorted(a, alg=ns.REAL | ns.LOCALE | ns.IGNORECASE) == realsorted(a, alg=ns.L | ns.IC) | |
247 | True | |
248 | >>> natsorted(a, alg=ns.REAL | ns.LOCALE | ns.IGNORECASE) == humansorted(a, alg=ns.R | ns.IC) | |
249 | True | |
250 | ||
251 | All of the available customizations can be found in the documentation for | |
252 | `the ns enum <https://natsort.readthedocs.io/en/master/api.html#natsort.ns>`_. | |
253 | ||
254 | You can also add your own custom transformation functions with the ``key`` | |
255 | argument. These can be used with ``alg`` if you wish. | |
256 | ||
257 | .. code-block:: pycon | |
258 | ||
259 | >>> a = ['apple2.50', '2.3apple'] | |
260 | >>> natsorted(a, key=lambda x: x.replace('apple', ''), alg=ns.REAL) | |
261 | ['2.3apple', 'apple2.50'] | |
262 | ||
263 | Sorting Mixed Types | |
264 | +++++++++++++++++++ | |
265 | ||
266 | You can mix and match ``int``, ``float``, and ``str`` (or ``unicode``) types | |
267 | when you sort: | |
268 | ||
269 | .. code-block:: pycon | |
270 | ||
271 | >>> a = ['4.5', 6, 2.0, '5', 'a'] | |
272 | >>> natsorted(a) | |
273 | [2.0, '4.5', '5', 6, 'a'] | |
274 | >>> # sorted(a) would raise an "unorderable types" TypeError | |
275 | ||
276 | Handling Bytes | |
277 | ++++++++++++++ | |
278 | ||
279 | ``natsort`` does not officially support the `bytes` type, but | |
280 | convenience functions are provided that help you decode to `str` first: | |
281 | ||
282 | .. code-block:: pycon | |
283 | ||
284 | >>> from natsort import as_utf8 | |
285 | >>> a = [b'a', 14.0, 'b'] | |
286 | >>> # natsorted(a) would raise a TypeError (bytes() < str()) | |
287 | >>> natsorted(a, key=as_utf8) == [14.0, b'a', 'b'] | |
288 | True | |
289 | >>> a = [b'a56', b'a5', b'a6', b'a40'] | |
290 | >>> # natsorted(a) would return the same results as sorted(a) | |
291 | >>> natsorted(a, key=as_utf8) == [b'a5', b'a6', b'a40', b'a56'] | |
292 | True | |
293 | ||
294 | Generating a Reusable Sorting Key and Sorting In-Place | |
295 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
296 | ||
297 | Under the hood, ``natsorted`` works by generating a custom sorting | |
298 | key using ``natsort_keygen`` and then passes that to the built-in | |
299 | ``sorted``. You can use the ``natsort_keygen`` function yourself to | |
300 | generate a custom sorting key to sort in-place using the ``list.sort`` | |
301 | method. | |
302 | ||
303 | .. code-block:: pycon | |
304 | ||
305 | >>> from natsort import natsort_keygen | |
306 | >>> natsort_key = natsort_keygen() | |
307 | >>> a = ['2 ft 7 in', '1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '7 ft 6 in'] | |
308 | >>> natsorted(a) == sorted(a, key=natsort_key) | |
309 | True | |
310 | >>> a.sort(key=natsort_key) | |
311 | >>> a | |
312 | ['1 ft 5 in', '2 ft 7 in', '2 ft 11 in', '7 ft 6 in', '10 ft 2 in'] | |
313 | ||
314 | All of the algorithm customizations mentioned in the | |
315 | `Further Customizing Natsort`_ section can also be applied to | |
316 | ``natsort_keygen`` through the *alg* keyword option. | |
317 | ||
318 | Other Useful Things | |
319 | +++++++++++++++++++ | |
320 | ||
321 | - recursively descend into lists of lists | |
322 | - automatic unicode normalization of input data | |
323 | - `controlling the case-sensitivity <https://natsort.readthedocs.io/en/master/examples.html#case-sort>`_ | |
324 | - `sorting file paths correctly <https://natsort.readthedocs.io/en/master/examples.html#path-sort>`_ | |
325 | - `allow custom sorting keys <https://natsort.readthedocs.io/en/master/examples.html#custom-sort>`_ | |
326 | - `accounting for units <https://natsort.readthedocs.io/en/master/examples.html#accounting-for-units-when-sorting>`_ | |
327 | ||
328 | FAQ | |
329 | --- | |
330 | ||
331 | How do I debug ``natsort.natsorted()``? | |
332 | The best way to debug ``natsorted()`` is to generate a key using ``natsort_keygen()`` | |
333 | with the same options being passed to ``natsorted``. One can take a look at | |
334 | exactly what is being done with their input using this key - it is highly | |
335 | recommended | |
336 | to `look at this issue describing how to debug <https://github.com/SethMMorton/natsort/issues/13#issuecomment-50422375>`_ | |
337 | for *how* to debug, and also to review the | |
338 | `How Does Natsort Work? <https://natsort.readthedocs.io/en/master/howitworks.html>`_ | |
339 | page for *why* ``natsort`` is doing that to your data. | |
340 | ||
341 | If you are trying to sort custom classes and running into trouble, please | |
342 | take a look at https://github.com/SethMMorton/natsort/issues/60. In short, | |
343 | custom classes are not likely to be sorted correctly if one relies | |
344 | on the behavior of ``__lt__`` and the other rich comparison operators in | |
345 | their custom class - it is better to use a ``key`` function with | |
346 | ``natsort``, or use the ``natsort`` key as part of your rich comparison | |
347 | operator definition. | |
348 | ||
349 | ``natsort`` gave me results I didn't expect, and it's a terrible library! | |
350 | Did you try to debug using the above advice? If so, and you still cannot figure out | |
351 | the error, then please `file an issue <https://github.com/SethMMorton/natsort/issues/new>`_. | |
352 | ||
353 | How *does* ``natsort`` work? | |
354 | If you don't want to read `How Does Natsort Work? <https://natsort.readthedocs.io/en/master/howitworks.html>`_, | |
355 | here is a quick primer. | |
356 | ||
357 | ``natsort`` provides a `key function <https://docs.python.org/3/howto/sorting.html#key-functions>`_ | |
358 | that can be passed to `list.sort() <https://docs.python.org/3/library/stdtypes.html#list.sort>`_ | |
359 | or `sorted() <https://docs.python.org/3/library/functions.html#sorted>`_ in order to | |
360 | modify the default sorting behavior. This key is generated on-demand with | |
361 | the key generator ``natsort.natsort_keygen()``. ``natsort.natsorted()`` | |
362 | is essentially a wrapper for the following code: | |
363 | ||
364 | .. code-block:: pycon | |
365 | ||
366 | >>> from natsort import natsort_keygen | |
367 | >>> natsort_key = natsort_keygen() | |
368 | >>> sorted(['1', '10', '2'], key=natsort_key) | |
369 | ['1', '2', '10'] | |
370 | ||
371 | Users can further customize ``natsort`` sorting behavior with the ``key`` | |
372 | and/or ``alg`` options (see details in the `Further Customizing Natsort`_ | |
373 | section). | |
374 | ||
375 | The key generated by ``natsort_keygen`` *always* returns a ``tuple``. It | |
376 | does so in the following way (*some details omitted for clarity*): | |
377 | ||
378 | 1. Assume the input is a string, and attempt to split it into numbers and | |
379 | non-numbers using regular expressions. Numbers are then converted into | |
380 | either ``int`` or ``float``. | |
381 | 2. If the above fails because the input is not a string, assume the input | |
382 | is some other sequence (e.g. ``list`` or ``tuple``), and recursively | |
383 | apply the key to each element of the sequence. | |
384 | 3. If the above fails because the input is not iterable, assume the input | |
385 | is an ``int`` or ``float``, and just return the input in a ``tuple``. | |
386 | ||
387 | Because a ``tuple`` is always returned, a ``TypeError`` should not be common | |
388 | unless one tries to do something odd like sort an ``int`` against a ``list``. | |
389 | ||
390 | Shell script | |
391 | ------------ | |
392 | ||
393 | ``natsort`` comes with a shell script called ``natsort``, or can also be called | |
394 | from the command line with ``python -m natsort``. | |
395 | ||
396 | Requirements | |
397 | ------------ | |
398 | ||
399 | ``natsort`` requires Python 3.7 or greater. | |
400 | ||
401 | Optional Dependencies | |
402 | --------------------- | |
403 | ||
404 | fastnumbers | |
405 | +++++++++++ | |
406 | ||
407 | The most efficient sorting can occur if you install the | |
408 | `fastnumbers <https://pypi.org/project/fastnumbers>`_ package | |
409 | (version >=2.0.0); it helps with the string to number conversions. | |
410 | ``natsort`` will still run (efficiently) without the package, but if you need | |
411 | to squeeze out that extra juice it is recommended you include this as a | |
412 | dependency. ``natsort`` will not require (or check) that | |
413 | `fastnumbers <https://pypi.org/project/fastnumbers>`_ is installed | |
414 | at installation. | |
415 | ||
416 | PyICU | |
417 | +++++ | |
418 | ||
419 | It is recommended that you install `PyICU <https://pypi.org/project/PyICU>`_ | |
420 | if you wish to sort in a locale-dependent manner, see | |
421 | https://natsort.readthedocs.io/en/master/locale_issues.html for an explanation why. | |
422 | ||
423 | Installation | |
424 | ------------ | |
425 | ||
426 | Use ``pip``! | |
427 | ||
428 | .. code-block:: console | |
429 | ||
430 | $ pip install natsort | |
431 | ||
432 | If you want to install the `Optional Dependencies`_, you can use the | |
433 | `"extras" notation <https://packaging.python.org/tutorials/installing-packages/#installing-setuptools-extras>`_ | |
434 | at installation time to install those dependencies as well - use ``fast`` for | |
435 | `fastnumbers <https://pypi.org/project/fastnumbers>`_ and ``icu`` for | |
436 | `PyICU <https://pypi.org/project/PyICU>`_. | |
437 | ||
438 | .. code-block:: console | |
439 | ||
440 | # Install both optional dependencies. | |
441 | $ pip install natsort[fast,icu] | |
442 | # Install just fastnumbers | |
443 | $ pip install natsort[fast] | |
444 | ||
445 | How to Run Tests | |
446 | ---------------- | |
447 | ||
448 | Please note that ``natsort`` is NOT set-up to support ``python setup.py test``. | |
449 | ||
450 | The recommended way to run tests is with `tox <https://tox.readthedocs.io/en/latest/>`_. | |
451 | After installing ``tox``, running tests is as simple as executing the following | |
452 | in the ``natsort`` directory: | |
453 | ||
454 | .. code-block:: console | |
455 | ||
456 | $ tox | |
457 | ||
458 | ``tox`` will create virtual a virtual environment for your tests and install | |
459 | all the needed testing requirements for you. You can specify a particular | |
460 | python version with the ``-e`` flag, e.g. ``tox -e py36``. Static analysis | |
461 | is done with ``tox -e flake8``. You can see all available testing environments | |
462 | with ``tox --listenvs``. | |
463 | ||
464 | How to Build Documentation | |
465 | -------------------------- | |
466 | ||
467 | If you want to build the documentation for ``natsort``, it is recommended to | |
468 | use ``tox``: | |
469 | ||
470 | .. code-block:: console | |
471 | ||
472 | $ tox -e docs | |
473 | ||
474 | This will place the documentation in ``build/sphinx/html``. | |
475 | ||
476 | Dropped Deprecated APIs | |
477 | ----------------------- | |
478 | ||
479 | In ``natsort`` version 6.0.0, the following APIs and functions were removed | |
480 | ||
481 | - ``number_type`` keyword argument (deprecated since 3.4.0) | |
482 | - ``signed`` keyword argument (deprecated since 3.4.0) | |
483 | - ``exp`` keyword argument (deprecated since 3.4.0) | |
484 | - ``as_path`` keyword argument (deprecated since 3.4.0) | |
485 | - ``py3_safe`` keyword argument (deprecated since 3.4.0) | |
486 | - ``ns.TYPESAFE`` (deprecated since version 5.0.0) | |
487 | - ``ns.DIGIT`` (deprecated since version 5.0.0) | |
488 | - ``ns.VERSION`` (deprecated since version 5.0.0) | |
489 | - ``versorted()`` (discouraged since version 4.0.0, | |
490 | officially deprecated since version 5.5.0) | |
491 | - ``index_versorted()`` (discouraged since version 4.0.0, | |
492 | officially deprecated since version 5.5.0) | |
493 | ||
494 | In general, if you want to determine if you are using deprecated APIs you | |
495 | can run your code with the following flag | |
496 | ||
497 | .. code-block:: console | |
498 | ||
499 | $ python -Wdefault::DeprecationWarning my-code.py | |
500 | ||
501 | By default ``DeprecationWarnings`` are not shown, but this will cause them | |
502 | to be shown. Alternatively, you can just set the environment variable | |
503 | ``PYTHONWARNINGS`` to "default::DeprecationWarning" and then run your code. | |
504 | ||
505 | Author | |
506 | ------ | |
507 | ||
508 | Seth M. Morton | |
509 | ||
510 | History | |
511 | ------- | |
512 | ||
513 | Please visit the changelog | |
514 | `on GitHub <https://github.com/SethMMorton/natsort/blob/master/CHANGELOG.md>`_ or | |
515 | `in the documentation <https://natsort.readthedocs.io/en/master/changelog.html>`_. |
0 | CHANGELOG.md | |
1 | LICENSE | |
2 | MANIFEST.in | |
3 | README.rst | |
4 | RELEASING.md | |
5 | setup.cfg | |
6 | setup.py | |
7 | tox.ini | |
8 | dev/README.md | |
9 | dev/bump.py | |
10 | dev/clean.py | |
11 | dev/generate_new_unicode_numbers.py | |
12 | docs/api.rst | |
13 | docs/changelog.rst | |
14 | docs/conf.py | |
15 | docs/examples.rst | |
16 | docs/howitworks.rst | |
17 | docs/index.rst | |
18 | docs/locale_issues.rst | |
19 | docs/requirements.in | |
20 | docs/requirements.txt | |
21 | docs/shell.rst | |
22 | docs/special_cases_everywhere.jpg | |
23 | mypy_stubs/icu.pyi | |
24 | natsort/__init__.py | |
25 | natsort/__main__.py | |
26 | natsort/natsort.py | |
27 | natsort/ns_enum.py | |
28 | natsort/py.typed | |
29 | natsort/unicode_numbers.py | |
30 | natsort/unicode_numeric_hex.py | |
31 | natsort/utils.py | |
32 | natsort.egg-info/PKG-INFO | |
33 | natsort.egg-info/SOURCES.txt | |
34 | natsort.egg-info/dependency_links.txt | |
35 | natsort.egg-info/entry_points.txt | |
36 | natsort.egg-info/not-zip-safe | |
37 | natsort.egg-info/requires.txt | |
38 | natsort.egg-info/top_level.txt | |
39 | natsort/compat/__init__.py | |
40 | natsort/compat/fake_fastnumbers.py | |
41 | natsort/compat/fastnumbers.py | |
42 | natsort/compat/locale.py | |
43 | tests/conftest.py | |
44 | tests/profile_natsorted.py | |
45 | tests/test_fake_fastnumbers.py | |
46 | tests/test_final_data_transform_factory.py | |
47 | tests/test_input_string_transform_factory.py | |
48 | tests/test_main.py | |
49 | tests/test_natsort_key.py | |
50 | tests/test_natsort_keygen.py | |
51 | tests/test_natsorted.py | |
52 | tests/test_natsorted_convenience.py | |
53 | tests/test_ns_enum.py | |
54 | tests/test_os_sorted.py | |
55 | tests/test_parse_bytes_function.py | |
56 | tests/test_parse_number_function.py | |
57 | tests/test_parse_string_function.py | |
58 | tests/test_regex.py | |
59 | tests/test_string_component_transform_factory.py | |
60 | tests/test_unicode_numbers.py | |
61 | tests/test_utils.py⏎ |
0 | natsort |
0 | 0 | [bumpversion] |
1 | current_version = 8.0.2 | |
1 | current_version = 8.2.0 | |
2 | 2 | commit = True |
3 | 3 | tag = True |
4 | 4 | tag_name = {new_version} |
24 | 24 | Natural Language :: English |
25 | 25 | Programming Language :: Python |
26 | 26 | Programming Language :: Python :: 3 |
27 | Programming Language :: Python :: 3.6 | |
28 | 27 | Programming Language :: Python :: 3.7 |
29 | 28 | Programming Language :: Python :: 3.8 |
30 | 29 | Programming Language :: Python :: 3.9 |
63 | 62 | .venv |
64 | 63 | |
65 | 64 | [mypy] |
65 | mypy_path = mypy_stubs | |
66 | 66 | |
67 | [mypy-icu] | |
68 | ignore_missing_imports = True | |
67 | [egg_info] | |
68 | tag_build = | |
69 | tag_date = 0 | |
70 |
3 | 3 | |
4 | 4 | setup( |
5 | 5 | name="natsort", |
6 | version="8.0.2", | |
6 | version="8.2.0", | |
7 | 7 | packages=find_packages(), |
8 | 8 | entry_points={"console_scripts": ["natsort = natsort.__main__:main"]}, |
9 | python_requires=">=3.6", | |
9 | python_requires=">=3.7", | |
10 | 10 | extras_require={"fast": ["fastnumbers >= 2.0.0"], "icu": ["PyICU >= 1.0.0"]}, |
11 | 11 | package_data={"": ["py.typed"]}, |
12 | 12 | zip_safe=False, |
4 | 4 | """ |
5 | 5 | |
6 | 6 | from operator import itemgetter |
7 | from pathlib import PurePosixPath | |
7 | 8 | from typing import List, Tuple, Union |
8 | 9 | |
9 | 10 | import pytest |
81 | 82 | given = ["1.9.9a", "1.11", "1.9.9b", "1.11.4", "1.10.1"] |
82 | 83 | expected = ["1.9.9a", "1.9.9b", "1.10.1", "1.11", "1.11.4"] |
83 | 84 | assert natsorted(given) == expected |
85 | ||
86 | ||
87 | def test_natsorted_can_sorts_paths_same_as_strings() -> None: | |
88 | paths = [ | |
89 | PurePosixPath("a/1/something"), | |
90 | PurePosixPath("a/2/something"), | |
91 | PurePosixPath("a/10/something"), | |
92 | ] | |
93 | assert [str(p) for p in natsorted(paths)] == natsorted([str(p) for p in paths]) | |
84 | 94 | |
85 | 95 | |
86 | 96 | @pytest.mark.parametrize( |
178 | 188 | # You can sort paths and numbers, not that you'd want to |
179 | 189 | given: List[Union[str, int]] = ["/Folder (9)/file.exe", 43] |
180 | 190 | expected: List[Union[str, int]] = [43, "/Folder (9)/file.exe"] |
191 | assert natsorted(given, alg=ns.PATH) == expected | |
192 | ||
193 | ||
194 | def test_natsorted_path_extensions_heuristic() -> None: | |
195 | # https://github.com/SethMMorton/natsort/issues/145 | |
196 | given = [ | |
197 | "Try.Me.Bug - 09 - One.Two.Three.[text].mkv", | |
198 | "Try.Me.Bug - 07 - One.Two.5.[text].mkv", | |
199 | "Try.Me.Bug - 08 - One.Two.Three[text].mkv", | |
200 | ] | |
201 | expected = [ | |
202 | "Try.Me.Bug - 07 - One.Two.5.[text].mkv", | |
203 | "Try.Me.Bug - 08 - One.Two.Three[text].mkv", | |
204 | "Try.Me.Bug - 09 - One.Two.Three.[text].mkv", | |
205 | ] | |
181 | 206 | assert natsorted(given, alg=ns.PATH) == expected |
182 | 207 | |
183 | 208 |
2 | 2 | Testing for the OS sorting |
3 | 3 | """ |
4 | 4 | import platform |
5 | from typing import cast | |
6 | 5 | |
7 | 6 | import natsort |
8 | 7 | import pytest |
43 | 42 | def test_os_sorted_key() -> None: |
44 | 43 | given = ["foo0", "foo2", "goo1"] |
45 | 44 | expected = ["foo0", "goo1", "foo2"] |
46 | result = natsort.os_sorted(given, key=lambda x: cast(str, x).replace("g", "f")) | |
45 | result = natsort.os_sorted(given, key=lambda x: x.replace("g", "f")) | |
47 | 46 | assert result == expected |
48 | 47 | |
49 | 48 |
6 | 6 | from hypothesis import given |
7 | 7 | from hypothesis.strategies import floats, integers |
8 | 8 | from natsort.ns_enum import NSType, ns |
9 | from natsort.utils import MaybeNumTransformer, parse_number_or_none_factory | |
9 | from natsort.utils import NumTransformer, parse_number_or_none_factory | |
10 | 10 | |
11 | 11 | |
12 | 12 | @pytest.mark.usefixtures("with_locale_en_us") |
21 | 21 | ) |
22 | 22 | @given(x=floats(allow_nan=False) | integers()) |
23 | 23 | def test_parse_number_factory_makes_function_that_returns_tuple( |
24 | x: Union[float, int], alg: NSType, example_func: MaybeNumTransformer | |
24 | x: Union[float, int], alg: NSType, example_func: NumTransformer | |
25 | 25 | ) -> None: |
26 | 26 | parse_number_func = parse_number_or_none_factory(alg, "", "xx") |
27 | 27 | assert parse_number_func(x) == example_func(x) |
5 | 5 | import string |
6 | 6 | from itertools import chain |
7 | 7 | from operator import neg as op_neg |
8 | from typing import List, Pattern, Union | |
8 | from typing import List, Pattern, Tuple, Union | |
9 | 9 | |
10 | 10 | import pytest |
11 | 11 | from hypothesis import given |
154 | 154 | assert tuple(utils.path_splitter(z)) == tuple(pathlib.Path(z).parts) |
155 | 155 | |
156 | 156 | |
157 | def test_path_splitter_splits_path_string_by_sep_and_removes_extension_example() -> None: | |
158 | given = "/this/is/a/path/file.x1.10.tar.gz" | |
159 | expected = (os.sep, "this", "is", "a", "path", "file.x1.10", ".tar", ".gz") | |
157 | @pytest.mark.parametrize( | |
158 | "given, expected", | |
159 | [ | |
160 | ( | |
161 | "/this/is/a/path/file.x1.10.tar.gz", | |
162 | (os.sep, "this", "is", "a", "path", "file.x1.10", ".tar", ".gz"), | |
163 | ), | |
164 | ( | |
165 | "/this/is/a/path/file.x1.10.tar", | |
166 | (os.sep, "this", "is", "a", "path", "file.x1.10", ".tar"), | |
167 | ), | |
168 | ( | |
169 | "/this/is/a/path/file.x1.threethousand.tar", | |
170 | (os.sep, "this", "is", "a", "path", "file.x1.threethousand", ".tar"), | |
171 | ), | |
172 | ], | |
173 | ) | |
174 | def test_path_splitter_splits_path_string_by_sep_and_removes_extension_example( | |
175 | given: str, expected: Tuple[str, ...] | |
176 | ) -> None: | |
160 | 177 | assert tuple(utils.path_splitter(given)) == tuple(expected) |
161 | 178 | |
162 | 179 |
4 | 4 | |
5 | 5 | [tox] |
6 | 6 | envlist = |
7 | flake8, mypy, py36, py37, py38, py39, py310 | |
8 | # Other valid evironments are: | |
7 | flake8, mypy, py37, py38, py39, py310 | |
8 | # Other valid environments are: | |
9 | 9 | # docs |
10 | 10 | # release |
11 | 11 | # clean |
59 | 59 | pytest |
60 | 60 | pytest-mock |
61 | 61 | fastnumbers |
62 | typing_extensions | |
62 | 63 | commands = |
63 | 64 | mypy --strict natsort tests |
64 | 65 | skip_install = true |
103 | 104 | # Get GitHub actions to run the correct tox environment |
104 | 105 | [gh-actions] |
105 | 106 | python = |
106 | 3.5: py35 | |
107 | 3.6: py36 | |
108 | 107 | 3.7: py37 |
109 | 108 | 3.8: py38 |
110 | 109 | 3.9: py39 |