├── .coveragerc
├── .gitattributes
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── pull_request_template.md
└── workflows
│ ├── all_core_tests.yml
│ ├── ready_doctest.yml
│ ├── ready_linting.yml
│ ├── ready_test_matrix.yml
│ ├── ready_test_nonloc.yml
│ ├── release_check_sdist.yml
│ ├── release_doc_warnings.yml
│ ├── release_flake8_noqa_nofail.yml
│ ├── release_readme_doctest.yml
│ └── release_test_file_coverage.yml
├── .gitignore
├── .readthedocs.yaml
├── AUTHORS.md
├── CHANGELOG.md
├── CONTENT_LICENSE.txt
├── CONTRIBUTING.md
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── SECURITY.md
├── conftest.py
├── doc
├── Makefile
├── make.bat
└── source
│ ├── _static
│ ├── css
│ │ └── custom.css
│ ├── extlink.svg
│ ├── mouseover_example.png
│ ├── no-leven.csv
│ ├── soi-logo.png
│ ├── soi-logo_duo.png
│ ├── soi-logo_duo_border.png
│ ├── soi-logo_duo_border.xcf
│ ├── suggest_timing.png
│ ├── suggest_timing.xlsx
│ ├── suggest_timing_commands.txt
│ └── with-leven.csv
│ ├── _templates
│ └── footer.html
│ ├── api
│ ├── data.rst
│ ├── enum.rst
│ ├── error.rst
│ ├── fileops.rst
│ ├── index.rst
│ ├── inventory.rst
│ ├── re.rst
│ ├── schema.rst
│ └── zlib.rst
│ ├── api_usage.rst
│ ├── cli
│ ├── convert.rst
│ ├── implementation
│ │ ├── convert.rst
│ │ ├── core.rst
│ │ ├── index.rst
│ │ ├── load.rst
│ │ ├── parser.rst
│ │ ├── paths.rst
│ │ ├── suggest.rst
│ │ ├── ui.rst
│ │ └── write.rst
│ ├── index.rst
│ └── suggest.rst
│ ├── conf.py
│ ├── customfile.rst
│ ├── index.rst
│ ├── isphx
│ └── objpull.py
│ ├── levenshtein.rst
│ └── syntax.rst
├── pyproject.toml
├── requirements-ci.txt
├── requirements-dev.txt
├── requirements-flake8.txt
├── requirements-interrogate.txt
├── requirements-rtd.txt
├── setup.py
├── src
└── sphobjinv
│ ├── __init__.py
│ ├── __main__.py
│ ├── _vendored
│ ├── __init__.py
│ └── fuzzywuzzy
│ │ ├── LICENSE.txt
│ │ ├── __init__.py
│ │ ├── fuzz.py
│ │ ├── process.py
│ │ ├── tests.py
│ │ └── utils.py
│ ├── cli
│ ├── __init__.py
│ ├── convert.py
│ ├── core.py
│ ├── load.py
│ ├── parser.py
│ ├── paths.py
│ ├── suggest.py
│ ├── ui.py
│ └── write.py
│ ├── data.py
│ ├── enum.py
│ ├── error.py
│ ├── fileops.py
│ ├── inventory.py
│ ├── re.py
│ ├── schema.py
│ ├── version.py
│ └── zlib.py
├── tests
├── __init__.py
├── resource
│ ├── objects_NAPALM.inv
│ ├── objects_attrs.inv
│ ├── objects_attrs.json
│ ├── objects_attrs.txt
│ ├── objects_attrs_17_2_0.inv
│ ├── objects_attrs_20_3_0.inv
│ ├── objects_beaker.inv
│ ├── objects_bokeh.inv
│ ├── objects_bootstrap_datepicker.inv
│ ├── objects_cclib.inv
│ ├── objects_celery.inv
│ ├── objects_click.inv
│ ├── objects_cookiecutter.inv
│ ├── objects_coverage.inv
│ ├── objects_django.inv
│ ├── objects_django_channels.inv
│ ├── objects_eyeD3.inv
│ ├── objects_faker.inv
│ ├── objects_flake8.inv
│ ├── objects_flask.inv
│ ├── objects_fonttools.inv
│ ├── objects_gspread.inv
│ ├── objects_h5py.inv
│ ├── objects_hypothesis.inv
│ ├── objects_jinja2.inv
│ ├── objects_jsonschema.inv
│ ├── objects_matplotlib.inv
│ ├── objects_mdn.bad_inv
│ ├── objects_mistune.inv
│ ├── objects_mkdoc_zlib0.inv
│ ├── objects_mypy.inv
│ ├── objects_nltk.inv
│ ├── objects_noinfo.inv
│ ├── objects_noproject.inv
│ ├── objects_numpy.inv
│ ├── objects_opencv.inv
│ ├── objects_pandas.inv
│ ├── objects_pdfminer.inv
│ ├── objects_pelican.inv
│ ├── objects_pingo.inv
│ ├── objects_plone.inv
│ ├── objects_psutil.inv
│ ├── objects_pyexcel.inv
│ ├── objects_pygame.inv
│ ├── objects_pymongo.inv
│ ├── objects_pyqt.inv
│ ├── objects_pyserial.inv
│ ├── objects_pytest.inv
│ ├── objects_python.inv
│ ├── objects_requests.inv
│ ├── objects_rocket.inv
│ ├── objects_sarge.inv
│ ├── objects_scapy.inv
│ ├── objects_scipy.inv
│ ├── objects_scrapy.inv
│ ├── objects_sklearn.inv
│ ├── objects_sphinx.inv
│ ├── objects_sphinx_1_6_6.inv
│ ├── objects_sqlalchemy.inv
│ ├── objects_sympy.inv
│ ├── objects_tinydb.inv
│ ├── objects_tox.inv
│ ├── objects_twython.inv
│ └── objects_yt.inv
├── test_api_fail.py
├── test_api_good.py
├── test_api_good_nonlocal.py
├── test_cli.py
├── test_cli_nonlocal.py
├── test_fixture.py
├── test_flake8_ext.py
├── test_intersphinx.py
└── test_valid_objects.py
└── tox.ini
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | source =
3 | src
4 |
5 | omit =
6 | # Don't worry about covering vendored libraries
7 | src/sphobjinv/_vendored/*
8 |
9 | [report]
10 | exclude_lines =
11 | pragma: no cover
12 | ^\s*pass\s*$
13 |
14 | show_missing = True
15 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | tests/resource/objects_mkdoc_zlib0.inv binary
2 | tests/resource/objects_attrs.txt binary
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [bskinn] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: pypi/sphobjinv # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # bskinn # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # ['https://paypal.me/btskinn'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Create a bug report to help improve the project
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Brief description**
11 |
12 |
13 | **Expected behavior**
14 |
15 |
16 | **Actual behavior**
17 |
18 |
19 | **To reproduce**
20 |
26 |
27 | **Attachments**
28 |
36 |
37 | **System information**
38 | - Device:
39 | - OS:
40 |
41 | **Python environment**
42 |
43 | *Python*
44 |
48 |
49 | *Libraries*
50 |
54 |
55 | **Additional information**
56 |
57 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 |
12 |
13 | **Describe the solution you'd like**
14 |
15 |
16 | **Describe alternatives you've considered**
17 |
18 |
19 | **Additional context**
20 |
24 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Is the PR a fix or a feature?**
4 |
5 |
6 | **Describe the changes in the PR**
7 |
13 |
14 | **Does this PR close any issues?**
15 |
24 |
25 | **Does the PR change/update the following, if relevant?**
26 |
36 |
37 | - [ ] Documentation
38 | - [ ] Tests
39 | - [ ] CHANGELOG
40 |
41 |
--------------------------------------------------------------------------------
/.github/workflows/all_core_tests.yml:
--------------------------------------------------------------------------------
1 | name: 'ALL: Run tests on Python 3.12'
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | current_python_tests:
11 | name: ${{ matrix.os }}
12 | runs-on: ${{ matrix.os }}
13 | concurrency:
14 | group: ${{ github.workflow }}-${{ matrix.os }}-${{ github.ref }}
15 | cancel-in-progress: true
16 | strategy:
17 | matrix:
18 | os: ['ubuntu-latest', 'windows-latest']
19 |
20 | steps:
21 | - name: Check out repo
22 | uses: actions/checkout@v4
23 |
24 | - name: Install Python
25 | uses: actions/setup-python@v5
26 | with:
27 | python-version: '3.12'
28 | cache: 'pip'
29 | cache-dependency-path: requirements-ci.txt
30 |
31 | - name: Update pip
32 | run: python -m pip install -U pip
33 |
34 | - name: Install & report CI dependencies
35 | run: |
36 | python -m pip install -U --force-reinstall -r requirements-ci.txt
37 | python --version
38 | pip list
39 |
40 | - name: Build docs
41 | run: |
42 | cd doc
43 | make html
44 | mkdir scratch
45 |
46 | - name: Run tests & report source coverage
47 | run: |
48 | pytest --cov --testall
49 |
--------------------------------------------------------------------------------
/.github/workflows/ready_doctest.yml:
--------------------------------------------------------------------------------
1 | name: 'READY: Run doctests'
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - reopened
8 | - synchronize
9 | - ready_for_review
10 | branches:
11 | - main
12 | - stable
13 |
14 | jobs:
15 | code_doctests:
16 | name: in code
17 | runs-on: 'ubuntu-latest'
18 | concurrency:
19 | group: ${{ github.workflow }}-${{ github.ref }}
20 | cancel-in-progress: true
21 | if: ${{ !github.event.pull_request.draft }}
22 |
23 | steps:
24 | - name: Check out repo
25 | uses: actions/checkout@v4
26 |
27 | - name: Install Python
28 | uses: actions/setup-python@v5
29 | with:
30 | python-version: '3.12'
31 | cache: 'pip'
32 | cache-dependency-path: requirements-ci.txt
33 |
34 | - name: Update pip
35 | run: python -m pip install -U pip
36 |
37 | - name: Install dev requirements
38 | run: python -m pip install -r requirements-ci.txt
39 |
40 | - name: Run doctests
41 | run: |
42 | cd doc
43 | make doctest
44 |
--------------------------------------------------------------------------------
/.github/workflows/ready_linting.yml:
--------------------------------------------------------------------------------
1 | name: 'READY: Lint codebase'
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - reopened
8 | - synchronize
9 | - ready_for_review
10 | branches:
11 | - main
12 | - stable
13 |
14 | jobs:
15 | lint_flake8:
16 | name: flake8
17 | runs-on: 'ubuntu-latest'
18 | concurrency:
19 | group: ${{ github.workflow }}-flake8-${{ github.ref }}
20 | cancel-in-progress: true
21 | if: ${{ !github.event.pull_request.draft }}
22 |
23 | steps:
24 | - name: Check out repo
25 | uses: actions/checkout@v4
26 |
27 | - name: Install Python
28 | uses: actions/setup-python@v5
29 | with:
30 | python-version: '3.12'
31 | cache: 'pip'
32 | cache-dependency-path: requirements-flake8.txt
33 |
34 | - name: Update pip
35 | run: python -m pip install -U pip
36 |
37 | - name: Install tox
38 | run: python -m pip install -U tox
39 |
40 | - name: Run flake8
41 | run: tox -e flake8
42 |
43 | lint_interrogate:
44 | name: interrogate
45 | runs-on: 'ubuntu-latest'
46 | concurrency:
47 | group: ${{ github.workflow }}-interrogate-${{ github.ref }}
48 | cancel-in-progress: true
49 | if: ${{ !github.event.pull_request.draft }}
50 |
51 | steps:
52 | - name: Check out repo
53 | uses: actions/checkout@v4
54 |
55 | - name: Install Python
56 | uses: actions/setup-python@v5
57 | with:
58 | python-version: '3.12'
59 | cache: 'pip'
60 | cache-dependency-path: requirements-interrogate.txt
61 |
62 | - name: Update pip
63 | run: python -m pip install -U pip
64 |
65 | - name: Install tox
66 | run: python -m pip install -U tox
67 |
68 | - name: Run interrogate
69 | run: tox -e interrogate
70 |
--------------------------------------------------------------------------------
/.github/workflows/ready_test_matrix.yml:
--------------------------------------------------------------------------------
1 | name: 'READY: Run OS/Python test matrix'
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - reopened
8 | - synchronize
9 | - ready_for_review
10 | branches:
11 | - main
12 | - stable
13 |
14 | jobs:
15 | python_os_test_matrix:
16 | name: ${{ matrix.os }} python ${{ matrix.py }}
17 | runs-on: ${{ matrix.os }}
18 | concurrency:
19 | group: ${{ github.workflow }}-${{ matrix.os }}-${{ matrix.py }}-${{ github.ref }}
20 | cancel-in-progress: true
21 | if: ${{ !github.event.pull_request.draft }}
22 | strategy:
23 | matrix:
24 | os: ['windows-latest', 'ubuntu-latest', 'macos-latest']
25 | py: ['3.9', '3.10', '3.11', '3.13']
26 | include:
27 | - os: 'macos-latest'
28 | py: '3.12'
29 |
30 | steps:
31 | - name: Check out repo
32 | uses: actions/checkout@v4
33 |
34 | - name: Install Python
35 | uses: actions/setup-python@v5
36 | with:
37 | python-version: ${{ matrix.py }}
38 | cache: 'pip'
39 | cache-dependency-path: requirements-ci.txt
40 |
41 | - name: Update pip
42 | run: python -m pip install -U pip
43 |
44 | - name: Install & report CI dependencies
45 | run: |
46 | python -m pip install -U --force-reinstall -r requirements-ci.txt
47 | python --version
48 | pip list
49 |
50 | - name: Build docs
51 | run: |
52 | cd doc
53 | make html
54 | mkdir scratch
55 |
56 | - name: Run tests
57 | run: |
58 | pytest --testall
59 |
--------------------------------------------------------------------------------
/.github/workflows/ready_test_nonloc.yml:
--------------------------------------------------------------------------------
1 | name: 'READY: Run nonlocal tests'
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - reopened
8 | - synchronize
9 | - ready_for_review
10 | branches:
11 | - main
12 | - stable
13 |
14 | jobs:
15 | python_os_test_matrix:
16 | name: ${{ matrix.os }} python 3.12
17 | runs-on: ${{ matrix.os }}
18 | concurrency:
19 | group: ${{ github.workflow }}-${{ matrix.os }}-${{ github.ref }}
20 | cancel-in-progress: true
21 | if: ${{ !github.event.pull_request.draft }}
22 | strategy:
23 | matrix:
24 | os: ['windows-latest', 'ubuntu-latest', 'macos-latest']
25 |
26 | steps:
27 | - name: Check out repo
28 | uses: actions/checkout@v4
29 |
30 | - name: Install Python
31 | uses: actions/setup-python@v5
32 | with:
33 | python-version: '3.12'
34 | cache: 'pip'
35 | cache-dependency-path: requirements-ci.txt
36 |
37 | - name: Update pip
38 | run: python -m pip install -U pip
39 |
40 | - name: Install & report CI dependencies
41 | run: |
42 | python -m pip install -U --force-reinstall -r requirements-ci.txt
43 | python --version
44 | pip list
45 |
46 | - name: Build docs
47 | run: |
48 | cd doc
49 | make html
50 | mkdir scratch
51 |
52 | - name: Run nonlocal tests
53 | run: |
54 | pytest --nonloc
55 |
--------------------------------------------------------------------------------
/.github/workflows/release_check_sdist.yml:
--------------------------------------------------------------------------------
1 | name: 'RELEASE: Check sdist'
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - reopened
8 | - synchronize
9 | - ready_for_review
10 | branches:
11 | - stable
12 |
13 | jobs:
14 | sdist_build_and_check:
15 | name: builds & is testable
16 | runs-on: 'ubuntu-latest'
17 | concurrency:
18 | group: ${{ github.workflow }}-${{ github.ref }}
19 | cancel-in-progress: true
20 | if: ${{ !github.event.pull_request.draft }}
21 |
22 | steps:
23 | - name: Check out repo
24 | uses: actions/checkout@v4
25 |
26 | - name: Install Python
27 | uses: actions/setup-python@v5
28 | with:
29 | python-version: '3.12'
30 | cache: 'pip'
31 | cache-dependency-path: requirements-dev.txt
32 |
33 | - name: Install 'build' package
34 | run: python -m pip install build
35 |
36 | - name: Build sdist
37 | run: |
38 | python -m build -s
39 | ls -lah dist
40 |
41 | - name: Create sandbox
42 | run: mkdir sandbox
43 |
44 | - name: Unpack sdist in sandbox
45 | run: |
46 | cp dist/*.gz sandbox/
47 | cd sandbox
48 | tar xvf *.gz
49 |
50 | - name: Create venv
51 | run: |
52 | cd sandbox
53 | python -m venv env
54 |
55 | # Only the dir of the unpacked sdist will have a digit in its name
56 | - name: Store sdist unpack path
57 | run: echo "UNPACK_PATH=$( find sandbox -maxdepth 1 -type d -regex 'sandbox/.+[0-9].+' )" >> $GITHUB_ENV
58 |
59 | - name: Report sdist unpack path
60 | run: echo $UNPACK_PATH
61 |
62 | - name: Install dev req'ts to venv
63 | run: |
64 | source sandbox/env/bin/activate
65 | cd "$UNPACK_PATH"
66 | python -m pip install -r requirements-dev.txt
67 |
68 | - name: Build docs in sandbox
69 | run: |
70 | source sandbox/env/bin/activate
71 | cd "$UNPACK_PATH"/doc
72 | O=-Ean make html
73 |
74 | - name: Run test suite in sandbox
75 | run: |
76 | source sandbox/env/bin/activate
77 | cd "$UNPACK_PATH"
78 | pytest --nonloc
79 |
--------------------------------------------------------------------------------
/.github/workflows/release_doc_warnings.yml:
--------------------------------------------------------------------------------
1 | name: 'RELEASE: Build docs'
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - reopened
8 | - synchronize
9 | - ready_for_review
10 | branches:
11 | - stable
12 |
13 | jobs:
14 | docs_warnings_as_errors:
15 | name: with warnings as errors
16 | runs-on: 'ubuntu-latest'
17 | concurrency:
18 | group: ${{ github.workflow }}-${{ github.ref }}
19 | cancel-in-progress: true
20 | if: ${{ !github.event.pull_request.draft }}
21 |
22 | steps:
23 | - name: Check out repo
24 | uses: actions/checkout@v4
25 |
26 | - name: Install Python
27 | uses: actions/setup-python@v5
28 | with:
29 | python-version: '3.12'
30 | cache: 'pip'
31 | cache-dependency-path: requirements-rtd.txt
32 |
33 | - name: Install project and docs requirements
34 | run: pip install . -r requirements-rtd.txt
35 |
36 | - name: Build docs with warnings as errors
37 | run: |
38 | cd doc
39 | make html -Wn --keep-going
40 |
--------------------------------------------------------------------------------
/.github/workflows/release_flake8_noqa_nofail.yml:
--------------------------------------------------------------------------------
1 | name: 'RELEASE: Check flake8 noqas'
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - reopened
8 | - synchronize
9 | - ready_for_review
10 | branches:
11 | - stable
12 |
13 | jobs:
14 | check_flake8_noqa:
15 | name: are all relevant (nofail)
16 | runs-on: 'ubuntu-latest'
17 | concurrency:
18 | group: ${{ github.workflow }}-${{ github.ref }}
19 | cancel-in-progress: true
20 | if: ${{ !github.event.pull_request.draft }}
21 |
22 | steps:
23 | - name: Check out repo
24 | uses: actions/checkout@v4
25 |
26 | - name: Install Python
27 | uses: actions/setup-python@v5
28 | with:
29 | python-version: '3.12'
30 | cache: 'pip'
31 | cache-dependency-path: requirements-ci.txt
32 |
33 | - name: Install tox
34 | run: pip install tox
35 |
36 | - name: Run never-fail flake8-with-noqa
37 | run: tox -e flake8_noqa
38 |
--------------------------------------------------------------------------------
/.github/workflows/release_readme_doctest.yml:
--------------------------------------------------------------------------------
1 | name: 'RELEASE: Run doctests'
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - reopened
8 | - synchronize
9 | - ready_for_review
10 | branches:
11 | - stable
12 |
13 | jobs:
14 | readme_doctest:
15 | name: on readme
16 | runs-on: 'ubuntu-latest'
17 | concurrency:
18 | group: ${{ github.workflow }}-${{ github.ref }}
19 | cancel-in-progress: true
20 | if: ${{ !github.event.pull_request.draft }}
21 |
22 | steps:
23 | - name: Check out repo
24 | uses: actions/checkout@v4
25 |
26 | - name: Install Python
27 | uses: actions/setup-python@v5
28 | with:
29 | python-version: '3.12'
30 | cache: 'pip'
31 | cache-dependency-path: requirements-ci.txt
32 |
33 | - name: Install CI requirements
34 | run: pip install -r requirements-ci.txt
35 |
36 | - name: Build docs
37 | run: |
38 | cd doc
39 | make html
40 |
41 | - name: Run README doctests
42 | run: pytest -k readme --doctest-glob="README.md"
43 |
--------------------------------------------------------------------------------
/.github/workflows/release_test_file_coverage.yml:
--------------------------------------------------------------------------------
1 | name: 'RELEASE: Check test code'
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - reopened
8 | - synchronize
9 | - ready_for_review
10 | branches:
11 | - stable
12 |
13 | jobs:
14 | check_all_tests_ran:
15 | name: all executed
16 | runs-on: 'ubuntu-latest'
17 | concurrency:
18 | group: ${{ github.workflow }}-${{ github.ref }}
19 | cancel-in-progress: true
20 | if: ${{ !github.event.pull_request.draft }}
21 |
22 | steps:
23 | - name: Check out repo
24 | uses: actions/checkout@v4
25 |
26 | - name: Install Python
27 | uses: actions/setup-python@v5
28 | with:
29 | python-version: '3.12'
30 | cache: 'pip'
31 | cache-dependency-path: |
32 | requirements-ci.txt
33 | requirements-flake8.txt
34 |
35 | - name: Install CI requirements
36 | run: pip install -r requirements-ci.txt -r requirements-flake8.txt
37 |
38 | - name: Build docs & ensure scratch
39 | run: |
40 | cd doc
41 | make html
42 | mkdir scratch
43 |
44 | - name: Run pytest covering entire project tree
45 | run: pytest --cov=. --nonloc --flake8_ext
46 |
47 | - name: Check 100% test code execution
48 | run: coverage report --include="tests/*" --fail-under=100
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env*/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | pip-wheel-metadata/
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *,cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 |
56 | # Sphinx documentation
57 | docs/_build/
58 |
59 | # PyBuilder
60 | target/
61 |
62 | #Ipython Notebook
63 | .ipynb_checkpoints
64 |
65 | # Editor backup files
66 | *.bak
67 | *.swp
68 |
69 | # intersphinx objects.inv hive
70 | doc/source/isphx/*.inv
71 |
72 | # Test scratch
73 | sphobjinv/test/scratch/*
74 |
75 | # Doctest scratch
76 | doc/scratch/*
77 |
78 | # Mutmut
79 | .mutmut-cache
80 |
81 | # VS Code
82 | .vscode
83 |
84 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Build and VM configuration
9 | build:
10 | os: 'ubuntu-22.04'
11 | tools:
12 | python: '3.12'
13 |
14 | # Python requirements
15 | python:
16 | install:
17 | - requirements: requirements-rtd.txt
18 | - method: pip
19 | path: .
20 |
21 | # Build with sphinx
22 | sphinx:
23 | configuration: doc/source/conf.py
24 |
25 | # Build all the things
26 | formats: all
27 |
--------------------------------------------------------------------------------
/AUTHORS.md:
--------------------------------------------------------------------------------
1 | Credits
2 | =======
3 |
4 | `sphobjinv` is authored and maintained by Brian Skinn
5 | ([Blog](https://bskinn.github.io))
6 | ([Mastodon](https://fosstodon.org/@btskinn)).
7 |
8 | The idea for the project came about as I was starting to deepen my expertise
9 | with Sphinx, and found it hugely frustrating to debug cross-references to
10 | objects in code. I discovered the `objects.inv` files relatively quickly, but
11 | struggled with trying to get at the actual object information. At the time
12 | (2016), the ability to
13 | [execute `sphinx.ext.intersphinx` as a module](https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#showing-all-links-of-an-intersphinx-mapping-file)
14 | hadn't yet been documented (that happened in
15 | [2018](https://github.com/sphinx-doc/sphinx/commit/7aaba1758a4622298d15339fddd8556eb221af86)),
16 | and a fair bit of searching didn't turn up anything promising.
17 |
18 | Once I dug into the Sphinx code to figure out how to zlib-decompress the object
19 | data, it was relatively straightforward to put together the initial v1.0 of
20 | `sphobjinv`, which could only (de)compress `objects.inv` files to/from
21 | plaintext. As I started to use it regularly in my own documentation work, it
22 | became clear that there would be significant advantages to also implement object
23 | searches, especially in large documentation sets. Also, it seemed likely that a
24 | robust API for creation/manipulation of inventory contents would be useful, in
25 | order to assist with things like scraping a non-Sphinx website to generate an
26 | `objects.inv` for cross-referencing in other docs. This led to the current
27 | object-oriented API of `sphobjinv` v2.x.
28 |
29 | While there are [numerous](https://github.com/bskinn/sphobjinv/issues) possible
30 | enhancements to the project, I'm satisfied with its ease of use and usefulness,
31 | at least for my purposes, and thus consider it fully stable. I'm always glad to
32 | receive feature requests and bug reports, though.
33 |
--------------------------------------------------------------------------------
/CONTENT_LICENSE.txt:
--------------------------------------------------------------------------------
1 | The sphobjinv documentation (including docstrings and README) is licensed under
2 | a Creative Commons Attribution 4.0 International License (CC-BY).
3 |
4 | See http://creativecommons.org/licenses/by/4.0/.
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2025 Brian Skinn and community contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include AUTHORS.md CHANGELOG.md CONTRIBUTING.md LICENSE.txt pyproject.toml
2 | include README.md requirements-dev.txt requirements-flake8.txt tox.ini
3 |
4 | graft src/sphobjinv/_vendored/fuzzywuzzy
5 |
6 | graft doc/source
7 | include doc/make.bat doc/Makefile
8 |
9 | include conftest.py
10 | graft tests
11 | prune tests/resource
12 | include tests/resource/objects_attrs* tests/resource/objects_sarge*
13 |
14 | global-exclude __pycache__/*
15 | prune **/*.egg-info
16 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | `sphobjinv` development currently does not use any maintenance branches,
6 | so any security fixes will be released inline with the primary development
7 | branch.
8 |
9 |
10 | ## Reporting a Vulnerability
11 |
12 | To report a security vulnerability, please use the
13 | [Tidelift security contact](https://tidelift.com/security).
14 | Tidelift will coordinate the fix and disclosure, including
15 | any updates on progress toward a fix on a vulnerability and
16 | on accept/decline status of the vulnerability report.
17 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Custom for sphinx-autobuild
18 | livehtml:
19 | sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)"/html -nb html $(SPHINXOPTS) $(O)
20 |
21 | # Catch-all target: route all unknown targets to Sphinx using the new
22 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
23 | %: Makefile
24 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
25 |
--------------------------------------------------------------------------------
/doc/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 |
29 | if "%1" == "livehtml" (
30 | sphinx-autobuild %SOURCEDIR% %BUILDDIR%/html -nb html %SPHINXOPTS% %O% %2
31 | goto end
32 | )
33 |
34 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% %2
35 | goto end
36 |
37 | :help
38 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
39 |
40 | :end
41 | popd
42 |
--------------------------------------------------------------------------------
/doc/source/_static/css/custom.css:
--------------------------------------------------------------------------------
1 | /* Lighten the header blue color */
2 | div.wy-side-nav-search {
3 | background-color: #7ebbff;
4 | }
5 |
6 | /* White version text in header */
7 | div.version {
8 | color: #ffffff !important;
9 | font-weight: bolder !important;
10 | }
11 |
12 | /* Blue border around the search bar */
13 | form.wy-form input {
14 | border: 3px solid #6eabef !important;
15 | }
16 |
17 | /*
18 | ul.current selects only the sidebar ul. Otherwise,
19 | toctrees in the text get colored white, too
20 | */
21 | ul.current li.toctree-l1:not(.current) a {
22 | color: #f8f8f8 !important;
23 | }
24 |
25 | /* Increase font size for code object names in navbar toctree */
26 | a.reference.internal code.docutils.literal.notranslate {
27 | font-size: 85%;
28 | }
29 |
30 | /*
31 | Reduce margin at bottom of 'property' docs.
32 | Too much vertical whitespace previously.
33 | */
34 | .rst-content dl.property {
35 | margin-bottom: 10px !important;
36 | }
37 |
38 | /*
39 | Reduced font size in footer
40 | */
41 | footer, footer div[role=contentinfo] p, footer p.footer-license {
42 | font-size: 13px;
43 | }
44 |
45 | footer p.footer-license {
46 | line-height: 20px;
47 | }
48 |
--------------------------------------------------------------------------------
/doc/source/_static/extlink.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/doc/source/_static/mouseover_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/mouseover_example.png
--------------------------------------------------------------------------------
/doc/source/_static/no-leven.csv:
--------------------------------------------------------------------------------
1 | objects_NAPALM.inv,130,0.11515661986055648
2 | objects_tox.inv,88,0.0683006333228036
3 | objects_matplotlib.inv,8953,7.64818928720864
4 | objects_attrs.inv,56,0.03558660290635487
5 | objects_sarge.inv,38,0.02755600001109997
6 | objects_celery.inv,3923,3.458657090239697
7 | objects_faker.inv,84,0.06208212610586372
8 | objects_flake8.inv,382,0.3444451205225533
9 | objects_nltk.inv,5116,5.172296742273704
10 | objects_numpy.inv,5788,4.830400875248978
11 | objects_scapy.inv,15,0.012303170722907453
12 | objects_sqlalchemy.inv,5091,4.315117286601753
13 | objects_bokeh.inv,5000,4.5330708350306566
14 | objects_sklearn.inv,3770,3.4456311832337407
15 | objects_yt.inv,17420,17.30672209541367
16 | objects_mypy.inv,86,0.06447999298280252
17 | objects_python.inv,11758,8.650611394120062
18 | objects_requests.inv,204,0.17869077736821737
19 | objects_pandas.inv,4350,3.9645195237969175
20 | objects_pingo.inv,31,0.032406772669554586
21 | objects_plone.inv,971,0.8242771353060278
22 | objects_pygame.inv,730,0.49247837856100884
23 | objects_cclib.inv,4,0.002771108091582164
24 | objects_django_channels.inv,58,0.04141235044569953
25 | objects_scrapy.inv,749,0.626478651977493
26 | objects_scipy.inv,6749,5.791793671671792
27 | objects_twython.inv,156,0.13935449598312744
28 | objects_cookiecutter.inv,159,0.11767726735197641
29 | objects_eyeD3.inv,908,0.8202465134454201
30 | objects_beaker.inv,140,0.10308000554734634
31 | objects_jsonschema.inv,78,0.06223058890839752
32 | objects_flask.inv,471,0.40689920373364713
33 | objects_pelican.inv,57,0.0405015217684138
34 | objects_pyexcel.inv,252,0.21774874285688384
35 | objects_pyserial.inv,166,0.1388799089033114
36 | objects_click.inv,217,0.18250199432067973
37 | objects_mistune.inv,36,0.02741383922237901
38 | objects_coverage.inv,151,0.12183406777806453
39 | objects_pytest.inv,466,0.3634280900709996
40 | objects_sphinx.inv,1069,0.865545171610347
41 | objects_tinydb.inv,82,0.06814130497913311
42 | objects_pyqt.inv,3285,3.0031304809246535
43 | objects_jinja2.inv,401,0.32361544360842914
44 | objects_h5py.inv,159,0.12122711865208088
45 | objects_opencv.inv,1144,0.6058922446509201
46 | objects_psutil.inv,176,0.14105603971968889
47 | objects_sympy.inv,4987,3.72812511729843
48 | objects_hypothesis.inv,149,0.09890921820654626
49 | objects_pdfminer.inv,6,0.004549224259631046
50 | objects_gspread.inv,75,0.06063720669458803
51 | objects_rocket.inv,5,0.0036870139398843095
52 | objects_pymongo.inv,773,0.6657593413032487
53 |
--------------------------------------------------------------------------------
/doc/source/_static/soi-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/soi-logo.png
--------------------------------------------------------------------------------
/doc/source/_static/soi-logo_duo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/soi-logo_duo.png
--------------------------------------------------------------------------------
/doc/source/_static/soi-logo_duo_border.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/soi-logo_duo_border.png
--------------------------------------------------------------------------------
/doc/source/_static/soi-logo_duo_border.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/soi-logo_duo_border.xcf
--------------------------------------------------------------------------------
/doc/source/_static/suggest_timing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/suggest_timing.png
--------------------------------------------------------------------------------
/doc/source/_static/suggest_timing.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/suggest_timing.xlsx
--------------------------------------------------------------------------------
/doc/source/_static/suggest_timing_commands.txt:
--------------------------------------------------------------------------------
1 | In [19]: print(*ho.get_range(711), sep='\n')
2 | (711, 1, 'import sphobjinv as soi')
3 | (711, 2, 'import os')
4 | (711, 3, 'import timeit')
5 | (711, 4, 'import csv')
6 | (711, 5, 'results = {}')
7 | (711, 6, 'lengths = {}')
8 | (711, 7, 'for fn in os.listdir():\n if fn.endswith(\'.inv\'):\n inv = soi.Inventory(fn)\n timings = timeit.repeat("inv.suggest(\'function\')", repeat=count, number=1, globals=globals())\n results.update({fn: sum(timings) / len(timings)})\n lengths.update({fn: inv.count})\n print((fn, results[fn], lengths[fn]))\n ')
9 | (711, 8, 'count = 20')
10 | (711, 9, 'for fn in os.listdir():\n if fn.endswith(\'.inv\'):\n inv = soi.Inventory(fn)\n timings = timeit.repeat("inv.suggest(\'function\')", repeat=count, number=1, globals=globals())\n results.update({fn: sum(timings) / len(timings)})\n lengths.update({fn: inv.count})\n print((fn, results[fn], lengths[fn]))\n ')
11 | (711, 10, "with open('\\\\git\\\\with-leven.csv', 'w') as f:\n csvw = csv.writer(f)\n for fn in results:\n csvw.writerow([fn, lengths[fn], results[fn]])\n ")
12 |
13 | In [20]: print(*ho.get_range(710), sep='\n')
14 | (710, 1, 'import sphobjinv as soi')
15 | (710, 2, 'results = {}')
16 | (710, 3, 'import os')
17 | (710, 4, "for fn in os.listdir():\n if fn.endswith('.inv'):\n inv = soi.Inventory(fn)\n ")
18 | (710, 5, 'import timeit')
19 | (710, 6, 'count = 20')
20 | (710, 7, 'timeit.repeat?')
21 | (710, 8, 'for fn in os.listdir():\n if fn.endswith(\'.inv\'):\n inv = soi.Inventory(fn)\n timings = timeit.repeat("inv.suggest(\'function\')", repeat=count, number=1, globals=globals())\n results.update({fn: sum(timings) / len(timings)})\n ')
22 | (710, 9, 'results')
23 | (710, 10, 'lengths = {}')
24 | (710, 11, 'results = {}')
25 | (710, 12, 'for fn in os.listdir():\n if fn.endswith(\'.inv\'):\n inv = soi.Inventory(fn)\n timings = timeit.repeat("inv.suggest(\'function\')", repeat=count, number=1, globals=globals())\n results.update({fn: sum(timings) / len(timings)})\n lengths.update({fn: inv.count})\n ')
26 | (710, 13, 'results')
27 | (710, 14, 'lengths')
28 | (710, 15, 'results = {}')
29 | (710, 16, 'lengths = {}')
30 | (710, 17, 'for fn in os.listdir():\n if fn.endswith(\'.inv\'):\n inv = soi.Inventory(fn)\n timings = timeit.repeat("inv.suggest(\'function\')", repeat=count, number=1, globals=globals())\n results.update({fn: sum(timings) / len(timings)})\n lengths.update({fn: inv.count})\n print((fn, results[fn], lengths[fn]))\n ')
31 | (710, 18, 'import csv')
32 | (710, 19, "with open('\\\\git\\\\no-leven.csv', 'w') as f:\n csvw = csv.writer(f)\n for fn in results:\n csvw.writeline([fn, lengths[fn], results[fn]])\n ")
33 | (710, 20, "with open('\\\\git\\\\no-leven.csv', 'w') as f:\n csvw = csv.writer(f)\n for fn in results:\n csvw.writerow([fn, lengths[fn], results[fn]])\n ")
--------------------------------------------------------------------------------
/doc/source/_static/with-leven.csv:
--------------------------------------------------------------------------------
1 | objects_sklearn.inv,3770,0.5595020737379116
2 | objects_scrapy.inv,749,0.10424044533704376
3 | objects_rocket.inv,5,0.0006751057019570794
4 | objects_python.inv,11758,1.5405005339919597
5 | objects_h5py.inv,159,0.020739275612154273
6 | objects_jsonschema.inv,78,0.01139018869296109
7 | objects_mypy.inv,86,0.011174557090911464
8 | objects_pyqt.inv,3285,0.4755398099597102
9 | objects_plone.inv,971,0.14140265048083123
10 | objects_yt.inv,17420,2.7540412503353435
11 | objects_pytest.inv,466,0.06242308678564825
12 | objects_django_channels.inv,58,0.007237378883788992
13 | objects_flask.inv,471,0.06570884617041059
14 | objects_opencv.inv,1144,0.12659738270845936
15 | objects_scapy.inv,15,0.0019016079815500574
16 | objects_sqlalchemy.inv,5091,0.7308581367296014
17 | objects_eyeD3.inv,908,0.13062778725823687
18 | objects_numpy.inv,5788,0.8150918019689974
19 | objects_requests.inv,204,0.02754663589018804
20 | objects_sympy.inv,4987,0.6616934360673582
21 | objects_attrs.inv,56,0.00730448841674052
22 | objects_psutil.inv,176,0.02321377418718811
23 | objects_beaker.inv,140,0.01950421918838754
24 | objects_gspread.inv,75,0.010038989512579376
25 | objects_matplotlib.inv,8953,1.3064366872822202
26 | objects_pymongo.inv,773,0.1132662132641002
27 | objects_cclib.inv,4,0.0005350785104488942
28 | objects_sarge.inv,38,0.004963023575857051
29 | objects_tox.inv,88,0.012009583802365853
30 | objects_NAPALM.inv,130,0.018531990326111015
31 | objects_pandas.inv,4350,0.6302736891514897
32 | objects_pyexcel.inv,252,0.034985007528833025
33 | objects_cookiecutter.inv,159,0.02084629131424407
34 | objects_tinydb.inv,82,0.0111470771496883
35 | objects_pdfminer.inv,6,0.0007919201465675485
36 | objects_coverage.inv,151,0.020564004556414696
37 | objects_pingo.inv,31,0.004558213025448765
38 | objects_nltk.inv,5116,0.8097258446180206
39 | objects_faker.inv,84,0.010932808679157269
40 | objects_hypothesis.inv,149,0.01897518586989655
41 | objects_pelican.inv,57,0.007269284063571746
42 | objects_mistune.inv,36,0.005061702444588789
43 | objects_twython.inv,156,0.021934998777132363
44 | objects_sphinx.inv,1069,0.14731972783202424
45 | objects_flake8.inv,382,0.05498021088632399
46 | objects_pyserial.inv,166,0.023005610175208347
47 | objects_jinja2.inv,401,0.055498121841830894
48 | objects_pygame.inv,730,0.093677302121705
49 | objects_bokeh.inv,5000,0.7286385635043465
50 | objects_scipy.inv,6749,0.9440434708568404
51 | objects_celery.inv,3923,0.5720658791585889
52 | objects_click.inv,217,0.029648288361187623
53 |
--------------------------------------------------------------------------------
/doc/source/_templates/footer.html:
--------------------------------------------------------------------------------
1 | {% extends "!footer.html" %}
2 |
3 | {%-block extrafooter %}
4 |
5 | {{ super() }}
6 |
7 |
8 |
9 |
14 |
15 |
20 |
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/doc/source/api/data.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for data.py
2 |
3 | sphobjinv.data
4 | ==============
5 |
6 | .. automodule:: sphobjinv.data
7 | :members:
--------------------------------------------------------------------------------
/doc/source/api/enum.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for enum.py
2 |
3 | sphobjinv.enum
4 | ==============
5 |
6 | .. automodule:: sphobjinv.enum
7 | :members:
8 |
--------------------------------------------------------------------------------
/doc/source/api/error.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for error.py
2 |
3 | sphobjinv.error
4 | ===============
5 |
6 | .. automodule:: sphobjinv.error
7 | :members:
--------------------------------------------------------------------------------
/doc/source/api/fileops.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for fileops.py
2 |
3 | sphobjinv.fileops
4 | =================
5 |
6 | .. automodule:: sphobjinv.fileops
7 | :members:
--------------------------------------------------------------------------------
/doc/source/api/index.rst:
--------------------------------------------------------------------------------
1 | .. API page
2 |
3 | API
4 | ===
5 |
6 | Most (all?) of the objects documented in the below submodules
7 | are also exposed at the |soi| package root. For example,
8 | both of the following will work to import the
9 | :class:`~sphobjinv.inventory.Inventory` class:
10 |
11 | .. doctest:: api_index
12 |
13 | >>> from sphobjinv import Inventory
14 | >>> from sphobjinv.inventory import Inventory
15 |
16 |
17 | .. toctree::
18 | :maxdepth: 1
19 |
20 | data
21 | enum
22 | error
23 | fileops
24 | inventory
25 | re
26 | schema
27 | zlib
28 |
--------------------------------------------------------------------------------
/doc/source/api/inventory.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for inventory.py
2 |
3 | sphobjinv.inventory
4 | ===================
5 |
6 | .. automodule:: sphobjinv.inventory
7 | :members:
8 |
9 |
--------------------------------------------------------------------------------
/doc/source/api/re.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for re.py
2 |
3 | sphobjinv.re
4 | ============
5 |
6 | .. automodule:: sphobjinv.re
7 | :members:
--------------------------------------------------------------------------------
/doc/source/api/schema.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for schema.py
2 |
3 | sphobjinv.schema
4 | ================
5 |
6 | .. automodule:: sphobjinv.schema
7 | :members:
--------------------------------------------------------------------------------
/doc/source/api/zlib.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for zlib.py
2 |
3 | sphobjinv.zlib
4 | ==============
5 |
6 | .. automodule:: sphobjinv.zlib
7 | :members:
--------------------------------------------------------------------------------
/doc/source/api_usage.rst:
--------------------------------------------------------------------------------
1 | .. API usage page
2 |
3 | API Usage
4 | =========
5 |
6 | In all of the below, the |soi| package has been imported as
7 | |cour|\ soi\ |/cour|, and the working temp directory has
8 | been populated with the |cour|\ objects_attrs.inv\ |/cour| inventory.
9 |
10 | Inspecting an Inventory
11 | -----------------------
12 |
13 | Inspecting the contents of an existing inventory is handled entirely by the
14 | :class:`~sphobjinv.inventory.Inventory` class:
15 |
16 | .. doctest:: api_inspect
17 |
18 | >>> inv = soi.Inventory('objects_attrs.inv')
19 | >>> print(inv)
20 |
21 | >>> inv.version
22 | '22.1'
23 | >>> inv.count
24 | 129
25 |
26 | The location of the inventory file to import can also be provided as
27 | a :class:`pathlib.Path`, instead of as a string:
28 |
29 | .. doctest:: api_inspect
30 |
31 | >>> soi.Inventory(Path('objects_attrs.inv')).project
32 | 'attrs'
33 |
34 | The individual objects contained in the inventory are represented by instances
35 | of the :class:`~sphobjinv.data.DataObjStr` class, which are stored in
36 | a |list| in the :attr:`~sphobjinv.inventory.Inventory.objects` attribute:
37 |
38 | .. doctest:: api_inspect
39 |
40 | >>> len(inv.objects)
41 | 129
42 | >>> dobj = inv.objects[0]
43 | >>> dobj
44 | DataObjStr(name='attr', domain='py', role='module', priority='0', uri='index.html#module-$', dispname='-')
45 | >>> dobj.name
46 | 'attr'
47 | >>> dobj.domain
48 | 'py'
49 | >>> [d.name for d in inv.objects if 'validator' in d.uri]
50 | ['api_validators', 'examples_validators']
51 |
52 | :class:`~sphobjinv.inventory.Inventory` objects can also import from plaintext or zlib-compressed
53 | inventories, as |bytes|:
54 |
55 | .. doctest:: api_inspect
56 |
57 | >>> inv2 = soi.Inventory(inv.data_file())
58 | >>> print(inv2)
59 |
60 | >>> inv3 = soi.Inventory(soi.compress(inv.data_file()))
61 | >>> print(inv3)
62 |
63 |
64 | Remote |objects.inv| files can also be retrieved via URL, with the *url* keyword argument:
65 |
66 | .. doctest:: api_inspect
67 |
68 | >>> inv4 = soi.Inventory(url='https://github.com/bskinn/sphobjinv/raw/main/tests/resource/objects_attrs.inv')
69 | >>> print(inv4)
70 |
71 |
72 | Comparing Inventories
73 | ---------------------
74 |
75 | |Inventory| instances compare equal when they have the same :attr:`~sphobjinv.inventory.Inventory.project` and
76 | :attr:`~sphobjinv.inventory.Inventory.version`, and when all the members of
77 | :attr:`~sphobjinv.inventory.Inventory.objects` are identical between the two instances:
78 |
79 | .. doctest:: api_compare
80 |
81 | >>> inv = soi.Inventory("objects_attrs.inv")
82 | >>> inv2 = soi.Inventory(inv.data_file())
83 | >>> inv is inv2
84 | False
85 | >>> inv == inv2
86 | True
87 | >>> inv2.project = "foo"
88 | >>> inv == inv2
89 | False
90 |
91 | Individual |DataObjStr| and (|DataObjBytes|) instances compare equal if all of
92 | :attr:`~sphobjinv.data.SuperDataObj.name`, :attr:`~sphobjinv.data.SuperDataObj.domain`,
93 | :attr:`~sphobjinv.data.SuperDataObj.role`, :attr:`~sphobjinv.data.SuperDataObj.priority`,
94 | :attr:`~sphobjinv.data.SuperDataObj.uri`, and :attr:`~sphobjinv.data.SuperDataObj.dispname`
95 | are equal:
96 |
97 | .. doctest:: api_compare
98 |
99 | >>> obj1 = inv.objects[0]
100 | >>> obj2 = inv.objects[1]
101 | >>> obj1 == obj1
102 | True
103 | >>> obj1 == obj2
104 | False
105 | >>> obj1 == obj1.evolve(name="foo")
106 | False
107 |
108 | .. versionchanged:: 2.1
109 | Previously, |Inventory| instances would only compare equal to themselves,
110 | and comparison attempts on |SuperDataObj| subclass instances would raise :exc:`RecursionError`.
111 |
112 | Modifying an Inventory
113 | ----------------------
114 |
115 | The :class:`~sphobjinv.data.DataObjStr` instances can be edited in place:
116 |
117 | .. doctest:: api_modify
118 |
119 | >>> inv = soi.Inventory('objects_attrs.inv')
120 | >>> inv.objects[0]
121 | DataObjStr(name='attr', domain='py', role='module', priority='0', uri='index.html#module-$', dispname='-')
122 | >>> inv.objects[0].uri = 'attribute.html'
123 | >>> inv.objects[0]
124 | DataObjStr(name='attr', domain='py', role='module', priority='0', uri='attribute.html', dispname='-')
125 |
126 | New instances can be easily created either by direct instantiation, or by
127 | :meth:`~sphobjinv.data.SuperDataObj.evolve`:
128 |
129 | .. doctest:: api_modify
130 |
131 | >>> inv.objects.append(inv.objects[0].evolve(name='attr.Generator', uri='generator.html'))
132 | >>> inv.count
133 | 130
134 | >>> inv.objects[-1]
135 | DataObjStr(name='attr.Generator', domain='py', role='module', priority='0', uri='generator.html', dispname='-')
136 |
137 | The other attributes of the :class:`~sphobjinv.inventory.Inventory` instance can also be freely modified:
138 |
139 | .. doctest:: api_modify
140 |
141 | >>> inv.project = 'not_attrs'
142 | >>> inv.version = '0.1'
143 | >>> print(inv)
144 |
145 |
146 |
147 | Formatting Inventory Contents
148 | -----------------------------
149 |
150 | The contents of the :class:`~sphobjinv.inventory.Inventory` can be converted to
151 | the plaintext |objects.inv| format **as** |bytes| via :meth:`~sphobjinv.inventory.Inventory.data_file`:
152 |
153 | .. doctest:: api_formatting
154 |
155 | >>> inv = soi.Inventory('objects_attrs.inv')
156 | >>> print(*inv.data_file().splitlines()[:6], sep='\n')
157 | b'# Sphinx inventory version 2'
158 | b'# Project: attrs'
159 | b'# Version: 22.1'
160 | b'# The remainder of this file is compressed using zlib.'
161 | b'attr py:module 0 index.html#module-$ -'
162 | b'attr.VersionInfo py:class 1 api.html#$ -'
163 |
164 | This method makes use of the :meth:`DataObjStr.data_line `
165 | method to format each of the object information lines.
166 |
167 | If desired, the :ref:`shorthand ` used for the
168 | :attr:`~sphobjinv.data.SuperDataObj.uri` and
169 | :attr:`~sphobjinv.data.SuperDataObj.dispname` fields can be expanded:
170 |
171 | .. doctest:: api_formatting
172 |
173 | >>> print(*inv.data_file(expand=True).splitlines()[4:6], sep='\n')
174 | b'attr py:module 0 index.html#module-attr attr'
175 | b'attr.VersionInfo py:class 1 api.html#attr.VersionInfo attr.VersionInfo'
176 | >>> do = inv.objects[0]
177 | >>> do.data_line(expand=True)
178 | 'attr py:module 0 index.html#module-attr attr'
179 |
180 |
181 | Exporting an Inventory
182 | ----------------------
183 |
184 | :class:`~sphobjinv.inventory.Inventory` instances can be written to disk
185 | in three formats: zlib-compressed |objects.inv|,
186 | plaintext |objects.txt|, and JSON. The API does not provide single-function
187 | means to do this, however.
188 |
189 | To start, load the source |objects.inv|:
190 |
191 | .. doctest:: api_exporting
192 |
193 | >>> from pathlib import Path
194 | >>> inv = soi.Inventory('objects_attrs.inv')
195 |
196 | To export plaintext:
197 |
198 | .. doctest:: api_exporting
199 |
200 | >>> df = inv.data_file()
201 | >>> soi.writebytes('objects_attrs.txt', df)
202 | >>> print(*Path('objects_attrs.txt').read_text().splitlines()[:6], sep='\n')
203 | # Sphinx inventory version 2
204 | # Project: attrs
205 | # Version: 22.1
206 | # The remainder of this file is compressed using zlib.
207 | attr py:module 0 index.html#module-$ -
208 | attr.VersionInfo py:class 1 api.html#$ -
209 |
210 | For zlib-compressed:
211 |
212 | .. doctest:: api_exporting
213 |
214 | >>> dfc = soi.compress(df)
215 | >>> soi.writebytes('objects_attrs_new.inv', dfc)
216 | >>> print(*Path('objects_attrs_new.inv').read_bytes().splitlines()[:4], sep='\n')
217 | b'# Sphinx inventory version 2'
218 | b'# Project: attrs'
219 | b'# Version: 22.1'
220 | b'# The remainder of this file is compressed using zlib.'
221 | >>> print(Path('objects_attrs_new.inv').read_bytes().splitlines()[6][:10])
222 | b'\xbf\x86\x8fL49\xc4\x91\xb8\x8c'
223 |
224 | For JSON:
225 |
226 | .. doctest:: api_exporting
227 |
228 | >>> jd = inv.json_dict()
229 | >>> soi.writejson('objects_attrs.json', jd)
230 | >>> print(Path('objects_attrs.json').read_text()[:51]) # doctest: +SKIP
231 | {"project": "attrs", "version": "17.2", "count": 56
232 |
--------------------------------------------------------------------------------
/doc/source/cli/convert.rst:
--------------------------------------------------------------------------------
1 | .. Description of convert commandline usage
2 |
3 | Command-Line Usage: "convert" Subcommand
4 | ========================================
5 |
6 | .. program:: sphobjinv convert
7 |
8 | The |cour|\ convert\ |/cour| subcommand is used for all conversions of
9 | "version 2" Sphinx inventory
10 | files among plaintext, zlib-compressed, and (unique to |soi|) JSON formats.
11 | The |soi| CLI can read and write inventory data from local files
12 | in any of these three formats, as well as read the standard zlib-compressed format
13 | from files in remote locations (see :option:`--url`).
14 |
15 | As of v2.1, the |soi| CLI can also read/write inventories at ``stdin``/``stdout``
16 | in the plaintext and JSON formats; see :ref:`below `.
17 |
18 | ----
19 |
20 | Basic file conversion to the default output filename is straightforward:
21 |
22 | .. doctest:: convert_main
23 |
24 | >>> Path('objects_attrs.txt').is_file()
25 | False
26 | >>> cli_run('sphobjinv convert plain objects_attrs.inv')
27 |
28 | Conversion completed.
29 | '...objects_attrs.inv' converted to '...objects_attrs.txt' (plain).
30 |
31 |
32 | >>> print(file_head('objects_attrs.txt', head=6))
33 | # Sphinx inventory version 2
34 | # Project: attrs
35 | # Version: 22.1
36 | # The remainder of this file is compressed using zlib.
37 | attr py:module 0 index.html#module-$ -
38 | attr.VersionInfo py:class 1 api.html#$ -
39 |
40 | A different target filename can be specified, to avoid overwriting an existing
41 | file:
42 |
43 | .. doctest:: convert_main
44 |
45 | >>> cli_run('sphobjinv convert plain objects_attrs.inv', inp='n\n')
46 | File exists. Overwrite (Y/N)? n
47 |
48 |
49 | Exiting...
50 |
51 | >>> cli_run('sphobjinv convert plain objects_attrs.inv objects_attrs_foo.txt')
52 |
53 | Conversion completed.
54 | '...objects_attrs.inv' converted to '...objects_attrs_foo.txt' (plain).
55 |
56 |
57 |
58 | If you don't provide an output file extension, the |soi| defaults
59 | (`.inv`/`.txt`/`.json`) will be used.
60 |
61 | If you want to pull an input file directly from the internet, use
62 | :option:`--url` (note that the base filename is **not** inferred from the
63 | indicated URL):
64 |
65 | .. doctest:: convert_url
66 |
67 | >>> cli_run('sphobjinv convert plain -u https://github.com/bskinn/sphobjinv/raw/main/tests/resource/objects_attrs.inv')
68 |
69 | Attempting https://github.com/bskinn/sphobjinv/raw/main/tests/resource/objects_attrs.inv ...
70 | ... inventory found.
71 |
72 | Conversion completed.
73 | 'https://github.com/b[...]ce/objects_attrs.inv' converted to '...objects.txt' (plain).
74 |
75 |
76 | >>> print(file_head('objects.txt', head=6))
77 | # Sphinx inventory version 2
78 | # Project: attrs
79 | # Version: 22.1
80 | # The remainder of this file is compressed using zlib.
81 | attr py:module 0 index.html#module-$ -
82 | attr.VersionInfo py:class 1 api.html#$ -
83 |
84 | The URL provided **MUST** have the leading protocol specified (here,
85 | |cour|\ https\ ://\ |/cour|).
86 |
87 | It is not necessary to locate the |objects.inv| file before running |soi|;
88 | for most Sphinx documentation sets, if you provide a URL to any page in the docs,
89 | it will automatically find and use the correct |objects.inv|:
90 |
91 | .. doctest:: convert_url
92 |
93 | >>> cli_run('sphobjinv convert plain -ou https://docs.python.org/3/library/urllib.error.html#urllib.error.URLError')
94 |
95 | Attempting https://docs.python.org/3/library/urllib.error.html#urllib.error.URLError ...
96 | ... no recognized inventory.
97 | Attempting "https://docs.python.org/3/library/urllib.error.html/objects.inv" ...
98 | ... HTTP error: 404 Not Found.
99 | Attempting "https://docs.python.org/3/library/objects.inv" ...
100 | ... HTTP error: 404 Not Found.
101 | Attempting "https://docs.python.org/3/objects.inv" ...
102 | ... inventory found.
103 |
104 | Conversion completed.
105 | '...objects.inv' converted to '...objects.txt' (plain).
106 |
107 |
108 |
109 | |soi| only supports download of zlib-compressed |objects.inv| files by URL.
110 | Plaintext download by URL is unreliable, presumably due to encoding problems.
111 | If processing of JSON files by API URL is desirable, please
112 | `submit an issue `__.
113 |
114 | .. versionadded:: 2.1
115 | The URL at which a remote inventory is found is now included
116 | in JSON output:
117 |
118 | .. doctest:: json-url
119 |
120 | >>> cli_run('sphobjinv convert json -qu https://docs.python.org/3/ objects.json')
121 |
122 | >>> data = json.loads(Path('objects.json').read_text())
123 | >>> data["metadata"]["url"]
124 | 'https://docs.python.org/3/objects.inv'
125 |
126 | .. _cli_usage_json_added:
127 |
128 | .. versionadded:: 2.1
129 | JSON and plaintext inventories can now be read from ``stdin`` and
130 | written to ``stdout``, by using the special value ``-`` in the invocation.
131 | E.g., to print to ``stdout``:
132 |
133 | .. doctest:: stdio
134 |
135 | >>> cli_run('sphobjinv co plain objects_attrs.inv -')
136 | # Sphinx inventory version 2
137 | # Project: attrs
138 | # Version: 22.1
139 | # The remainder of this file is compressed using zlib.
140 | attr py:module 0 index.html#module-$ -
141 | attr.VersionInfo py:class 1 api.html#$ -
142 | attr._make.Attribute py:class -1 api.html#attrs.Attribute -
143 | ...
144 |
145 |
146 | **Usage**
147 |
148 | .. command-output:: sphobjinv convert --help
149 | :ellipsis: 4
150 |
151 |
152 | **Positional Arguments**
153 |
154 | .. option:: mode
155 |
156 | Conversion output format.
157 |
158 | Must be one of `plain`, `zlib`, or `json`
159 |
160 | .. option:: infile
161 |
162 | Path (or URL, if :option:`--url` is specified) to file to be converted.
163 |
164 | If passed as ``-``, |soi| will attempt import of a plaintext or JSON
165 | inventory from ``stdin`` (incompatible with :option:`--url`).
166 |
167 | .. option:: outfile
168 |
169 | *(Optional)* Path to desired output file. Defaults to same directory
170 | and main file name as input file but with extension
171 | |cour|\ .inv/.txt/.json\ |/cour|, as appropriate for the output format.
172 |
173 | A bare path is accepted here, using the default output
174 | file name/extension.
175 |
176 | If passed as ``-``, or if omitted when `infile` is passed as ``-``,
177 | |soi| will emit plaintext or JSON (but *not*
178 | zlib-compressed) inventory contents to ``stdout``.
179 |
180 | **Flags**
181 |
182 | .. option:: -h, --help
183 |
184 | Display `convert` help message and exit.
185 |
186 | .. option:: -o, --overwrite
187 |
188 | If the output file already exists, overwrite without prompting
189 | for confirmation.
190 |
191 | .. option:: -q, --quiet
192 |
193 | Suppress all status message output, regardless of success or failure.
194 | Useful for scripting/automation. Implies :option:`--overwrite`.
195 |
196 | .. option:: -u, --url
197 |
198 | Treat :option:`infile` as a URL for download. Cannot be used when
199 | :option:`infile` is passed as ``-``.
200 |
201 | .. option:: -e, --expand
202 |
203 | Expand any abbreviations in `uri` or `dispname` fields before writing to output;
204 | see :ref:`here `. Cannot be specified with
205 | :option:`--contract`.
206 |
207 | .. option:: -c, --contract
208 |
209 | Contract `uri` and `dispname` fields, if possible, before writing to output;
210 | see :ref:`here `. Cannot be specified with
211 | :option:`--expand`.
212 |
--------------------------------------------------------------------------------
/doc/source/cli/implementation/convert.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for cli/convert.py
2 |
3 | sphobjinv.cli.convert
4 | =====================
5 |
6 | .. automodule:: sphobjinv.cli.convert
7 | :members:
8 |
--------------------------------------------------------------------------------
/doc/source/cli/implementation/core.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for cli/core.py
2 |
3 | sphobjinv.cli.core
4 | ==================
5 |
6 | .. automodule:: sphobjinv.cli.core
7 | :members:
8 |
9 |
--------------------------------------------------------------------------------
/doc/source/cli/implementation/index.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for CLI submodule code
2 |
3 | sphobjinv.cli (non-API)
4 | =======================
5 |
6 | .. toctree::
7 | :maxdepth: 1
8 |
9 | convert
10 | core
11 | load
12 | parser
13 | paths
14 | suggest
15 | ui
16 | write
17 |
18 |
19 | .. .. |argparse| replace:: :mod:`argparse`
20 |
--------------------------------------------------------------------------------
/doc/source/cli/implementation/load.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for cli/load.py
2 |
3 | sphobjinv.cli.load
4 | ==================
5 |
6 | .. automodule:: sphobjinv.cli.load
7 | :members:
8 |
9 |
--------------------------------------------------------------------------------
/doc/source/cli/implementation/parser.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for cli/parser.py
2 |
3 | sphobjinv.cli.parser
4 | ====================
5 |
6 | .. automodule:: sphobjinv.cli.parser
7 | :members:
8 |
9 |
--------------------------------------------------------------------------------
/doc/source/cli/implementation/paths.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for cli/paths.py
2 |
3 | sphobjinv.cli.paths
4 | ===================
5 |
6 | .. automodule:: sphobjinv.cli.paths
7 | :members:
8 |
9 |
--------------------------------------------------------------------------------
/doc/source/cli/implementation/suggest.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for cli/suggest.py
2 |
3 | sphobjinv.cli.suggest
4 | =====================
5 |
6 | .. automodule:: sphobjinv.cli.suggest
7 | :members:
8 |
--------------------------------------------------------------------------------
/doc/source/cli/implementation/ui.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for cli/ui.py
2 |
3 | sphobjinv.cli.ui
4 | ================
5 |
6 | .. automodule:: sphobjinv.cli.ui
7 | :members:
8 |
9 |
--------------------------------------------------------------------------------
/doc/source/cli/implementation/write.rst:
--------------------------------------------------------------------------------
1 | .. Module API page for cli/write.py
2 |
3 | sphobjinv.cli.write
4 | ===================
5 |
6 | .. automodule:: sphobjinv.cli.write
7 | :members:
8 |
9 |
--------------------------------------------------------------------------------
/doc/source/cli/index.rst:
--------------------------------------------------------------------------------
1 | .. Description of commandline usage
2 |
3 | Command-Line Usage
4 | ==================
5 |
6 | The CLI for |soi| is implemented using two subcommands:
7 |
8 | - A :doc:`convert ` subcommand, which handles conversion of
9 | inventories between supported formats (currently zlib-compressed,
10 | plaintext, and JSON).
11 | - A :doc:`suggest ` subcommand, which provides suggestions for
12 | objects in an inventory matching a desired search term.
13 |
14 | More information about the underlying implementation of these subcommands can
15 | be found :doc:`here ` and in the documentation for the
16 | :class:`~sphobjinv.inventory.Inventory` object, in particular the
17 | :meth:`~sphobjinv.inventory.Inventory.data_file` and
18 | :meth:`~sphobjinv.inventory.Inventory.suggest` methods.
19 |
20 | Some notes on these CLI docs:
21 |
22 | * CLI docs examples are executed in a sandboxed directory pre-loaded with
23 | |cour|\ objects_attrs.inv\ |/cour| (from, e.g.,
24 | `here `__).
26 |
27 | * :class:`~pathlib.Path` (from :mod:`pathlib`)
28 | is imported into the namespace before all tests.
29 |
30 | * |cour|\ cli_run\ |/cour| is a helper function that enables doctesting
31 | of CLI examples by mimicking execution of a shell command.
32 | It is described in more detail
33 | `here `__.
34 |
35 | * |cour|\ file_head\ |/cour| is a helper function
36 | that retrieves the head of a specified file.
37 |
38 |
39 | .. program:: sphobjinv
40 |
41 | The options for the parent |soi| command are:
42 |
43 | .. option:: -h, --help
44 |
45 | Show help message and exit
46 |
47 | .. program-output:: sphobjinv --help
48 |
49 |
50 | .. option:: -v, --version
51 |
52 | Print package version & other info
53 |
54 | .. program-output:: sphobjinv --version
55 |
56 |
57 | .. toctree::
58 | :maxdepth: 1
59 | :hidden:
60 |
61 | "convert" Mode
62 | "suggest" Mode
63 |
--------------------------------------------------------------------------------
/doc/source/cli/suggest.rst:
--------------------------------------------------------------------------------
1 | .. Description of suggest commandline usage
2 |
3 | Command-Line Usage: "suggest" Subcommand
4 | ========================================
5 |
6 | .. program:: sphobjinv suggest
7 |
8 | The |cour|\ suggest\ |/cour| subcommand is used to query an inventory for objects
9 | fuzzy-matching a given search string. Fuzzy-matching is carried out via the
10 | |fuzzywuzzy|_ library, against the Restructured Text-like representation of each
11 | object exposed by :attr:`SuperDataObj.as_rst `:
12 |
13 | .. command-output:: sphobjinv suggest objects_attrs.inv instance
14 | :cwd: /../../tests/resource
15 |
16 | The |fuzzywuzzy|_ match score and the index of the object within the inventory can
17 | be printed by passing the :option:`--score` and :option:`--index` options,
18 | respectively:
19 |
20 | .. command-output:: sphobjinv suggest objects_attrs.inv instance -s -i
21 | :cwd: /../../tests/resource
22 |
23 | If too few or too many matches are returned, the reporting threshold can be changed
24 | via :option:`--thresh`:
25 |
26 | .. command-output:: sphobjinv suggest objects_attrs.inv instance -s -i -t 48
27 | :cwd: /../../tests/resource
28 |
29 | Remote |objects.inv| files can be retrieved for inspection by passing the
30 | :option:`--url` flag:
31 |
32 | .. command-output:: sphobjinv suggest https://github.com/bskinn/sphobjinv/raw/main/tests/resource/objects_attrs.inv instance -u -t 48
33 | :cwd: /../../tests/resource
34 |
35 | The URL provided **MUST** have the leading protocol specified (here,
36 | |cour|\ https\ ://\ |/cour|).
37 |
38 | It is usually not necessary to locate the |objects.inv| file before running |soi|;
39 | for most Sphinx documentation sets, if you provide a URL to any page in the docs,
40 | it will automatically find and use the correct |objects.inv|:
41 |
42 | .. command-output:: sphobjinv suggest -u https://sphobjinv.readthedocs.io/en/stable/cli/convert.html compress
43 | :cwd: /../../tests/resource
44 |
45 | |soi| only supports download of zlib-compressed |objects.inv| files by URL.
46 | Plaintext download by URL is unreliable, presumably due to encoding problems.
47 | If download of JSON files by URL is desirable, please
48 | `submit an issue `__.
49 |
50 | .. versionadded:: 2.1
51 | The |soi| CLI can now read JSON and plaintext inventories from ``stdin``
52 | by passing the special ``-`` argument for `infile`:
53 |
54 | .. command-output:: sphobjinv suggest -s - valid < objects_attrs.txt
55 | :cwd: /../../tests/resource
56 | :shell:
57 |
58 | **Usage**
59 |
60 | .. command-output:: sphobjinv suggest --help
61 | :ellipsis: 4
62 |
63 | **Positional Arguments**
64 |
65 | .. option:: infile
66 |
67 | Path (or URL, if :option:`--url` is specified) to file to be searched.
68 |
69 | If passed as ``-``, |soi| will attempt import of a plaintext or JSON
70 | inventory from ``stdin``. This is incompatible with :option:`--url`,
71 | and automatically enables :option:`--all`.
72 |
73 | .. option:: search
74 |
75 | Search term for |fuzzywuzzy|_ matching.
76 |
77 | **Flags**
78 |
79 | .. option:: -h, --help
80 |
81 | Display `suggest` help message and exit.
82 |
83 | .. option:: -a, --all
84 |
85 | Display all search results without prompting, regardless of the number of hits.
86 | Otherwise, prompt if number of results exceeds
87 | :attr:`~sphobjinv.cli.parser.PrsConst.SUGGEST_CONFIRM_LENGTH`.
88 |
89 | .. option:: -i, --index
90 |
91 | Display the index position within the
92 | :attr:`Inventory.objects ` list
93 | for each search result returned.
94 |
95 | .. option:: -s, --score
96 |
97 | Display the |fuzzywuzzy|_ match score for each search result returned.
98 |
99 | .. option:: -t, --thresh <#>
100 |
101 | Change the |fuzzywuzzy|_ match quality threshold (0-100; higher values
102 | yield fewer results). Default is specified in
103 | :attr:`~sphobjinv.cli.parser.PrsConst.DEF_THRESH`.
104 |
105 | .. option:: -u, --url
106 |
107 | Treat :option:`infile` as a URL for download. Cannot be used when
108 | :option:`infile` is passed as ``-``.
109 |
--------------------------------------------------------------------------------
/doc/source/customfile.rst:
--------------------------------------------------------------------------------
1 | .. Instructions for creating and using a custom objects.inv file
2 |
3 |
4 | Creating and Using a Custom objects.inv
5 | =======================================
6 |
7 | The workflow presented here is introduced in the context of manually
8 | assembling an objects inventory, but the functionality is mainly
9 | intended for use downstream of a web-scraping or other automated
10 | content-extraction tool.
11 |
12 | A (possibly obsolete) representative example of such a custom |objects.inv|
13 | can be found at the GitHub repo
14 | `here `__.
15 |
16 | .. note::
17 |
18 | These instructions are for |soi| v2.x;
19 | the prior instructions for v1.0 can be found
20 | `here `__.
21 |
22 | #. Identify the head of the URI to the documentation. |br| |br|
23 |
24 |
25 | #. Construct an |Inventory| containing all of the objects of interest.
26 | The :attr:`~sphobjinv.data.SuperDataObj.uri` and
27 | :attr:`~sphobjinv.data.SuperDataObj.dispname` values
28 | can be entered with or without the
29 | :ref:`standard abbreviations `.
30 |
31 | * Create an empty |Inventory|:
32 |
33 | .. doctest:: customfile
34 |
35 | >>> import sphobjinv as soi
36 | >>> inv = soi.Inventory()
37 | >>> print(inv)
38 | , 0 objects>
39 |
40 | * Define the :attr:`~sphobjinv.inventory.Inventory.project`
41 | and :attr:`~sphobjinv.inventory.Inventory.version` attributes:
42 |
43 | .. doctest:: customfile
44 |
45 | >>> inv.project = 'foobar'
46 | >>> inv.version = '1.5'
47 | >>> print(inv)
48 |
49 |
50 | * Append new :class:`~sphobjinv.data.DataObjStr` instances to
51 | :attr:`~sphobjinv.inventory.Inventory.objects` as needed
52 | to populate the inventory:
53 |
54 | .. doctest:: customfile
55 |
56 | >>> o = soi.DataObjStr(name='baz', domain='py', role='class',
57 | ... priority='1', uri='api.html#$', dispname='-')
58 | >>> print(o)
59 |
60 | >>> inv.objects.append(o)
61 | >>> print(inv)
62 |
63 | >>> inv.objects.append(soi.DataObjStr(name='baz.quux', domain='py',
64 | ... role='method', priority='1', uri='api.html#$', dispname='-'))
65 | >>> inv.objects.append(soi.DataObjStr(name='quuux', domain='py',
66 | ... role='function', priority='1', uri='api.html#$', dispname='-'))
67 | >>> print(inv)
68 |
69 |
70 | .. note::
71 |
72 | The `role` values here must be the **full** role names ("`block directives`"),
73 | described as the "directives" in the `Sphinx documentation for
74 | domains `__,
75 | and not the abbreviated forms ("`inline directives`")
76 | `used when constructing cross-references
77 | `__.
78 |
79 | Thus, for example, a :class:`~sphobjinv.data.DataObjStr` corresponding
80 | to a method on a class should be constructed with
81 | |cour|\ role='method'\ |/cour|, not |cour|\ role='meth'\ |/cour|.
82 |
83 |
84 |
85 | #. Export the |Inventory| in compressed form.
86 |
87 | * Generate the text of the inventory file
88 | with :meth:`~sphobjinv.inventory.Inventory.data_file`,
89 | optionally :ref:`contracting ` the
90 | :attr:`~sphobjinv.data.SuperDataObj.uri` and
91 | :attr:`~sphobjinv.data.SuperDataObj.dispname` fields:
92 |
93 | .. doctest:: customfile
94 |
95 | >>> text = inv.data_file(contract=True)
96 |
97 | * Compress the file text:
98 |
99 | .. doctest:: customfile
100 |
101 | >>> ztext = soi.compress(text)
102 |
103 | * Save to disk:
104 |
105 | .. doctest:: customfile
106 |
107 | >>> soi.writebytes('objects_foobar.inv', ztext)
108 |
109 |
110 | #. Transfer the compressed file to its distribution location.
111 |
112 | * If only local access is needed, it can be kept local.
113 |
114 | * If external access needed, upload to a suitable host. |br|
115 |
116 | #. Add an element to the |isphxmap|_ parameter in ``conf.py``.
117 |
118 | * The key of the element is an arbitrary name, which can be used
119 | to specify the desired documentation set to be searched
120 | for the target object, in the event of a `name` collision
121 | between one or more documentation projects; e.g.::
122 |
123 | :meth:`python:str.join`
124 |
125 | * The value of the element is a |tuple| of length two:
126 |
127 | * The first element of the value tuple is the head URI for the
128 | documentation repository,
129 | identified in step (1),
130 | to which the
131 | :attr:`~sphobjinv.data.SuperDataObj.uri` of given object
132 | is appended when constructing an |isphx| cross-reference.
133 |
134 | * The second element of the value tuple can be |None|, in which case
135 | the |objects.inv| file is assumed to be at the repository head URI.
136 | Otherwise, this element is the complete address of the
137 | distribution location of the compressed inventory file,
138 | from step (4), whether a local path or a remote URL.
139 |
140 | Examples:
141 |
142 | .. code::
143 |
144 | intersphinx_mapping = {
145 | # Standard reference to web docs, with web objects.inv
146 | 'python': ('https://docs.python.org/3.12', None),
147 |
148 | # Django puts its objects.inv file in a non-standard location
149 | 'django': ('https://docs.djangoproject.com/en/dev/', 'https://docs.djangoproject.com/en/dev/_objects/'),
150 |
151 | # Drawing the Sphinx objects.inv from a local copy, but referring to the current web docs
152 | 'sphinx': ('https://www.sphinx-doc.org/en/master/', '/path/to/local/objects.inv'),
153 | }
154 |
155 | .. MAKE SURE TO UPDATE THESE TWO STEP REFERENCES IF NUMBERING CHANGES!!
156 |
--------------------------------------------------------------------------------
/doc/source/index.rst:
--------------------------------------------------------------------------------
1 | .. Sphinx Objects.inv Converter documentation master file, created by
2 | sphinx-quickstart on Wed May 18 22:42:29 2016.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to sphobjinv!
7 | =====================
8 |
9 | *A toolkit for inspection/manipulation of Sphinx objects inventories*
10 |
11 |
12 | When documentation is built using, e.g., Sphinx's :obj:`~sphinx.builders.html.StandaloneHTMLBuilder`,
13 | an inventory of the named objects in the documentation set `is dumped
14 | `__
15 | to a file called |objects.inv| in the html build directory.
16 | (One common location is, |cour|\ doc/build/html\ |/cour|, though the exact location will vary
17 | depending on the details of how Sphinx is configured.) This file is read by |isphx| when
18 | generating links in other documentation.
19 |
20 | Since version 1.0 of Sphinx (~July 2010), the data in these |objects.inv| inventories is compressed by
21 | :mod:`zlib` (presumably to reduce storage requirements and improve download speeds; "version 2"),
22 | whereas prior to that date the data was left uncompressed ("version 1"). This compression renders
23 | the files non-human-readable. **It is the purpose of this package to enable quick and simple
24 | compression/decompression and inspection of these "version 2" inventory files.**
25 |
26 | In particular, |soi| was developed to satisfy two primary use cases:
27 |
28 | #. Searching and inspection of |objects.inv| contents in order to identify
29 | how to properly construct |isphx| references. |br| |br|
30 |
31 | #. Assembly of new |objects.inv| files in order to allow |isphx| cross-referencing
32 | of other documentation sets that were not created by Sphinx.
33 |
34 | For more background on the mechanics of the Sphinx data model and
35 | Sphinx cross-references generally, see
36 | `this talk `__ from PyOhio 2019.
37 |
38 | ----
39 |
40 | Install |soi| via |cour|\ pip\ |/cour|::
41 |
42 | $ pip install sphobjinv
43 |
44 | Or, if you only plan to use the |soi| CLI, another option is |pipx|_::
45 |
46 | $ pipx install sphobjinv
47 |
48 | As of Nov 2022, |soi| is also available via conda-forge. After activating the desired conda environment::
49 |
50 | $ conda install -c conda-forge sphobjinv
51 |
52 | Alternatively, |soi| is packaged with
53 | `multiple POSIX distributions `__
54 | and package managers, including:
55 |
56 | * Alpine Linux: ``py3-sphobjinv`` (`info `__)
57 |
58 | * Arch Linux: ``python-sphobjinv``
59 |
60 | * Fedora: ``python-sphobjinv`` (`info `__)
61 |
62 | * Gentoo: ``dev-python/sphobjinv`` (`info `__)
63 |
64 | * Guix: ``python-sphobjinv``
65 |
66 | * Manjaro: ``python-sphobjinv``
67 |
68 | * OpenEuler: ``python-sphobjinv``
69 |
70 | * openSUSE: ``python-sphobjinv`` (`info `__)
71 |
72 | * Parabola: ``python-sphobjinv`` (`info `__)
73 |
74 | * pkgsrc: ``textproc/py-sphobjinv`` (`info `__)
75 |
76 | * spack: ``py-sphobjinv``
77 |
78 |
79 | |soi| is configured for use both as a
80 | :doc:`command-line script ` and as a
81 | :doc:`Python package `.
82 |
83 | The optional dependency |python-Levenshtein|_ for accelerating
84 | the "suggest" functionality is no longer available due to a
85 | licensing conflict, and has been deprecated. See
86 | :doc:`here ` for more information.
87 |
88 | The project source repository is on GitHub: `bskinn/sphobjinv
89 | `__.
90 |
91 |
92 |
93 | .. toctree::
94 | :maxdepth: 1
95 | :hidden:
96 |
97 | cli/index
98 | api_usage
99 | customfile
100 | levenshtein
101 | syntax
102 | api/index
103 | CLI Implementation (non-API)
104 |
105 |
106 |
107 | Indices and Tables
108 | ------------------
109 |
110 | :ref:`genindex` --- :ref:`modindex` --- :ref:`search`
111 |
--------------------------------------------------------------------------------
/doc/source/isphx/objpull.py:
--------------------------------------------------------------------------------
1 | # Quickie script for refreshing the local objects.inv cache
2 | # OVERWRITES EXISTING FILES, WITH PRE-DELETION
3 |
4 |
5 |
6 | def pullobjs():
7 |
8 | import os
9 | import urllib.request as urlrq
10 |
11 | import certifi
12 |
13 | # Open conf.py, retrieve content and compile
14 | with open(os.path.join(os.pardir, 'conf.py'), 'r') as f:
15 | confcode = compile(f.read(), 'conf.py', 'exec')
16 |
17 | # Execute conf.py into the global namespace (I know, sloppy)
18 | exec(confcode, globals())
19 |
20 | # Iterate intersphinx_mapping from conf.py to retrieve the objects.inv files
21 | # Make use of the conf.py 'isphx_objstr' substitution string, too
22 | for n, t in intersphinx_mapping.items():
23 |
24 | print('{0}:\n'.format(n) + '-' * 16)
25 |
26 | try:
27 | os.remove(isphx_objstr.format(n))
28 | except FileNotFoundError:
29 | pass # No big deal
30 |
31 | try:
32 | resp = urlrq.urlopen(t[0] + '/objects.inv', cafile=certifi.where())
33 | except Exception as e:
34 | print('HTTP request failed:\n' + str(e) + '\n')
35 | continue
36 | else:
37 | print('... located ...')
38 |
39 | try:
40 | b_s = resp.read()
41 | except Exception as e:
42 | print('Download failed:\n' + str(e) + '\n')
43 | continue
44 | else:
45 | print('... downloaded ...')
46 |
47 | try:
48 | with open(isphx_objstr.format(n), 'wb') as f:
49 | f.write(b_s)
50 | except Exception as e:
51 | print('Write failed:\n' + str(e) + '\n')
52 | continue
53 | else:
54 | print('... done.')
55 |
56 | print('')
57 |
58 |
59 | if __name__ == '__main__':
60 |
61 | pullobjs()
62 |
--------------------------------------------------------------------------------
/doc/source/levenshtein.rst:
--------------------------------------------------------------------------------
1 | .. Info on speedups from python-Levenshtein
2 |
3 | Speeding up "suggest" with python-Levenshtein (DEPRECATED)
4 | ==========================================================
5 |
6 | |soi| uses |fuzzywuzzy|_ for fuzzy-match searching of object
7 | names/domains/roles as part of the
8 | :meth:`Inventory.suggest() ` functionality,
9 | also implemented as the CLI :doc:`suggest ` subcommand.
10 |
11 | |fuzzywuzzy|_ uses :class:`difflib.SequenceMatcher`
12 | from the Python standard library for its fuzzy searching.
13 | While earlier versions of |soi| were able to make use of
14 | |fuzzywuzzy|_\ 's optional link to |python-Levenshtein|_,
15 | a Python C extension providing similar functionality,
16 | due to a licensing conflict this is no longer possible.
17 | |soi| now uses a vendored copy of |fuzzywuzzy|_ from an
18 | era when it was released under the MIT License.
19 |
20 | Formally:
21 |
22 | .. versionremoved:: 2.2
23 |
24 | Acceleration of the |soi| "suggest" mode via |python-Levenshtein|_
25 | has been deprecated and is no longer available.
26 |
27 | The discussion of performance benchmarks and variations in matching
28 | behavior is kept below for historical interest.
29 |
30 |
31 | Performance Benchmark
32 | ---------------------
33 |
34 | The chart below presents one dataset illustrating the performance enhancement
35 | that can be obtained by installing |python-Levenshtein|_.
36 | The timings plotted here are from execution of
37 | :func:`timeit.repeat` around a
38 | :meth:`~sphobjinv.inventory.Inventory.suggest` call,
39 | searching for the term "function", for a number of
40 | |objects.inv| files from different projects (see
41 | `here `__).
43 |
44 | The timings were collected using the following code::
45 |
46 | import sphobjinv as soi
47 |
48 | durations = {}
49 | obj_counts = {}
50 |
51 | for fn in os.listdir():
52 | if fn.endswith('.inv'):
53 | inv = soi.Inventory(fn)
54 |
55 | # Execute the 'suggest' operation 20 times for each file
56 | timings = timeit.repeat("inv.suggest('function')", repeat=20, number=1, globals=globals())
57 |
58 | # Store the average timing for each file
59 | durations.update({fn: sum(timings) / len(timings)})
60 |
61 | # Store the number of objects
62 | obj_counts.update({fn: inv.count})
63 |
64 |
65 | As can be seen, the fifty-two |objects.inv| files in this dataset
66 | contain widely varying numbers of objects :math:`n`:
67 |
68 | .. image:: /_static/suggest_timing.png
69 |
70 | Unsurprisingly, larger inventories require more time to search.
71 | Also relatively unsurprisingly, the time required appears to be
72 | roughly :math:`O(n)`, since the fuzzy search must be performed once on the
73 | :attr:`~sphobjinv.data.SuperDataObj.as_rst` representation of each object.
74 |
75 | For this specific search, using |python-Levenshtein|_ instead of
76 | :mod:`difflib` decreases the time required from 0.90 seconds per thousand objects
77 | down to 0.15 seconds per thousand objects,
78 | representing a performance improvement of almost exactly six-fold.
79 | Other searches will likely exhibit somewhat better or worse
80 | improvement from the use of |python-Levenshtein|_,
81 | depending on the average length of the reST-like representations
82 | of the objects in an |objects.inv|
83 | and the length of the search term.
84 |
85 |
86 | Variations in Matching Behavior
87 | -------------------------------
88 |
89 | Note that the matching scores calculated by
90 | :mod:`difflib` and |python-Levenshtein|_ can often
91 | differ appreciably. (This is illustrated in
92 | `issue #128 `__
93 | of the |fuzzywuzzy|_ GitHub repo.)
94 | This difference in behavior doesn't have much practical significance,
95 | save for the potential of causing some confusion between users with/without
96 | |python-Levenshtein|_ installed.
97 |
98 |
99 | As an example, the following shows an excerpt of the results of a representative
100 | CLI :doc:`suggest ` call **without**
101 | |python-Levenshtein|_::
102 |
103 | $ sphobjinv suggest objects_scipy.inv surface -asit 40
104 |
105 | Name Score Index
106 | ------------------------------------------------------ ------- -------
107 | :py:function:`scipy.misc.face` 64 1018
108 | :py:function:`scipy.misc.source` 64 1032
109 | :std:doc:`generated/scipy.misc.face` 64 4042
110 | :std:doc:`generated/scipy.misc.source` 64 4056
111 | :py:data:`scipy.stats.rice` 56 2688
112 | :std:label:`continuous-rice` 56 2896
113 | :py:method:`scipy.integrate.complex_ode.successful` 51 156
114 | :py:method:`scipy.integrate.ode.successful` 51 171
115 | :py:function:`scipy.linalg.lu_factor` 51 967
116 |
117 | ... more with score 51 ...
118 |
119 | :py:attribute:`scipy.LowLevelCallable.signature` 50 5
120 | :py:function:`scipy.constants.convert_temperature` 50 53
121 | :py:function:`scipy.integrate.quadrature` 50 176
122 |
123 | ... more with score 50 and below ...
124 |
125 | This is a similar excerpt **with** |python-Levenshtein|_::
126 |
127 | Name Score Index
128 | ------------------------------------------------------ ------- -------
129 | :py:function:`scipy.misc.face` 64 1018
130 | :py:function:`scipy.misc.source` 64 1032
131 | :std:doc:`generated/scipy.misc.face` 64 4042
132 | :std:doc:`generated/scipy.misc.source` 64 4056
133 | :py:method:`scipy.integrate.ode.successful` 51 171
134 | :py:function:`scipy.linalg.lu_factor` 51 967
135 | :py:function:`scipy.linalg.subspace_angles` 51 1003
136 |
137 | ... more with score 51 ...
138 |
139 | :py:function:`scipy.cluster.hierarchy.fcluster` 49 23
140 | :py:function:`scipy.cluster.hierarchy.fclusterdata` 49 24
141 | :py:method:`scipy.integrate.complex_ode.successful` 49 156
142 |
143 | ... more with score 49 and below ...
144 |
145 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | build-backend = "setuptools.build_meta"
3 | requires = [
4 | "setuptools>=61.2",
5 | "wheel",
6 | ]
7 |
8 | [project]
9 | name = "sphobjinv"
10 | description = "Sphinx objects.inv Inspection/Manipulation Tool"
11 | license = {text = "MIT License"}
12 | authors = [{name = "Brian Skinn", email = "brian.skinn@gmail.com"}]
13 | classifiers = [
14 | "License :: OSI Approved",
15 | "License :: OSI Approved :: MIT License",
16 | "Natural Language :: English",
17 | "Environment :: Console",
18 | "Framework :: Sphinx",
19 | "Intended Audience :: Developers",
20 | "Operating System :: OS Independent",
21 | "Programming Language :: Python",
22 | "Programming Language :: Python :: 3",
23 | "Programming Language :: Python :: 3 :: Only",
24 | "Programming Language :: Python :: 3.9",
25 | "Programming Language :: Python :: 3.10",
26 | "Programming Language :: Python :: 3.11",
27 | "Programming Language :: Python :: 3.12",
28 | "Programming Language :: Python :: 3.13",
29 | "Topic :: Documentation",
30 | "Topic :: Documentation :: Sphinx",
31 | "Topic :: Software Development",
32 | "Topic :: Software Development :: Documentation",
33 | "Topic :: Utilities",
34 | "Development Status :: 5 - Production/Stable",
35 | ]
36 | keywords = ["sphinx", "sphinx-doc", "inventory", "manager", "inspector"]
37 | requires-python = ">=3.9"
38 | dependencies = [
39 | "attrs>=19.2",
40 | "certifi",
41 | "jsonschema>=3.0",
42 | ]
43 | dynamic = ["version", "readme"]
44 |
45 | [project.urls]
46 | Homepage = "https://github.com/bskinn/sphobjinv"
47 | Changelog = "https://github.com/bskinn/sphobjinv/blob/main/CHANGELOG.md"
48 | Docs = "https://sphobjinv.readthedocs.io/en/stable/"
49 | Thank = "https://fosstodon.org/@btskinn"
50 | Donate = "https://github.com/sponsors/bskinn"
51 |
52 | [project.scripts]
53 | sphobjinv = "sphobjinv.cli.core:main"
54 |
55 | [tool.setuptools]
56 | package-dir = {"" = "src"}
57 | platforms = ["any"]
58 | license-files = ["LICENSE.txt"]
59 | include-package-data = false
60 |
61 | [tool.setuptools.packages.find]
62 | where = ["src"]
63 | namespaces = false
64 |
65 | [tool.setuptools.dynamic]
66 | version = {attr = "sphobjinv.version.__version__"}
67 |
68 | [tool.black]
69 | line-length = 88
70 | include = '''
71 | (
72 | ^/tests/.*[.]py$
73 | | ^/src/sphobjinv/.*[.]py$
74 | | ^/setup[.]py
75 | | ^/conftest[.]py
76 | )
77 | '''
78 | exclude = '''
79 | (
80 | __pycache__
81 | | ^/[.]
82 | | ^/doc
83 | | ^/env
84 | | ^/src/sphobjinv/_vendored
85 | )
86 | '''
87 |
88 | [tool.interrogate]
89 | exclude = ["src/sphobjinv/_vendored"]
90 | fail-under = 100
91 | verbose = 1
92 |
--------------------------------------------------------------------------------
/requirements-ci.txt:
--------------------------------------------------------------------------------
1 | attrs>=19.2
2 | certifi
3 | coverage
4 | dictdiffer
5 | jsonschema
6 | md-toc
7 | pytest>=4.4.0
8 | pytest-check>=1.1.2
9 | pytest-cov
10 | pytest-retry
11 | pytest-timeout
12 | sphinx==7.4.7
13 | sphinx-issues
14 | sphinx-rtd-theme>=0.5.1
15 | sphinxcontrib-programoutput
16 | stdio-mgr>=1.0.1
17 | tox
18 | wheel
19 | -e .
20 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | attrs>=19.2
2 | build
3 | certifi
4 | coverage
5 | dictdiffer
6 | jsonschema
7 | md-toc
8 | pytest>=4.4.0
9 | pytest-check>=1.1.2
10 | pytest-cov
11 | pytest-retry
12 | pytest-timeout
13 | restview
14 | sphinx==7.4.7 # Staying <8 since 8.0 drops Python 3.9
15 | sphinx-autobuild
16 | sphinx-issues
17 | sphinx-rtd-theme>=0.5.1
18 | sphinxcontrib-programoutput
19 | stdio-mgr>=1.0.1
20 | tox
21 | twine
22 | -r requirements-flake8.txt
23 | -e .
24 |
--------------------------------------------------------------------------------
/requirements-flake8.txt:
--------------------------------------------------------------------------------
1 | colorama
2 | flake8>=6.1.0 # Avoids pycodestyle bug @ v6.0 w.r.t re.py
3 | flake8-2020
4 | flake8-absolute-import
5 | flake8-bandit
6 | flake8-black
7 | flake8-bugbear
8 | flake8-builtins
9 | flake8-comprehensions
10 | flake8-docstrings>=1.3.1
11 | flake8-eradicate
12 | flake8-implicit-str-concat
13 | flake8-import-order
14 | flake8-pie
15 | flake8-raise
16 | flake8-rst-docstrings
17 | pep8-naming
18 |
--------------------------------------------------------------------------------
/requirements-interrogate.txt:
--------------------------------------------------------------------------------
1 | interrogate
2 |
--------------------------------------------------------------------------------
/requirements-rtd.txt:
--------------------------------------------------------------------------------
1 | attrs>=19.2
2 | sphinx==7.4.7
3 | sphinx-issues
4 | sphinx-rtd-theme>=0.5.1
5 | sphinxcontrib-programoutput
6 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import re
2 | from pathlib import Path
3 |
4 | from setuptools import setup
5 |
6 | NAME = "sphobjinv"
7 |
8 | exec_ns = {}
9 | exec(Path("src", "sphobjinv", "version.py").read_text(encoding="utf-8"), exec_ns)
10 | __version__ = exec_ns["__version__"]
11 |
12 | version_override = "2.3.1.2"
13 |
14 |
15 | def readme():
16 | content = Path("README.md").read_text(encoding="utf-8")
17 |
18 | new_ver = version_override if version_override else __version__
19 |
20 | # Helper function
21 | def content_update(content, pattern, sub):
22 | return re.sub(pattern, sub, content, flags=re.M | re.I)
23 |
24 | # Docs reference updates to current release version, for PyPI
25 | # This one gets the badge image
26 | content = content_update(
27 | content, r"(?<=/readthedocs/{0}/)\S+?(?=\.svg$)".format(NAME), "v" + new_ver
28 | )
29 |
30 | # This one gets the RtD links
31 | content = content_update(
32 | content, r"(?<={0}\.readthedocs\.io/en/)\S+?(?=/)".format(NAME), "v" + new_ver
33 | )
34 |
35 | return content
36 |
37 |
38 | setup(
39 | name=NAME,
40 | long_description=readme(),
41 | long_description_content_type="text/markdown",
42 | )
43 |
--------------------------------------------------------------------------------
/src/sphobjinv/__init__.py:
--------------------------------------------------------------------------------
1 | r"""``sphobjinv`` *package definition module*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 17 May 2016
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | from sphobjinv.data import DataFields, DataObjBytes, DataObjStr
33 | from sphobjinv.enum import HeaderFields, SourceTypes
34 | from sphobjinv.error import SphobjinvError, VersionError
35 | from sphobjinv.fileops import readbytes, readjson, urlwalk, writebytes, writejson
36 | from sphobjinv.inventory import Inventory
37 | from sphobjinv.re import p_data, pb_comments, pb_data, pb_project, pb_version
38 | from sphobjinv.schema import json_schema
39 | from sphobjinv.version import __version__
40 | from sphobjinv.zlib import compress, decompress
41 |
--------------------------------------------------------------------------------
/src/sphobjinv/__main__.py:
--------------------------------------------------------------------------------
1 | r"""``sphobjinv`` *package execution module*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 15 May 2020
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import sys
33 |
34 | from sphobjinv.cli.core import main
35 |
36 | if __name__ == "__main__":
37 | # Spoof so 'help' usage display shows "sphobjinv" and
38 | # not "__main__.py"
39 | sys.argv[0] = "sphobjinv"
40 |
41 | sys.exit(main())
42 |
--------------------------------------------------------------------------------
/src/sphobjinv/_vendored/__init__.py:
--------------------------------------------------------------------------------
1 | r"""``sphobjinv._vendored`` *package definition module*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | Subpackage marker module for vendored packages.
7 |
8 | **Author**
9 | Brian Skinn (brian.skinn@gmail.com)
10 |
11 | **File Created**
12 | 11 Dec 2021
13 |
14 | **Copyright**
15 | \(c) Brian Skinn 2016-2025
16 |
17 | **Source Repository**
18 | https://github.com/bskinn/sphobjinv
19 |
20 | **Documentation**
21 | https://sphobjinv.readthedocs.io/en/stable
22 |
23 | **License**
24 | Code: `MIT License`_
25 |
26 | Docs & Docstrings: |CC BY 4.0|_
27 |
28 | See |license_txt|_ for full license terms.
29 |
30 | **Members**
31 |
32 | """
33 |
--------------------------------------------------------------------------------
/src/sphobjinv/_vendored/fuzzywuzzy/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Adam Cohen
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/src/sphobjinv/_vendored/fuzzywuzzy/__init__.py:
--------------------------------------------------------------------------------
1 | r"""``sphobjinv._vendored.fuzzywuzzy`` *package definition module*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | This subpackage vendors an archival version of fuzzywuzzy from
7 | before it incorporated python-Levenshtein and thus acquired
8 | the GPL licence by flow-through, overriding the prior
9 | MIT license of the package.
10 |
11 | All code in this subpackage vendored from:
12 | https://github.com/seatgeek/fuzzywuzzy/tree/4bf28161f7005f3aa9d4d931455ac55126918df7
13 |
14 | Imports and code details were changed as required to make the code compatible
15 | with current (2021) versions of Python 3.
16 |
17 | Link to MIT-licensed fuzzywuzzy version obtained from the bottom of the README at:
18 | https://github.com/maxbachmann/RapidFuzz/tree/460d291a38922c7fead920de8147798b817653cb
19 |
20 |
21 | **Author**
22 | Brian Skinn (brian.skinn@gmail.com)
23 |
24 | **File Created**
25 | 11 Dec 2021
26 |
27 | **Copyright**
28 | \(c) Brian Skinn 2016-2025
29 |
30 | **Source Repository**
31 | https://github.com/bskinn/sphobjinv
32 |
33 | **Documentation**
34 | https://sphobjinv.readthedocs.io/en/stable
35 |
36 | **License**
37 | Code: `MIT License`_
38 |
39 | Docs & Docstrings: |CC BY 4.0|_
40 |
41 | See |license_txt|_ for full license terms.
42 |
43 | **Members**
44 |
45 | """
46 |
--------------------------------------------------------------------------------
/src/sphobjinv/_vendored/fuzzywuzzy/fuzz.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | score.py
5 |
6 | Copyright (c) 2011 Adam Cohen
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining
9 | a copy of this software and associated documentation files (the
10 | "Software"), to deal in the Software without restriction, including
11 | without limitation the rights to use, copy, modify, merge, publish,
12 | distribute, sublicense, and/or sell copies of the Software, and to
13 | permit persons to whom the Software is furnished to do so, subject to
14 | the following conditions:
15 |
16 | The above copyright notice and this permission notice shall be
17 | included in all copies or substantial portions of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 | """
27 |
28 | import sys
29 | import os
30 | import re
31 | from difflib import SequenceMatcher
32 | from sphobjinv._vendored.fuzzywuzzy import utils
33 |
34 | REG_TOKEN = re.compile(r"[\w\d]+") # B Skinn 2021-12-11
35 |
36 | ###########################
37 | # Basic Scoring Functions #
38 | ###########################
39 |
40 | def ratio(s1, s2):
41 |
42 | if s1 is None: raise TypeError("s1 is None")
43 | if s2 is None: raise TypeError("s2 is None")
44 |
45 | m = SequenceMatcher(None, s1, s2)
46 | return int(100 * m.ratio())
47 |
48 | # todo: skip duplicate indexes for a little more speed
49 | def partial_ratio(s1, s2):
50 |
51 | if s1 is None: raise TypeError("s1 is None")
52 | if s2 is None: raise TypeError("s2 is None")
53 |
54 | if len(s1) <= len(s2):
55 | shorter = s1; longer = s2;
56 | else:
57 | shorter = s2; longer = s1
58 |
59 | m = SequenceMatcher(None, shorter, longer)
60 | blocks = m.get_matching_blocks()
61 |
62 | # each block represents a sequence of matching characters in a string
63 | # of the form (idx_1, idx_2, len)
64 | # the best partial match will block align with at least one of those blocks
65 | # e.g. shorter = "abcd", longer = XXXbcdeEEE
66 | # block = (1,3,3)
67 | # best score === ratio("abcd", "Xbcd")
68 | scores = []
69 | for block in blocks:
70 | long_start = block[1] - block[0] if (block[1] - block[0]) > 0 else 0
71 | long_end = long_start + len(shorter)
72 | long_substr = longer[long_start:long_end]
73 |
74 | m2 = SequenceMatcher(None, shorter, long_substr)
75 | r = m2.ratio()
76 | if r > .995: return 100
77 | else: scores.append(r)
78 |
79 | return int(100 * max(scores))
80 |
81 | ##############################
82 | # Advanced Scoring Functions #
83 | ##############################
84 |
85 | # Sorted Token
86 | # find all alphanumeric tokens in the string
87 | # sort those tokens and take ratio of resulting joined strings
88 | # controls for unordered string elements
89 | def _token_sort(s1, s2, partial=True):
90 |
91 | if s1 is None: raise TypeError("s1 is None")
92 | if s2 is None: raise TypeError("s2 is None")
93 |
94 | # pull tokens
95 | tokens1 = REG_TOKEN.findall(s1)
96 | tokens2 = REG_TOKEN.findall(s2)
97 |
98 | # sort tokens and join
99 | sorted1 = u" ".join(sorted(tokens1))
100 | sorted2 = u" ".join(sorted(tokens2))
101 |
102 | sorted1 = sorted1.strip()
103 | sorted2 = sorted2.strip()
104 |
105 | if partial:
106 | return partial_ratio(sorted1, sorted2)
107 | else:
108 | return ratio(sorted1, sorted2)
109 |
110 | def token_sort_ratio(s1, s2):
111 | return _token_sort(s1, s2, False)
112 |
113 | def partial_token_sort_ratio(s1, s2):
114 | return _token_sort(s1, s2, True)
115 |
116 | # Token Set
117 | # find all alphanumeric tokens in each string...treat them as a set
118 | # construct two strings of the form
119 | #
120 | # take ratios of those two strings
121 | # controls for unordered partial matches
122 | def _token_set(s1, s2, partial=True):
123 |
124 | if s1 is None: raise TypeError("s1 is None")
125 | if s2 is None: raise TypeError("s2 is None")
126 |
127 | # pull tokens
128 | tokens1 = set(REG_TOKEN.findall(s1))
129 | tokens2 = set(REG_TOKEN.findall(s2))
130 |
131 | intersection = tokens1.intersection(tokens2)
132 | diff1to2 = tokens1.difference(tokens2)
133 | diff2to1 = tokens2.difference(tokens1)
134 |
135 | sorted_sect = u" ".join(sorted(intersection))
136 | sorted_1to2 = u" ".join(sorted(diff1to2))
137 | sorted_2to1 = u" ".join(sorted(diff2to1))
138 |
139 | combined_1to2 = sorted_sect + " " + sorted_1to2
140 | combined_2to1 = sorted_sect + " " + sorted_2to1
141 |
142 | # strip
143 | sorted_sect = sorted_sect.strip()
144 | combined_1to2 = combined_1to2.strip()
145 | combined_2to1 = combined_2to1.strip()
146 |
147 | pairwise = [
148 | ratio(sorted_sect, combined_1to2),
149 | ratio(sorted_sect, combined_2to1),
150 | ratio(combined_1to2, combined_2to1)
151 | ]
152 | return max(pairwise)
153 |
154 | # if partial:
155 | # # partial_token_set_ratio
156 | #
157 | # else:
158 | # # token_set_ratio
159 | # tsr = ratio(combined_1to2, combined_2to1)
160 | # return tsr
161 |
162 | def token_set_ratio(s1, s2):
163 | return _token_set(s1, s2, False)
164 |
165 | def partial_token_set_ratio(s1, s2):
166 | return _token_set(s1, s2, True)
167 |
168 | # TODO: numerics
169 |
170 | ###################
171 | # Combination API #
172 | ###################
173 |
174 | # q is for quick
175 | def QRatio(s1, s2):
176 | if not utils.validate_string(s1): return 0
177 | if not utils.validate_string(s2): return 0
178 |
179 | p1 = utils.full_process(s1)
180 | p2 = utils.full_process(s2)
181 |
182 | return ratio(p1, p2)
183 |
184 | # w is for weighted
185 | def WRatio(s1, s2):
186 | p1 = utils.full_process(s1)
187 | p2 = utils.full_process(s2)
188 | if not utils.validate_string(p1): return 0
189 | if not utils.validate_string(p2): return 0
190 |
191 | # should we look at partials?
192 | try_partial = True
193 | unbase_scale = .95
194 | partial_scale = .90
195 |
196 | base = ratio(p1, p2)
197 | len_ratio = float(max(len(p1),len(p2)))/min(len(p1),len(p2))
198 |
199 | # if strings are similar length, don't use partials
200 | if len_ratio < 1.5: try_partial = False
201 |
202 | # if one string is much much shorter than the other
203 | if len_ratio > 8: partial_scale = .6
204 |
205 | if try_partial:
206 | partial = partial_ratio(p1, p2) * partial_scale
207 | ptsor = partial_token_sort_ratio(p1, p2) * unbase_scale * partial_scale
208 | ptser = partial_token_set_ratio(p1, p2) * unbase_scale * partial_scale
209 |
210 | return int(max(base, partial, ptsor, ptser))
211 | else:
212 | tsor = token_sort_ratio(p1, p2) * unbase_scale
213 | tser = token_set_ratio(p1, p2) * unbase_scale
214 |
215 | return int(max(base, tsor, tser))
216 |
217 |
--------------------------------------------------------------------------------
/src/sphobjinv/_vendored/fuzzywuzzy/process.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | process.py
5 |
6 | Copyright (c) 2011 Adam Cohen
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining
9 | a copy of this software and associated documentation files (the
10 | "Software"), to deal in the Software without restriction, including
11 | without limitation the rights to use, copy, modify, merge, publish,
12 | distribute, sublicense, and/or sell copies of the Software, and to
13 | permit persons to whom the Software is furnished to do so, subject to
14 | the following conditions:
15 |
16 | The above copyright notice and this permission notice shall be
17 | included in all copies or substantial portions of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 | """
27 | from sphobjinv._vendored.fuzzywuzzy.fuzz import *
28 |
29 | import sys, os
30 | from sphobjinv._vendored.fuzzywuzzy import utils
31 |
32 | #######################################
33 | # Find Best Matchs In List Of Choices #
34 | #######################################
35 |
36 | def extract(query, choices, processor=None, scorer=None, limit=5):
37 |
38 | # choices = a list of objects we are attempting to extract values from
39 | # query = an object representing the thing we want to find
40 | # scorer f(OBJ, QUERY) --> INT. We will return the objects with the highest score
41 | # by default, we use score.WRatio() and both OBJ and QUERY should be strings
42 | # processor f(OBJ_A) --> OBJ_B, where the output is an input to scorer
43 | # for example, "processor = lambda x: x[0]" would return the first element in a collection x (of, say, strings)
44 | # this would then be used in the scoring collection
45 |
46 | if choices is None or len(choices) == 0:
47 | return []
48 |
49 | # default, turn whatever the choice is into a string
50 | if processor is None:
51 | processor = lambda x: utils.asciidammit(x)
52 |
53 | # default: wratio
54 | if scorer is None:
55 | scorer = WRatio
56 |
57 | sl = list()
58 |
59 | for choice in choices:
60 | processed = processor(choice)
61 | score = scorer(query, processed)
62 | tuple = (choice, score)
63 | sl.append(tuple)
64 |
65 | sl.sort(key=lambda i: -1*i[1])
66 | return sl[:limit]
67 |
68 | ##########################
69 | # Find Single Best Match #
70 | ##########################
71 |
72 | def extractOne(query, choices, processor=None, scorer=None, score_cutoff=0):
73 |
74 | # convenience method which returns the single best choice
75 | # optional parameter: score_cutoff.
76 | # If the best choice has a score of less than score_cutoff
77 | # we will return none (intuition: not a good enough match)
78 |
79 | best_list = extract(query, choices, processor, scorer, limit=1)
80 | if len(best_list) > 0:
81 | best = best_list[0]
82 | if best[1] > score_cutoff:
83 | return best
84 | else:
85 | return None
86 | else:
87 | return None
88 |
--------------------------------------------------------------------------------
/src/sphobjinv/_vendored/fuzzywuzzy/utils.py:
--------------------------------------------------------------------------------
1 | import string
2 |
3 | bad_chars=b"" # B Skinn 2021-12-11
4 | for i in range(128,256):
5 | bad_chars+=chr(i).encode() # B Skinn 2021-12-11
6 | table_from=string.punctuation+string.ascii_uppercase
7 | table_to=' '*len(string.punctuation)+string.ascii_lowercase
8 | trans_table=bytes.maketrans(table_from.encode(), table_to.encode()) # B Skinn 2021-12-11
9 |
10 |
11 | def asciionly(s):
12 | return s.encode().translate(None, bad_chars).decode(errors='replace') # B Skinn 2021-12-11
13 |
14 | # remove non-ASCII characters from strings
15 | def asciidammit(s):
16 | if type(s) is str:
17 | return asciionly(s)
18 | elif type(s) is unicode:
19 | return asciionly(s.encode('ascii', 'ignore'))
20 | else:
21 | return asciidammit(unicode(s))
22 |
23 | def validate_string(s):
24 | try:
25 | if len(s)>0:
26 | return True
27 | else:
28 | return False
29 | except:
30 | return False
31 |
32 | def full_process(s):
33 | s = asciidammit(s)
34 | # B Skinn 2021-12-11
35 | return s.encode().translate(trans_table, bad_chars).decode(errors='replace').strip()
36 |
--------------------------------------------------------------------------------
/src/sphobjinv/cli/__init__.py:
--------------------------------------------------------------------------------
1 | r"""``sphobjinv.cli`` *subpackage definition module*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 15 Nov 2020
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | from sphobjinv.cli.core import main
33 |
--------------------------------------------------------------------------------
/src/sphobjinv/cli/convert.py:
--------------------------------------------------------------------------------
1 | r"""``sphobjinv`` *module for CLI convert functionality*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 20 Oct 2022
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | from sphobjinv.cli.parser import PrsConst
33 | from sphobjinv.cli.write import write_file, write_stdout
34 |
35 |
36 | def do_convert(inv, in_path, params):
37 | r"""Carry out the conversion operation, including writing output.
38 |
39 | If |cli:OVERWRITE| is passed and the output file
40 | (the default location, or as passed to |cli:OUTFILE|)
41 | exists, it will be overwritten without a prompt. Otherwise,
42 | the user will be queried if it is desired to overwrite
43 | the existing file.
44 |
45 | If |cli:QUIET| is passed, nothing will be
46 | printed to |cour|\ stdout\ |/cour|
47 | (potentially useful for scripting),
48 | and any existing output file will be overwritten
49 | without prompting.
50 |
51 | Parameters
52 | ----------
53 | inv
54 |
55 | |Inventory| -- Inventory object to be output in the format
56 | indicated by |cli:MODE|.
57 |
58 | in_path
59 |
60 | |str| -- For a local input file, its absolute path.
61 | For a URL, the (possibly truncated) URL text.
62 |
63 | params
64 |
65 | |dict| -- Parameters/values mapping from the active subparser
66 |
67 | """
68 | if params[PrsConst.OUTFILE] == "-" or (
69 | params[PrsConst.INFILE] == "-" and params[PrsConst.OUTFILE] is None
70 | ):
71 | write_stdout(inv, params)
72 | else:
73 | write_file(inv, in_path, params)
74 |
--------------------------------------------------------------------------------
/src/sphobjinv/cli/core.py:
--------------------------------------------------------------------------------
1 | r"""``sphobjinv`` *CLI core execution module*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 15 Nov 2020
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import sys
33 |
34 | from sphobjinv.cli.convert import do_convert
35 | from sphobjinv.cli.load import inv_local, inv_stdin, inv_url
36 | from sphobjinv.cli.parser import getparser, PrsConst
37 | from sphobjinv.cli.suggest import do_suggest
38 | from sphobjinv.cli.ui import print_stderr
39 |
40 |
41 | def main():
42 | r"""Handle command line invocation.
43 |
44 | Parses command line arguments,
45 | handling the no-arguments and
46 | |cli:VERSION| cases.
47 |
48 | Creates the |Inventory| from the indicated source
49 | and method.
50 |
51 | Invokes :func:`~sphobjinv.cli.convert.do_convert` or
52 | :func:`~sphobjinv.cli.suggest.do_suggest`
53 | per the subparser name stored in |cli:SUBPARSER_NAME|.
54 |
55 | """
56 | # If no args passed, stick in '-h'
57 | if len(sys.argv) == 1:
58 | sys.argv.append("-h")
59 |
60 | # Parse commandline arguments, discarding any unknown ones
61 | # I forget why I set it up to discard these, it might be
62 | # more confusing than it's worth to swallow them this way....
63 | prs = getparser()
64 | ns, _ = prs.parse_known_args()
65 | params = vars(ns)
66 |
67 | # Print version &c. and exit if indicated
68 | if params[PrsConst.VERSION]:
69 | print(PrsConst.VER_TXT)
70 | sys.exit(0)
71 |
72 | # At this point, need to trap for a null subparser
73 | if not params[PrsConst.SUBPARSER_NAME]:
74 | prs.error("No subparser selected")
75 |
76 | # Regardless of mode, insert extra blank line
77 | # for cosmetics
78 | print_stderr(" ", params)
79 |
80 | # Generate the input Inventory based on --url or stdio or file.
81 | # These inventory-load functions should call
82 | # sys.exit(n) internally in error-exit situations
83 | if params[PrsConst.URL]:
84 | if params[PrsConst.INFILE] == "-":
85 | prs.error("argument -u/--url not allowed with '-' as infile")
86 | inv, in_path = inv_url(params)
87 | elif params[PrsConst.INFILE] == "-":
88 | inv = inv_stdin(params)
89 | in_path = None
90 | else:
91 | inv, in_path = inv_local(params)
92 |
93 | # Perform action based upon mode
94 | if params[PrsConst.SUBPARSER_NAME][:2] == PrsConst.CONVERT[:2]:
95 | do_convert(inv, in_path, params)
96 | elif params[PrsConst.SUBPARSER_NAME][:2] == PrsConst.SUGGEST[:2]:
97 | do_suggest(inv, params)
98 |
99 | # Cosmetic final blank line
100 | print_stderr(" ", params)
101 |
102 | # Clean exit
103 | sys.exit(0)
104 |
--------------------------------------------------------------------------------
/src/sphobjinv/cli/load.py:
--------------------------------------------------------------------------------
1 | r"""*Module for* ``sphobjinv`` *CLI* |Inventory| *loading*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 17 Nov 2020
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import json
33 | import sys
34 | from json import JSONDecodeError
35 | from urllib.error import HTTPError, URLError
36 |
37 | from jsonschema.exceptions import ValidationError
38 |
39 | from sphobjinv import Inventory, readjson, urlwalk, VersionError
40 | from sphobjinv.cli.parser import PrsConst
41 | from sphobjinv.cli.paths import resolve_inpath
42 | from sphobjinv.cli.ui import err_format, print_stderr
43 |
44 |
45 | def import_infile(in_path):
46 | """Attempt import of indicated file.
47 |
48 | Convenience function wrapping attempts to load an
49 | |Inventory| from a local path.
50 |
51 | Parameters
52 | ----------
53 | in_path
54 |
55 | |str| -- Path to input file
56 |
57 | Returns
58 | -------
59 | inv
60 |
61 | |Inventory| or |None| -- If instantiation with the file at
62 | `in_path` succeeds, the resulting |Inventory| instance;
63 | otherwise, |None|
64 |
65 | """
66 | # Try general import, for zlib or plaintext files
67 | try:
68 | inv = Inventory(in_path)
69 | except AttributeError:
70 | pass # Punt to JSON attempt
71 | else:
72 | return inv
73 |
74 | # Maybe it's JSON
75 | try:
76 | inv = Inventory(readjson(in_path))
77 | except JSONDecodeError:
78 | return None
79 | else:
80 | return inv
81 |
82 |
83 | def inv_local(params):
84 | """Create |Inventory| from local source.
85 |
86 | Uses |resolve_inpath| to sanity-check and/or convert
87 | |cli:INFILE|.
88 |
89 | Calls :func:`sys.exit` internally in error-exit situations.
90 |
91 | Parameters
92 | ----------
93 | params
94 |
95 | |dict| -- Parameters/values mapping from the active subparser
96 |
97 | Returns
98 | -------
99 | inv
100 |
101 | |Inventory| -- Object representation of the inventory
102 | at |cli:INFILE|
103 |
104 | in_path
105 |
106 | |str| -- Input file path as resolved/checked by
107 | |resolve_inpath|
108 |
109 | """
110 | # Resolve input file path
111 | try:
112 | in_path = resolve_inpath(params[PrsConst.INFILE])
113 | except Exception as e:
114 | print_stderr("\nError while parsing input file path:", params)
115 | print_stderr(err_format(e), params)
116 | sys.exit(1)
117 |
118 | # Attempt import
119 | inv = import_infile(in_path)
120 | if inv is None:
121 | print_stderr("\nError: Unrecognized file format", params)
122 | sys.exit(1)
123 |
124 | return inv, in_path
125 |
126 |
127 | def inv_url(params):
128 | """Create |Inventory| from file downloaded from URL.
129 |
130 | Initially, treats |cli:INFILE| as a download URL to be passed to
131 | the `url` initialization argument
132 | of :class:`~sphobjinv.inventory.Inventory`.
133 |
134 | If an inventory is not found at that exact URL, progressively
135 | searches the directory tree of the URL for |objects.inv|.
136 |
137 | Injects the URL at which an inventory was found into `params`
138 | under the |cli:FOUND_URL| key.
139 |
140 | Calls :func:`sys.exit` internally in error-exit situations.
141 |
142 | Parameters
143 | ----------
144 | params
145 |
146 | |dict| -- Parameters/values mapping from the active subparser
147 |
148 | Returns
149 | -------
150 | inv
151 |
152 | |Inventory| -- Object representation of the inventory
153 | at |cli:INFILE|
154 |
155 | ret_path
156 |
157 | |str| -- URL from |cli:INFILE| used to construct `inv`.
158 | If URL is longer than 45 characters, the central portion is elided.
159 |
160 | """
161 | in_file = params[PrsConst.INFILE]
162 |
163 | def attempt_inv_load(url, params):
164 | """Attempt the Inventory load and report outcome."""
165 | inv = None
166 |
167 | try:
168 | inv = Inventory(url=url)
169 | except HTTPError as e:
170 | print_stderr(f" ... HTTP error: {e.code} {e.reason}.", params)
171 | except URLError: # pragma: no cover
172 | print_stderr(" ... error attempting to retrieve URL.", params)
173 | except VersionError: # pragma: no cover
174 | print_stderr(" ... no recognized inventory.", params)
175 | except ValueError:
176 | print_stderr(
177 | (
178 | " ... file found but inventory could not be loaded. "
179 | "(Did you forget https:// ?)"
180 | ),
181 | params,
182 | )
183 | else:
184 | print_stderr(" ... inventory found.", params)
185 |
186 | return inv
187 |
188 | # Disallow --url mode on local files
189 | if in_file.startswith("file:/"):
190 | print_stderr("\nError: URL mode on local file is invalid", params)
191 | sys.exit(1)
192 |
193 | print_stderr(f"Attempting {in_file} ...", params)
194 | inv = attempt_inv_load(in_file, params)
195 |
196 | if inv:
197 | url = in_file
198 | else:
199 | for url in urlwalk(in_file):
200 | print_stderr(f'Attempting "{url}" ...', params)
201 | inv = attempt_inv_load(url, params)
202 | if inv:
203 | break
204 |
205 | # Cosmetic line break
206 | print_stderr(" ", params)
207 |
208 | # Success or no?
209 | if not inv:
210 | print_stderr("No inventory found!", params)
211 | sys.exit(1)
212 |
213 | params.update({PrsConst.FOUND_URL: url})
214 | if len(url) > 45:
215 | ret_path = url[:20] + "[...]" + url[-20:]
216 | else: # pragma: no cover
217 | ret_path = url
218 |
219 | return inv, ret_path
220 |
221 |
222 | def inv_stdin(params):
223 | """Create |Inventory| from contents of stdin.
224 |
225 | Due to stdin's encoding and formatting assumptions, only
226 | text-based inventory formats can be sanely parsed.
227 |
228 | Thus, only plaintext and JSON inventory formats can be
229 | used as inputs here.
230 |
231 | Parameters
232 | ----------
233 | params
234 |
235 | |dict| -- Parameters/values mapping from the active subparser
236 |
237 | Returns
238 | -------
239 | inv
240 |
241 | |Inventory| -- Object representation of the inventory
242 | provided at stdin
243 |
244 | """
245 | data = sys.stdin.read()
246 |
247 | try:
248 | return Inventory(dict_json=json.loads(data))
249 | except (JSONDecodeError, ValidationError):
250 | pass
251 |
252 | try:
253 | return Inventory(plaintext=data)
254 | except (AttributeError, UnicodeEncodeError, TypeError):
255 | pass
256 |
257 | print_stderr("Invalid plaintext or JSON inventory format.", params)
258 | sys.exit(1)
259 |
--------------------------------------------------------------------------------
/src/sphobjinv/cli/paths.py:
--------------------------------------------------------------------------------
1 | r"""``sphobjinv`` *CLI path resolution module*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 19 Nov 2020
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import os
33 |
34 | from sphobjinv.cli.parser import PrsConst
35 |
36 |
37 | def resolve_inpath(in_path):
38 | """Resolve the input file, handling invalid values.
39 |
40 | Currently, only checks for existence and not-directory.
41 |
42 | Parameters
43 | ----------
44 | in_path
45 |
46 | |str| -- Path to desired input file
47 |
48 | Returns
49 | -------
50 | abs_path
51 |
52 | |str| -- Absolute path to indicated file
53 |
54 | Raises
55 | ------
56 | :exc:`FileNotFoundError`
57 |
58 | If a file is not found at the given path
59 |
60 | """
61 | # Path MUST be to a file, that exists
62 | if not os.path.isfile(in_path):
63 | raise FileNotFoundError("Indicated path is not a valid file")
64 |
65 | # Return the path as absolute
66 | return os.path.abspath(in_path)
67 |
68 |
69 | def resolve_outpath(out_path, in_path, params):
70 | r"""Resolve the output location, handling mode-specific defaults.
71 |
72 | If the output path or basename are not specified, they are
73 | taken as the same as the input file. If the extension is
74 | unspecified, it is taken as the appropriate mode-specific value
75 | from |cli:DEF_OUT_EXT|.
76 |
77 | If |cli:URL| is passed, the input directory
78 | is taken to be :func:`os.getcwd` and the input basename
79 | is taken as |cli:DEF_BASENAME|.
80 |
81 | Parameters
82 | ----------
83 | out_path
84 |
85 | |str| or |None| -- Output location provided by the user,
86 | or |None| if omitted
87 |
88 | in_path
89 |
90 | |str| -- For a local input file, its absolute path.
91 | For a URL, the (possibly truncated) URL text.
92 |
93 | params
94 |
95 | |dict| -- Parameters/values mapping from the active subparser
96 |
97 | Returns
98 | -------
99 | out_path
100 |
101 | |str| -- Absolute path to the target output file
102 |
103 | """
104 | mode = params[PrsConst.MODE]
105 |
106 | if params[PrsConst.URL] or in_path is None:
107 | in_fld = os.getcwd()
108 | in_fname = PrsConst.DEF_BASENAME
109 | else:
110 | in_fld, in_fname = os.path.split(in_path)
111 |
112 | if out_path:
113 | # Must check if the path entered is a folder
114 | if os.path.isdir(out_path):
115 | # Set just the folder and leave the name blank
116 | out_fld = out_path
117 | out_fname = None
118 | else:
119 | # Split appropriately
120 | out_fld, out_fname = os.path.split(out_path)
121 |
122 | # Output to same folder if unspecified
123 | if not out_fld:
124 | out_fld = in_fld
125 |
126 | # Use same base filename if not specified
127 | if not out_fname:
128 | out_fname = os.path.splitext(in_fname)[0] + PrsConst.DEF_OUT_EXT[mode]
129 |
130 | # Composite the full output path
131 | out_path = os.path.join(out_fld, out_fname)
132 | else:
133 | # No output location specified; use defaults
134 | out_fname = os.path.splitext(in_fname)[0] + PrsConst.DEF_OUT_EXT[mode]
135 | out_path = os.path.join(in_fld, out_fname)
136 |
137 | return out_path
138 |
--------------------------------------------------------------------------------
/src/sphobjinv/cli/ui.py:
--------------------------------------------------------------------------------
1 | r"""``sphobjinv`` *CLI UI functions*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 19 Nov 2020
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import sys
33 |
34 | from sphobjinv.cli.parser import PrsConst
35 |
36 |
37 | def print_stderr(thing, params, *, end="\n"):
38 | r"""Print `thing` to stderr if not in quiet mode.
39 |
40 | Quiet mode is indicated by the value at the |cli:QUIET| key
41 | within `params`.
42 |
43 | Quiet mode is not implemented for the ":doc:`suggest `"
44 | CLI mode.
45 |
46 | Parameters
47 | ----------
48 | thing
49 |
50 | *any* -- Object to be printed
51 |
52 | params
53 |
54 | |dict| -- Parameters/values mapping from the active subparser
55 |
56 | end
57 |
58 | |str| -- String to append to printed content (default: ``\n``\ )
59 |
60 | """
61 | if params[PrsConst.SUBPARSER_NAME][:2] == "su" or not params[PrsConst.QUIET]:
62 | print(thing, file=sys.stderr, end=end)
63 |
64 |
65 | def err_format(exc):
66 | r"""Pretty-format an exception.
67 |
68 | Parameters
69 | ----------
70 | exc
71 |
72 | :class:`Exception` -- Exception instance to pretty-format
73 |
74 | Returns
75 | -------
76 | pretty_exc
77 |
78 | |str| -- Exception type and message formatted as
79 | |cour|\ '{type}: {message}'\ |/cour|
80 |
81 | """
82 | return f"{type(exc).__name__}: {str(exc)}"
83 |
84 |
85 | def yesno_prompt(prompt):
86 | r"""Query user at `stdin` for yes/no confirmation.
87 |
88 | Uses :func:`input`, so will hang if used programmatically
89 | unless `stdin` is suitably mocked.
90 |
91 | The value returned from :func:`input` must satisfy either
92 | |cour|\ resp.lower() == 'n'\ |/cour| or
93 | |cour|\ resp.lower() == 'y'\ |/cour|,
94 | or else the query will be repeated *ad infinitum*.
95 | This function does **NOT** augment `prompt`
96 | to indicate the constraints on the accepted values.
97 |
98 | Parameters
99 | ----------
100 | prompt
101 |
102 | |str| -- Prompt to display to user that
103 | requests a 'Y' or 'N' response
104 |
105 | Returns
106 | -------
107 | resp
108 |
109 | |str| -- User response
110 |
111 | """
112 | resp = ""
113 | while not (resp.lower() == "n" or resp.lower() == "y"):
114 | resp = input(prompt)
115 | return resp
116 |
--------------------------------------------------------------------------------
/src/sphobjinv/cli/write.py:
--------------------------------------------------------------------------------
1 | r"""*Module for* ``sphobjinv`` *CLI* |Inventory| *writing*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 19 Nov 2020
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import json
33 | import os
34 | import sys
35 |
36 | from sphobjinv.cli.parser import PrsConst
37 | from sphobjinv.cli.paths import resolve_outpath
38 | from sphobjinv.cli.ui import err_format, print_stderr, yesno_prompt
39 | from sphobjinv.fileops import writebytes, writejson
40 | from sphobjinv.zlib import compress
41 |
42 |
43 | def write_plaintext(inv, path, *, expand=False, contract=False):
44 | """Write an |Inventory| to plaintext.
45 |
46 | Newlines are inserted in an OS-aware manner,
47 | based on the value of :data:`os.linesep`.
48 |
49 | Calling with both `expand` and `contract` as |True| is invalid.
50 |
51 | Parameters
52 | ----------
53 | inv
54 |
55 | |Inventory| -- Objects inventory to be written as plaintext
56 |
57 | path
58 |
59 | |str| -- Path to output file
60 |
61 | expand
62 |
63 | |bool| *(optional)* -- Generate output with any
64 | :data:`~sphobjinv.data.SuperDataObj.uri` or
65 | :data:`~sphobjinv.data.SuperDataObj.dispname`
66 | abbreviations expanded
67 |
68 | contract
69 |
70 | |bool| *(optional)* -- Generate output with abbreviated
71 | :data:`~sphobjinv.data.SuperDataObj.uri` and
72 | :data:`~sphobjinv.data.SuperDataObj.dispname` values
73 |
74 | Raises
75 | ------
76 | ValueError
77 |
78 | If both `expand` and `contract` are |True|
79 |
80 | """
81 | b_str = inv.data_file(expand=expand, contract=contract)
82 | writebytes(path, b_str.replace(b"\n", os.linesep.encode("utf-8")))
83 |
84 |
85 | def write_zlib(inv, path, *, expand=False, contract=False):
86 | """Write an |Inventory| to zlib-compressed format.
87 |
88 | Calling with both `expand` and `contract` as |True| is invalid.
89 |
90 | Parameters
91 | ----------
92 | inv
93 |
94 | |Inventory| -- Objects inventory to be written zlib-compressed
95 |
96 | path
97 |
98 | |str| -- Path to output file
99 |
100 | expand
101 |
102 | |bool| *(optional)* -- Generate output with any
103 | :data:`~sphobjinv.data.SuperDataObj.uri` or
104 | :data:`~sphobjinv.data.SuperDataObj.dispname`
105 | abbreviations expanded
106 |
107 | contract
108 |
109 | |bool| *(optional)* -- Generate output with abbreviated
110 | :data:`~sphobjinv.data.SuperDataObj.uri` and
111 | :data:`~sphobjinv.data.SuperDataObj.dispname` values
112 |
113 | Raises
114 | ------
115 | ValueError
116 |
117 | If both `expand` and `contract` are |True|
118 |
119 | """
120 | b_str = inv.data_file(expand=expand, contract=contract)
121 | bz_str = compress(b_str)
122 | writebytes(path, bz_str)
123 |
124 |
125 | def write_json(inv, path, params):
126 | """Write an |Inventory| to JSON.
127 |
128 | Writes output via
129 | :func:`fileops.writejson() `.
130 |
131 | Calling with both `expand` and `contract` as |True| is invalid.
132 |
133 | Parameters
134 | ----------
135 | inv
136 |
137 | |Inventory| -- Objects inventory to be written zlib-compressed
138 |
139 | path
140 |
141 | |str| -- Path to output file
142 |
143 | params
144 |
145 | dict -- `argparse` parameters
146 |
147 | Raises
148 | ------
149 | ValueError
150 |
151 | If both `params["expand"]` and `params["contract"]` are |True|
152 |
153 | """
154 | json_dict = inv.json_dict(
155 | expand=params[PrsConst.EXPAND], contract=params[PrsConst.CONTRACT]
156 | )
157 |
158 | if params.get(PrsConst.FOUND_URL, False):
159 | json_dict.update({"metadata": {PrsConst.URL: params[PrsConst.FOUND_URL]}})
160 |
161 | writejson(path, json_dict)
162 |
163 |
164 | def write_stdout(inv, params):
165 | r"""Write the inventory contents to stdout.
166 |
167 | Parameters
168 | ----------
169 | inv
170 |
171 | |Inventory| -- Objects inventory to be written to stdout
172 |
173 | params
174 |
175 | dict -- `argparse` parameters
176 |
177 | Raises
178 | ------
179 | ValueError
180 |
181 | If both `params["expand"]` and `params["contract"]` are |True|
182 |
183 | """
184 | if params[PrsConst.MODE] == PrsConst.PLAIN:
185 | print(
186 | inv.data_file(
187 | expand=params[PrsConst.EXPAND], contract=params[PrsConst.CONTRACT]
188 | ).decode()
189 | )
190 | elif params[PrsConst.MODE] == PrsConst.JSON:
191 | json_dict = inv.json_dict(
192 | expand=params[PrsConst.EXPAND], contract=params[PrsConst.CONTRACT]
193 | )
194 |
195 | if params.get(PrsConst.FOUND_URL, False):
196 | json_dict.update({"metadata": {PrsConst.URL: params[PrsConst.FOUND_URL]}})
197 |
198 | print(json.dumps(json_dict))
199 | else:
200 | print_stderr("Error: Only plaintext and JSON can be emitted to stdout.", params)
201 | sys.exit(1)
202 |
203 |
204 | def write_file(inv, in_path, params):
205 | r"""Write the inventory contents to a file on disk.
206 |
207 | Parameters
208 | ----------
209 | inv
210 |
211 | |Inventory| -- Objects inventory to be written to stdout
212 |
213 | in_path
214 |
215 | |str| -- For a local input file, its absolute path.
216 | For a URL, the (possibly truncated) URL text.
217 |
218 | params
219 |
220 | dict -- `argparse` parameters
221 |
222 | Raises
223 | ------
224 | ValueError
225 |
226 | If both `params["expand"]` and `params["contract"]` are |True|
227 |
228 | """
229 | mode = params[PrsConst.MODE]
230 |
231 | # Work up the output location
232 | try:
233 | out_path = resolve_outpath(params[PrsConst.OUTFILE], in_path, params)
234 | except Exception as e: # pragma: no cover
235 | # This may not actually be reachable except in exceptional situations
236 | print_stderr("\nError while constructing output file path:", params)
237 | print_stderr(err_format(e), params)
238 | sys.exit(1)
239 |
240 | # If exists, must handle overwrite
241 | if os.path.isfile(out_path) and not params[PrsConst.OVERWRITE]:
242 | if params[PrsConst.INFILE] == "-":
243 | # If reading from stdin, just alert and don't overwrite
244 | print_stderr("\nFile exists. To overwrite, supply '-o'. Exiting...", params)
245 | sys.exit(0)
246 | # This could be written w/o nesting via elif, but would be harder to read.
247 | else:
248 | if not params[PrsConst.QUIET]:
249 | # If not a stdin read, confirm overwrite; or, just clobber if QUIET
250 | resp = yesno_prompt("File exists. Overwrite (Y/N)? ")
251 | if resp.lower() == "n":
252 | print_stderr("\nExiting...", params)
253 | sys.exit(0)
254 |
255 | # Write the output file
256 | try:
257 | if mode == PrsConst.ZLIB:
258 | write_zlib(
259 | inv,
260 | out_path,
261 | expand=params[PrsConst.EXPAND],
262 | contract=params[PrsConst.CONTRACT],
263 | )
264 | if mode == PrsConst.PLAIN:
265 | write_plaintext(
266 | inv,
267 | out_path,
268 | expand=params[PrsConst.EXPAND],
269 | contract=params[PrsConst.CONTRACT],
270 | )
271 | if mode == PrsConst.JSON:
272 | write_json(inv, out_path, params)
273 | except Exception as e:
274 | print_stderr("\nError during write of output file:", params)
275 | print_stderr(err_format(e), params)
276 | sys.exit(1)
277 |
278 | # Report success, if not QUIET
279 | print_stderr(
280 | "Conversion completed.\n"
281 | f"'{in_path if in_path else 'stdin'}' converted to '{out_path}' ({mode}).",
282 | params,
283 | )
284 |
--------------------------------------------------------------------------------
/src/sphobjinv/enum.py:
--------------------------------------------------------------------------------
1 | r"""*Helper enums for* ``sphobjinv``.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 4 May 2019
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | from enum import Enum
33 |
34 |
35 | class SourceTypes(Enum):
36 | """|Enum| for the import mode used in instantiating an |Inventory|.
37 |
38 | Since |Enum| keys iterate in definition order, the
39 | definition order here defines the order in which |Inventory|
40 | objects attempt to parse a source object passed to
41 | :class:`Inventory.__init__() `
42 | either as a positional argument
43 | or via the generic `source` keyword argument.
44 |
45 | This order **DIFFERS** from the documentation order, which is
46 | alphabetical.
47 |
48 | """
49 |
50 | #: No source; |Inventory| was instantiated with
51 | #: :data:`~sphobjinv.inventory.Inventory.project`
52 | #: and :data:`~sphobjinv.inventory.Inventory.version`
53 | #: as empty strings and
54 | #: :data:`~sphobjinv.inventory.Inventory.objects` as an empty |list|.
55 | Manual = "manual"
56 |
57 | #: Instantiation from a plaintext |objects.inv| |bytes|.
58 | BytesPlaintext = "bytes_plain"
59 |
60 | #: Instantiation from a zlib-compressed
61 | #: |objects.inv| |bytes|.
62 | BytesZlib = "bytes_zlib"
63 |
64 | #: Instantiation from a plaintext |objects.inv| file on disk.
65 | FnamePlaintext = "fname_plain"
66 |
67 | #: Instantiation from a zlib-compressed |objects.inv| file on disk.
68 | FnameZlib = "fname_zlib"
69 |
70 | #: Instantiation from a |dict| validated against
71 | #: :data:`schema.json_schema `.
72 | DictJSON = "dict_json"
73 |
74 | #: Instantiation from a zlib-compressed |objects.inv| file
75 | #: downloaded from a URL.
76 | URL = "url"
77 |
78 |
79 | class HeaderFields(Enum):
80 | """|Enum| for various inventory-level data items.
81 |
82 | A subset of these |Enum| values is used in various Regex,
83 | JSON, and string formatting contexts within
84 | class:`~sphobjinv.inventory.Inventory`
85 | and :data:`schema.json_schema `.
86 |
87 | """
88 |
89 | #: Project name associated with an inventory
90 | Project = "project"
91 |
92 | #: Project version associated with an inventory
93 | Version = "version"
94 |
95 | #: Number of objects contained in the inventory
96 | Count = "count"
97 |
98 | #: The |str| value of this |Enum| member is accepted as a root-level
99 | #: key in a |dict| to be imported into an
100 | #: :class:`~sphobjinv.inventory.Inventory`.
101 | #: The corresponding value in the |dict| may contain any arbitrary data.
102 | #: Its possible presence is accounted for in
103 | #: :data:`schema.json_schema `.
104 | #:
105 | #: The data associated with this key are **ignored**
106 | #: during import into an
107 | #: :class:`~sphobjinv.inventory.Inventory`.
108 | Metadata = "metadata"
109 |
--------------------------------------------------------------------------------
/src/sphobjinv/error.py:
--------------------------------------------------------------------------------
1 | r"""*Custom errors for* ``sphobjinv``.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 5 Nov 2017
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 |
33 | class SphobjinvError(Exception):
34 | """Custom ``sphobjinv`` error superclass."""
35 |
36 |
37 | class VersionError(SphobjinvError):
38 | """Raised when attempting an operation on an unsupported version.
39 |
40 | The current version of ``sphobjinv`` only supports 'version 2'
41 | |objects.inv| files (see :doc:`here `).
42 |
43 | """
44 |
45 | # TODO: Add SOI prefix to this class name as part of the exceptions refactor
46 |
--------------------------------------------------------------------------------
/src/sphobjinv/fileops.py:
--------------------------------------------------------------------------------
1 | r"""*File I/O helpers for* ``sphobjinv``.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 5 Nov 2017
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import json
33 | from pathlib import Path
34 |
35 |
36 | def readbytes(path):
37 | """Read file contents and return as |bytes|.
38 |
39 | .. versionchanged:: 2.1
40 |
41 | `path` can now be |Path| or |str|. Previously, it had to be |str|.
42 |
43 | Parameters
44 | ----------
45 | path
46 |
47 | |str| or |Path| -- Path to file to be opened.
48 |
49 | Returns
50 | -------
51 | b
52 |
53 | |bytes| -- Contents of the indicated file.
54 |
55 | """
56 | return Path(path).read_bytes()
57 |
58 |
59 | def writebytes(path, contents):
60 | """Write indicated file contents.
61 |
62 | Any existing file at `path` will be overwritten.
63 |
64 | .. versionchanged:: 2.1
65 |
66 | `path` can now be |Path| or |str|. Previously, it had to be |str|.
67 |
68 | Parameters
69 | ----------
70 | path
71 |
72 | |str| or |Path| -- Path to file to be written.
73 |
74 | contents
75 |
76 | |bytes| -- Content to be written to file.
77 |
78 | """
79 | Path(path).write_bytes(contents)
80 |
81 |
82 | def readjson(path):
83 | """Create |dict| from JSON file.
84 |
85 | No data or schema validation is performed.
86 |
87 | .. versionchanged:: 2.1
88 |
89 | `path` can now be |Path| or |str|. Previously, it had to be |str|.
90 |
91 | Parameters
92 | ----------
93 | path
94 |
95 | |str| or |Path| -- Path to JSON file to be read.
96 |
97 | Returns
98 | -------
99 | d
100 |
101 | |dict| -- Deserialized JSON.
102 |
103 | """
104 | return json.loads(Path(path).read_text())
105 |
106 |
107 | def writejson(path, d):
108 | """Create JSON file from |dict|.
109 |
110 | No data or schema validation is performed.
111 | Any existing file at `path` will be overwritten.
112 |
113 | .. versionchanged:: 2.1
114 |
115 | `path` can now be |Path| or |str|. Previously, it had to be |str|.
116 |
117 | Parameters
118 | ----------
119 | path
120 |
121 | |str| or |Path| -- Path to output JSON file.
122 |
123 | d
124 |
125 | |dict| -- Data structure to serialize.
126 |
127 | """
128 | Path(path).write_text(json.dumps(d))
129 |
130 |
131 | def urlwalk(url):
132 | r"""Generate a series of candidate |objects.inv| URLs.
133 |
134 | URLs are based on the seed `url` passed in. Ensure that the
135 | path separator in `url` is the standard **forward** slash
136 | ('|cour|\ /\ |/cour|').
137 |
138 | Parameters
139 | ----------
140 | url
141 |
142 | |str| -- Seed URL defining directory structure to walk through.
143 |
144 | Yields
145 | ------
146 | inv_url
147 |
148 | |str| -- Candidate URL for |objects.inv| location.
149 |
150 | """
151 | # Scrub any anchor, as it fouls things
152 | url = url.partition("#")[0]
153 |
154 | urlparts = url.rstrip("/").split("/")
155 |
156 | # This loop condition results in the yielded values stopping at
157 | # 'http[s]://domain.com/objects.inv', since the URL protocol
158 | # specifier has two forward slashes
159 | while len(urlparts) >= 3:
160 | urlparts.append("objects.inv")
161 | yield "/".join(urlparts)
162 | urlparts.pop()
163 | urlparts.pop()
164 |
--------------------------------------------------------------------------------
/src/sphobjinv/re.py:
--------------------------------------------------------------------------------
1 | r"""*Helper regexes for* ``sphobjinv``.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 5 Nov 2017
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import re
33 |
34 | from sphobjinv.data import DataFields as DF # noqa: N817
35 | from sphobjinv.enum import HeaderFields as HF # noqa: N817
36 |
37 |
38 | #: Compiled |re| |bytes| pattern for comment lines in decompressed
39 | #: inventory files
40 | pb_comments = re.compile(b"^#.*$", re.M)
41 |
42 | #: Compiled |re| |bytes| pattern for project line
43 | pb_project = re.compile(
44 | rf"""
45 | ^ # Start of line
46 | [#][ ]Project:[ ] # Preamble
47 | (?P<{HF.Project.value}>.*?) # Lazy rest of line is project name
48 | \r?$ # Ignore possible CR at EOL
49 | """.encode(
50 | encoding="utf-8"
51 | ),
52 | re.M | re.X,
53 | )
54 |
55 | #: Compiled |re| |bytes| pattern for version line
56 | pb_version = re.compile(
57 | rf"""
58 | ^ # Start of line
59 | [#][ ]Version:[ ] # Preamble
60 | (?P<{HF.Version.value}>.*?) # Lazy rest of line is version
61 | \r?$ # Ignore possible CR at EOL
62 | """.encode(
63 | encoding="utf-8"
64 | ),
65 | re.M | re.X,
66 | )
67 |
68 | #: Regex pattern string used to compile
69 | #: :data:`~sphobjinv.re.p_data` and
70 | #: :data:`~sphobjinv.re.pb_data`
71 | ptn_data = rf"""
72 | ^ # Start of line
73 | (?P<{DF.Name.value}>.+?) # --> Name
74 | \s+ # Dividing space
75 | (?P<{DF.Domain.value}>[^\s:]+) # --> Domain
76 | : # Dividing colon
77 | (?P<{DF.Role.value}>[^\s]+) # --> Role
78 | \s+ # Dividing space
79 | (?P<{DF.Priority.value}>-?\d+) # --> Priority
80 | \s+? # Dividing space
81 | (?P<{DF.URI.value}>\S*) # --> URI
82 | \s+ # Dividing space
83 | (?P<{DF.DispName.value}>.+?) # --> Display name, lazy b/c possible CR
84 | \r?$ # Ignore possible CR at EOL
85 | """
86 |
87 | #: Compiled |re| |bytes| regex pattern for data lines in |bytes| decompressed
88 | #: inventory files
89 | pb_data = re.compile(ptn_data.encode(encoding="utf-8"), re.M | re.X)
90 |
91 | #: Compiled |re| |str| regex pattern for data lines in |str| decompressed
92 | #: inventory files
93 | p_data = re.compile(ptn_data, re.M | re.X)
94 |
--------------------------------------------------------------------------------
/src/sphobjinv/schema.py:
--------------------------------------------------------------------------------
1 | r"""*JSON schema to validate inventory dictionaries*.
2 |
3 | This module is part of ``sphobjinv``,
4 | a toolkit for manipulation and inspection of
5 | Sphinx |objects.inv| files.
6 |
7 | **Author**
8 | Brian Skinn (brian.skinn@gmail.com)
9 |
10 | **File Created**
11 | 7 Dec 2017
12 |
13 | **Copyright**
14 | \(c) Brian Skinn 2016-2025
15 |
16 | **Source Repository**
17 | https://github.com/bskinn/sphobjinv
18 |
19 | **Documentation**
20 | https://sphobjinv.readthedocs.io/en/stable
21 |
22 | **License**
23 | Code: `MIT License`_
24 |
25 | Docs & Docstrings: |CC BY 4.0|_
26 |
27 | See |license_txt|_ for full license terms.
28 |
29 | **Members**
30 |
31 | """
32 |
33 | # For jsonschema Draft 4.
34 | # Schemas are defined with static field names as a versioning
35 | # guarantee, instead of basing them dynamically on DataFields, etc.
36 |
37 | # JSON dict schema
38 | # Subschema for the inner data, both for clarity and to make it
39 | # possible to satisfy flake8
40 | subschema_json = {
41 | "name": {"type": "string"},
42 | "domain": {"type": "string"},
43 | "role": {"type": "string"},
44 | "priority": {"type": "string"},
45 | "uri": {"type": "string"},
46 | "dispname": {"type": "string"},
47 | }
48 |
49 | #: JSON schema for validating the |dict| forms of
50 | #: Sphinx |objects.inv| inventories
51 | #: as generated from or expected by
52 | #: :class:`~sphobjinv.inventory.Inventory` classes.
53 | json_schema = {
54 | "$schema": "https://json-schema.org/draft-04/schema#",
55 | "type": "object",
56 | "properties": {
57 | "project": {"type": "string"},
58 | "version": {"type": "string"},
59 | "count": {"type": "integer"},
60 | "metadata": {},
61 | },
62 | "patternProperties": {
63 | r"^\d+": {
64 | "type": "object",
65 | "properties": subschema_json,
66 | "additionalProperties": False,
67 | "required": list(subschema_json),
68 | }
69 | },
70 | "additionalProperties": False,
71 | "required": ["project", "version", "count"],
72 | }
73 |
--------------------------------------------------------------------------------
/src/sphobjinv/version.py:
--------------------------------------------------------------------------------
1 | r"""``sphobjinv`` *version definition module*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 18 Mar 2019
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | __version__ = "2.3.2.dev0"
33 |
--------------------------------------------------------------------------------
/src/sphobjinv/zlib.py:
--------------------------------------------------------------------------------
1 | r"""*zlib (de)compression helpers for* ``sphobjinv``.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 5 Nov 2017
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | https://github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import io
33 | import os
34 | import zlib
35 |
36 |
37 | BUFSIZE = 16 * 1024 # 16k chunks
38 |
39 |
40 | def decompress(bstr):
41 | """Decompress a version 2 |isphx| |objects.inv| bytestring.
42 |
43 | The `#`-prefixed comment lines are left unchanged, whereas the
44 | :mod:`zlib`-compressed data lines are decompressed to plaintext.
45 |
46 | Parameters
47 | ----------
48 | bstr
49 |
50 | |bytes| -- Binary string containing a compressed |objects.inv|
51 | file.
52 |
53 | Returns
54 | -------
55 | out_b
56 |
57 | |bytes| -- Decompressed binary string containing the plaintext
58 | |objects.inv| content.
59 |
60 | """
61 | from sphobjinv.error import VersionError
62 |
63 | def decompress_chunks(bstrm):
64 | """Handle chunk-wise zlib decompression.
65 |
66 | Internal function pulled from intersphinx.py@v1.4.1:
67 | https://github.com/sphinx-doc/sphinx/blob/1.4.1/sphinx/
68 | ext/intersphinx.py#L79-L124.
69 |
70 | BUFSIZE taken as the default value from intersphinx signature
71 | Modified slightly to take the stream as a parameter,
72 | rather than assuming one from the parent namespace.
73 |
74 | """
75 | decompressor = zlib.decompressobj()
76 | for chunk in iter(lambda: bstrm.read(BUFSIZE), b""):
77 | yield decompressor.decompress(chunk)
78 | yield decompressor.flush()
79 |
80 | # Make stream and output string
81 | strm = io.BytesIO(bstr)
82 |
83 | # Check to be sure it's v2
84 | out_b = strm.readline()
85 | if not out_b.endswith(b"2\n"): # pragma: no cover
86 | raise VersionError("Only v2 objects.inv files currently supported")
87 |
88 | # Pull name, version, and description lines
89 | for _ in range(3):
90 | out_b += strm.readline()
91 |
92 | # Decompress chunks and append
93 | for chunk in decompress_chunks(strm):
94 | out_b += chunk
95 |
96 | # Replace newlines with the OS-local newlines, and return
97 | return out_b.replace(b"\n", os.linesep.encode("utf-8"))
98 |
99 |
100 | def compress(bstr):
101 | """Compress a version 2 |isphx| |objects.inv| bytestring.
102 |
103 | The `#`-prefixed comment lines are left unchanged, whereas the
104 | plaintext data lines are compressed with :mod:`zlib`.
105 |
106 | Parameters
107 | ----------
108 | bstr
109 |
110 | |bytes| -- Binary string containing the decompressed contents of an
111 | |objects.inv| file.
112 |
113 | Returns
114 | -------
115 | out_b
116 |
117 | |bytes| -- Binary string containing the compressed |objects.inv|
118 | content.
119 |
120 | """
121 | from sphobjinv.re import pb_comments, pb_data
122 |
123 | # Preconvert any DOS newlines to Unix
124 | s = bstr.replace(b"\r\n", b"\n")
125 |
126 | # Pull all of the lines
127 | m_comments = pb_comments.findall(s)
128 | m_data = pb_data.finditer(s)
129 |
130 | # Assemble the binary header comments and data
131 | # Comments and data blocks must end in newlines
132 | hb = b"\n".join(m_comments) + b"\n"
133 | db = b"\n".join(_.group(0) for _ in m_data) + b"\n"
134 |
135 | # Compress the data block
136 | # Compression level nine is to match that specified in
137 | # sphinx html builder:
138 | # https://github.com/sphinx-doc/sphinx/blob/1.4.1/sphinx/
139 | # builders/html.py#L843
140 | dbc = zlib.compress(db, 9)
141 |
142 | # Return the composited bytestring
143 | return hb + dbc
144 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | """Blank __init__.py for pytest namespace isolation."""
2 |
--------------------------------------------------------------------------------
/tests/resource/objects_NAPALM.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_NAPALM.inv
--------------------------------------------------------------------------------
/tests/resource/objects_attrs.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_attrs.inv
--------------------------------------------------------------------------------
/tests/resource/objects_attrs.txt:
--------------------------------------------------------------------------------
1 | # Sphinx inventory version 2
2 | # Project: attrs
3 | # Version: 22.1
4 | # The remainder of this file is compressed using zlib.
5 | attr py:module 0 index.html#module-$ -
6 | attr.VersionInfo py:class 1 api.html#$ -
7 | attr._make.Attribute py:class -1 api.html#attrs.Attribute -
8 | attr._make.Factory py:class -1 api.html#attrs.Factory -
9 | attr._version_info.VersionInfo py:class -1 api.html#attr.VersionInfo -
10 | attr.asdict py:function 1 api.html#$ -
11 | attr.assoc py:function 1 api.html#$ -
12 | attr.astuple py:function 1 api.html#$ -
13 | attr.attr.NOTHING py:data 1 api.html#$ -
14 | attr.attr.cmp_using py:function 1 api.html#$ -
15 | attr.attr.evolve py:function 1 api.html#$ -
16 | attr.attr.fields py:function 1 api.html#$ -
17 | attr.attr.fields_dict py:function 1 api.html#$ -
18 | attr.attr.filters.exclude py:function 1 api.html#$ -
19 | attr.attr.filters.include py:function 1 api.html#$ -
20 | attr.attr.has py:function 1 api.html#$ -
21 | attr.attr.resolve_types py:function 1 api.html#$ -
22 | attr.attr.validate py:function 1 api.html#$ -
23 | attr.attrs.frozen py:function 1 api.html#$ -
24 | attr.attrs.mutable py:function 1 api.html#$ -
25 | attr.attrs.setters.NO_OP py:data 1 api.html#$ -
26 | attr.define py:function 1 api.html#$ -
27 | attr.exceptions.AttrsAttributeNotFoundError py:exception -1 api.html#attrs.exceptions.AttrsAttributeNotFoundError -
28 | attr.exceptions.DefaultAlreadySetError py:exception -1 api.html#attrs.exceptions.DefaultAlreadySetError -
29 | attr.exceptions.FrozenAttributeError py:exception -1 api.html#attrs.exceptions.FrozenAttributeError -
30 | attr.exceptions.FrozenError py:exception -1 api.html#attrs.exceptions.FrozenError -
31 | attr.exceptions.FrozenInstanceError py:exception -1 api.html#attrs.exceptions.FrozenInstanceError -
32 | attr.exceptions.NotAnAttrsClassError py:exception -1 api.html#attrs.exceptions.NotAnAttrsClassError -
33 | attr.exceptions.NotCallableError py:exception -1 api.html#attrs.exceptions.NotCallableError -
34 | attr.exceptions.PythonTooOldError py:exception -1 api.html#attrs.exceptions.PythonTooOldError -
35 | attr.exceptions.UnannotatedAttributeError py:exception -1 api.html#attrs.exceptions.UnannotatedAttributeError -
36 | attr.field py:function 1 api.html#$ -
37 | attr.frozen py:function 1 api.html#$ -
38 | attr.get_run_validators py:function 1 api.html#$ -
39 | attr.ib py:function 1 api.html#$ -
40 | attr.mutable py:function 1 api.html#$ -
41 | attr.s py:function 1 api.html#$ -
42 | attr.set_run_validators py:function 1 api.html#$ -
43 | attrs py:module 0 index.html#module-$ -
44 | attrs.Attribute py:class 1 api.html#$ -
45 | attrs.Attribute.evolve py:method 1 api.html#$ -
46 | attrs.Factory py:class 1 api.html#$ -
47 | attrs.NOTHING py:data 1 api.html#$ -
48 | attrs.asdict py:function 1 api.html#$ -
49 | attrs.astuple py:function 1 api.html#$ -
50 | attrs.cmp_using py:function 1 api.html#$ -
51 | attrs.converters.default_if_none py:function 1 api.html#$ -
52 | attrs.converters.optional py:function 1 api.html#$ -
53 | attrs.converters.pipe py:function 1 api.html#$ -
54 | attrs.converters.to_bool py:function 1 api.html#$ -
55 | attrs.define py:function 1 api.html#$ -
56 | attrs.evolve py:function 1 api.html#$ -
57 | attrs.exceptions.AttrsAttributeNotFoundError py:exception 1 api.html#$ -
58 | attrs.exceptions.DefaultAlreadySetError py:exception 1 api.html#$ -
59 | attrs.exceptions.FrozenAttributeError py:exception 1 api.html#$ -
60 | attrs.exceptions.FrozenError py:exception 1 api.html#$ -
61 | attrs.exceptions.FrozenInstanceError py:exception 1 api.html#$ -
62 | attrs.exceptions.NotAnAttrsClassError py:exception 1 api.html#$ -
63 | attrs.exceptions.NotCallableError py:exception 1 api.html#$ -
64 | attrs.exceptions.PythonTooOldError py:exception 1 api.html#$ -
65 | attrs.exceptions.UnannotatedAttributeError py:exception 1 api.html#$ -
66 | attrs.field py:function 1 api.html#$ -
67 | attrs.fields py:function 1 api.html#$ -
68 | attrs.fields_dict py:function 1 api.html#$ -
69 | attrs.filters.exclude py:function 1 api.html#$ -
70 | attrs.filters.include py:function 1 api.html#$ -
71 | attrs.has py:function 1 api.html#$ -
72 | attrs.make_class py:function 1 api.html#$ -
73 | attrs.resolve_types py:function 1 api.html#$ -
74 | attrs.setters.convert py:function 1 api.html#$ -
75 | attrs.setters.frozen py:function 1 api.html#$ -
76 | attrs.setters.pipe py:function 1 api.html#$ -
77 | attrs.setters.validate py:function 1 api.html#$ -
78 | attrs.validate py:function 1 api.html#$ -
79 | attrs.validators.and_ py:function 1 api.html#$ -
80 | attrs.validators.deep_iterable py:function 1 api.html#$ -
81 | attrs.validators.deep_mapping py:function 1 api.html#$ -
82 | attrs.validators.disabled py:function 1 api.html#$ -
83 | attrs.validators.ge py:function 1 api.html#$ -
84 | attrs.validators.get_disabled py:function 1 api.html#$ -
85 | attrs.validators.gt py:function 1 api.html#$ -
86 | attrs.validators.in_ py:function 1 api.html#$ -
87 | attrs.validators.instance_of py:function 1 api.html#$ -
88 | attrs.validators.is_callable py:function 1 api.html#$ -
89 | attrs.validators.le py:function 1 api.html#$ -
90 | attrs.validators.lt py:function 1 api.html#$ -
91 | attrs.validators.matches_re py:function 1 api.html#$ -
92 | attrs.validators.max_len py:function 1 api.html#$ -
93 | attrs.validators.min_len py:function 1 api.html#$ -
94 | attrs.validators.optional py:function 1 api.html#$ -
95 | attrs.validators.provides py:function 1 api.html#$ -
96 | attrs.validators.set_disabled py:function 1 api.html#$ -
97 | api std:doc -1 api.html API Reference
98 | api_setters std:label -1 api.html#api-setters Setters
99 | api_validators std:label -1 api.html#api-validators Validators
100 | asdict std:label -1 examples.html#$ Converting to Collections Types
101 | changelog std:doc -1 changelog.html Changelog
102 | comparison std:doc -1 comparison.html Comparison
103 | converters std:label -1 init.html#$ Converters
104 | custom-comparison std:label -1 comparison.html#$ Customization
105 | dict classes std:term -1 glossary.html#term-dict-classes -
106 | dunder methods std:term -1 glossary.html#term-dunder-methods -
107 | examples std:doc -1 examples.html attrs by Example
108 | examples_validators std:label -1 examples.html#examples-validators Validators
109 | extending std:doc -1 extending.html Extending
110 | extending_metadata std:label -1 extending.html#extending-metadata Metadata
111 | genindex std:label -1 genindex.html Index
112 | glossary std:doc -1 glossary.html Glossary
113 | hashing std:doc -1 hashing.html Hashing
114 | helpers std:label -1 api.html#$ Helpers
115 | how std:label -1 how-does-it-work.html#$ How Does It Work?
116 | how-does-it-work std:doc -1 how-does-it-work.html How Does It Work?
117 | how-frozen std:label -1 how-does-it-work.html#$ Immutability
118 | index std:doc -1 index.html attrs: Classes Without Boilerplate
119 | init std:doc -1 init.html Initialization
120 | license std:doc -1 license.html License and Credits
121 | metadata std:label -1 examples.html#$ Metadata
122 | modindex std:label -1 py-modindex.html Module Index
123 | names std:doc -1 names.html On The Core API Names
124 | overview std:doc -1 overview.html Overview
125 | philosophy std:label -1 overview.html#$ Philosophy
126 | py-modindex std:label -1 py-modindex.html Python Module Index
127 | search std:label -1 search.html Search Page
128 | slotted classes std:term -1 glossary.html#term-slotted-classes -
129 | transform-fields std:label -1 extending.html#$ Automatic Field Transformation and Modification
130 | types std:doc -1 types.html Type Annotations
131 | validators std:label -1 init.html#$ Validators
132 | version-info std:label -1 api.html#$ -
133 | why std:doc -1 why.html Why not…
134 |
--------------------------------------------------------------------------------
/tests/resource/objects_attrs_17_2_0.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_attrs_17_2_0.inv
--------------------------------------------------------------------------------
/tests/resource/objects_attrs_20_3_0.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_attrs_20_3_0.inv
--------------------------------------------------------------------------------
/tests/resource/objects_beaker.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_beaker.inv
--------------------------------------------------------------------------------
/tests/resource/objects_bokeh.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_bokeh.inv
--------------------------------------------------------------------------------
/tests/resource/objects_bootstrap_datepicker.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_bootstrap_datepicker.inv
--------------------------------------------------------------------------------
/tests/resource/objects_cclib.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_cclib.inv
--------------------------------------------------------------------------------
/tests/resource/objects_celery.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_celery.inv
--------------------------------------------------------------------------------
/tests/resource/objects_click.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_click.inv
--------------------------------------------------------------------------------
/tests/resource/objects_cookiecutter.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_cookiecutter.inv
--------------------------------------------------------------------------------
/tests/resource/objects_coverage.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_coverage.inv
--------------------------------------------------------------------------------
/tests/resource/objects_django.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_django.inv
--------------------------------------------------------------------------------
/tests/resource/objects_django_channels.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_django_channels.inv
--------------------------------------------------------------------------------
/tests/resource/objects_eyeD3.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_eyeD3.inv
--------------------------------------------------------------------------------
/tests/resource/objects_faker.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_faker.inv
--------------------------------------------------------------------------------
/tests/resource/objects_flake8.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_flake8.inv
--------------------------------------------------------------------------------
/tests/resource/objects_flask.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_flask.inv
--------------------------------------------------------------------------------
/tests/resource/objects_fonttools.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_fonttools.inv
--------------------------------------------------------------------------------
/tests/resource/objects_gspread.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_gspread.inv
--------------------------------------------------------------------------------
/tests/resource/objects_h5py.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_h5py.inv
--------------------------------------------------------------------------------
/tests/resource/objects_hypothesis.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_hypothesis.inv
--------------------------------------------------------------------------------
/tests/resource/objects_jinja2.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_jinja2.inv
--------------------------------------------------------------------------------
/tests/resource/objects_jsonschema.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_jsonschema.inv
--------------------------------------------------------------------------------
/tests/resource/objects_matplotlib.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_matplotlib.inv
--------------------------------------------------------------------------------
/tests/resource/objects_mdn.bad_inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_mdn.bad_inv
--------------------------------------------------------------------------------
/tests/resource/objects_mistune.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_mistune.inv
--------------------------------------------------------------------------------
/tests/resource/objects_mkdoc_zlib0.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_mkdoc_zlib0.inv
--------------------------------------------------------------------------------
/tests/resource/objects_mypy.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_mypy.inv
--------------------------------------------------------------------------------
/tests/resource/objects_nltk.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_nltk.inv
--------------------------------------------------------------------------------
/tests/resource/objects_noinfo.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_noinfo.inv
--------------------------------------------------------------------------------
/tests/resource/objects_noproject.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_noproject.inv
--------------------------------------------------------------------------------
/tests/resource/objects_numpy.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_numpy.inv
--------------------------------------------------------------------------------
/tests/resource/objects_opencv.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_opencv.inv
--------------------------------------------------------------------------------
/tests/resource/objects_pandas.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pandas.inv
--------------------------------------------------------------------------------
/tests/resource/objects_pdfminer.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pdfminer.inv
--------------------------------------------------------------------------------
/tests/resource/objects_pelican.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pelican.inv
--------------------------------------------------------------------------------
/tests/resource/objects_pingo.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pingo.inv
--------------------------------------------------------------------------------
/tests/resource/objects_plone.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_plone.inv
--------------------------------------------------------------------------------
/tests/resource/objects_psutil.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_psutil.inv
--------------------------------------------------------------------------------
/tests/resource/objects_pyexcel.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pyexcel.inv
--------------------------------------------------------------------------------
/tests/resource/objects_pygame.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pygame.inv
--------------------------------------------------------------------------------
/tests/resource/objects_pymongo.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pymongo.inv
--------------------------------------------------------------------------------
/tests/resource/objects_pyqt.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pyqt.inv
--------------------------------------------------------------------------------
/tests/resource/objects_pyserial.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pyserial.inv
--------------------------------------------------------------------------------
/tests/resource/objects_pytest.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pytest.inv
--------------------------------------------------------------------------------
/tests/resource/objects_python.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_python.inv
--------------------------------------------------------------------------------
/tests/resource/objects_requests.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_requests.inv
--------------------------------------------------------------------------------
/tests/resource/objects_rocket.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_rocket.inv
--------------------------------------------------------------------------------
/tests/resource/objects_sarge.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sarge.inv
--------------------------------------------------------------------------------
/tests/resource/objects_scapy.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_scapy.inv
--------------------------------------------------------------------------------
/tests/resource/objects_scipy.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_scipy.inv
--------------------------------------------------------------------------------
/tests/resource/objects_scrapy.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_scrapy.inv
--------------------------------------------------------------------------------
/tests/resource/objects_sklearn.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sklearn.inv
--------------------------------------------------------------------------------
/tests/resource/objects_sphinx.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sphinx.inv
--------------------------------------------------------------------------------
/tests/resource/objects_sphinx_1_6_6.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sphinx_1_6_6.inv
--------------------------------------------------------------------------------
/tests/resource/objects_sqlalchemy.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sqlalchemy.inv
--------------------------------------------------------------------------------
/tests/resource/objects_sympy.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sympy.inv
--------------------------------------------------------------------------------
/tests/resource/objects_tinydb.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_tinydb.inv
--------------------------------------------------------------------------------
/tests/resource/objects_tox.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_tox.inv
--------------------------------------------------------------------------------
/tests/resource/objects_twython.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_twython.inv
--------------------------------------------------------------------------------
/tests/resource/objects_yt.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_yt.inv
--------------------------------------------------------------------------------
/tests/test_api_good_nonlocal.py:
--------------------------------------------------------------------------------
1 | r"""*Direct, NONLOCAL expect-good API tests for* ``sphobjinv``.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 21 Mar 2019
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | http://www.github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import pytest
33 |
34 | import sphobjinv as soi
35 |
36 |
37 | pytestmark = [
38 | pytest.mark.api,
39 | pytest.mark.nonloc,
40 | pytest.mark.flaky(retries=2, delay=5),
41 | ]
42 |
43 |
44 | @pytest.fixture(scope="module", autouse=True)
45 | def skip_if_no_nonloc(pytestconfig):
46 | """Skip test if --nonloc not provided.
47 |
48 | Auto-applied to all functions in module, since module is nonlocal.
49 |
50 | """
51 | if not pytestconfig.getoption("--nonloc"):
52 | pytest.skip("'--nonloc' not specified") # pragma: no cover
53 |
54 |
55 | @pytest.mark.parametrize(
56 | ["name", "url"],
57 | [
58 | ("flask", "http://flask.palletsprojects.com/en/1.1.x/objects.inv"),
59 | ("h5py", "https://docs.h5py.org/en/stable/objects.inv"),
60 | ],
61 | ids=(lambda x: "" if "://" in x else x),
62 | )
63 | @pytest.mark.timeout(30)
64 | def test_api_inventory_known_header_required(name, url):
65 | """Confirm URL load works on docs pages requiring HTTP header config."""
66 | inv = soi.Inventory(url=url)
67 | assert inv.count > 0
68 |
69 |
70 | @pytest.mark.testall
71 | @pytest.mark.timeout(30)
72 | def test_api_inventory_many_url_imports(
73 | testall_inv_path,
74 | res_path,
75 | scratch_path,
76 | misc_info,
77 | sphinx_load_test,
78 | pytestconfig,
79 | ):
80 | """Confirm a plethora of .inv files downloads properly via url arg.
81 |
82 | This test is SLOW, and so does not run by default. Invoke with `--nonloc`
83 | to run it; invoke with `--testall` to test over all .inv files in
84 | tests/resource.
85 |
86 | """
87 | fname = testall_inv_path.name
88 | scr_fpath = scratch_path / fname
89 |
90 | # Drop most unless testall
91 | if not pytestconfig.getoption("--testall") and fname != "objects_attrs.inv":
92 | pytest.skip("'--testall' not specified")
93 |
94 | # Construct inventories for comparison
95 | mch = misc_info.p_inv.match(fname)
96 | proj_name = mch.group(1)
97 | inv1 = soi.Inventory(str(res_path / fname))
98 | inv2 = soi.Inventory(url=misc_info.remote_url.format(proj_name))
99 |
100 | # Test the things
101 | assert inv1 == inv2
102 |
103 | # Ensure sphinx likes the regenerated inventory
104 | data = inv2.data_file()
105 | cmp_data = soi.compress(data)
106 | soi.writebytes(scr_fpath, cmp_data)
107 | sphinx_load_test(scr_fpath)
108 |
--------------------------------------------------------------------------------
/tests/test_cli_nonlocal.py:
--------------------------------------------------------------------------------
1 | r"""*Nonlocal CLI tests for* ``sphobjinv``.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 20 Mar 2019
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | http://www.github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import json
33 | import re
34 |
35 | import pytest
36 | from stdio_mgr import stdio_mgr
37 |
38 | from sphobjinv import Inventory
39 |
40 | CLI_TEST_TIMEOUT = 5
41 |
42 | p_instance_of = re.compile("^.*instance_of.*$", re.M)
43 | p_inventory = re.compile("^.*nventory.*$", re.I | re.M)
44 |
45 | pytestmark = [
46 | pytest.mark.cli,
47 | pytest.mark.nonloc,
48 | pytest.mark.flaky(retries=2, delay=5),
49 | ]
50 |
51 |
52 | @pytest.fixture(scope="module", autouse=True)
53 | def skip_if_no_nonloc(pytestconfig):
54 | """Skip test if --nonloc not provided.
55 |
56 | Auto-applied to all functions in module, since module is nonlocal.
57 |
58 | """
59 | if not pytestconfig.getoption("--nonloc"):
60 | pytest.skip("'--nonloc' not specified") # pragma: no cover
61 |
62 |
63 | class TestConvert:
64 | """Test nonlocal CLI convert mode functionality."""
65 |
66 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4)
67 | def test_cli_convert_from_url_with_dest(
68 | self, scratch_path, misc_info, run_cmdline_test, monkeypatch
69 | ):
70 | """Confirm CLI URL D/L, convert works w/outfile supplied."""
71 | monkeypatch.chdir(scratch_path)
72 |
73 | dest_path = scratch_path / (misc_info.FNames.MOD + misc_info.Extensions.DEC)
74 | run_cmdline_test(
75 | [
76 | "convert",
77 | "plain",
78 | "-u",
79 | misc_info.remote_url.format("attrs"),
80 | str(dest_path),
81 | ]
82 | )
83 |
84 | assert dest_path.is_file()
85 |
86 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4)
87 | def test_cli_convert_from_url_no_dest(
88 | self, scratch_path, misc_info, run_cmdline_test, monkeypatch
89 | ):
90 | """Confirm CLI URL D/L, convert works w/o outfile supplied."""
91 | monkeypatch.chdir(scratch_path)
92 | dest_path = scratch_path / (misc_info.FNames.INIT + misc_info.Extensions.DEC)
93 | dest_path.unlink()
94 | run_cmdline_test(
95 | ["convert", "plain", "-u", misc_info.remote_url.format("attrs")]
96 | )
97 | assert dest_path.is_file()
98 |
99 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4)
100 | def test_cli_url_in_json(
101 | self, scratch_path, misc_info, run_cmdline_test, monkeypatch
102 | ):
103 | """Confirm URL is present when using CLI URL mode."""
104 | monkeypatch.chdir(scratch_path)
105 | dest_path = scratch_path / (misc_info.FNames.MOD + misc_info.Extensions.JSON)
106 | run_cmdline_test(
107 | [
108 | "convert",
109 | "json",
110 | "-u",
111 | misc_info.remote_url.format("attrs"),
112 | str(dest_path.resolve()),
113 | ]
114 | )
115 |
116 | d = json.loads(dest_path.read_text())
117 |
118 | assert "objects" in d.get("metadata", {}).get("url", {})
119 |
120 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4)
121 | def test_clifail_bad_url(self, run_cmdline_test, misc_info, scratch_path):
122 | """Confirm proper error behavior when a bad URL is passed."""
123 | with stdio_mgr() as (in_, out_, err_):
124 | run_cmdline_test(
125 | [
126 | "convert",
127 | "plain",
128 | "-u",
129 | misc_info.remote_url.format("blarghers"),
130 | str(scratch_path),
131 | ],
132 | expect=1,
133 | )
134 | assert "HTTP error: 404 Not Found." in err_.getvalue()
135 |
136 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4)
137 | def test_clifail_url_no_leading_http(self, run_cmdline_test, scratch_path):
138 | """Confirm proper error behavior when a URL w/o leading 'http://' is passed."""
139 | with stdio_mgr() as (in_, out_, err_):
140 | run_cmdline_test(
141 | [
142 | "convert",
143 | "plain",
144 | "-u",
145 | "sphobjinv.readthedocs.io/en/latest",
146 | str(scratch_path),
147 | ],
148 | expect=1,
149 | )
150 | assert "file found but inventory could not be loaded" in err_.getvalue()
151 |
152 | def test_cli_json_export_import(
153 | self, res_cmp, scratch_path, misc_info, run_cmdline_test, sphinx_load_test
154 | ):
155 | """Confirm JSON sent to stdout from local source imports ok."""
156 | inv_url = misc_info.remote_url.format("attrs")
157 | mod_path = scratch_path / (misc_info.FNames.MOD + misc_info.Extensions.CMP)
158 |
159 | with stdio_mgr() as (in_, out_, err_):
160 | run_cmdline_test(["convert", "json", "-u", inv_url, "-"])
161 |
162 | data = out_.getvalue()
163 |
164 | with stdio_mgr(data) as (in_, out_, err_):
165 | run_cmdline_test(["convert", "zlib", "-", str(mod_path.resolve())])
166 |
167 | assert Inventory(json.loads(data))
168 | assert Inventory(mod_path)
169 | sphinx_load_test(mod_path)
170 |
171 |
172 | class TestSuggest:
173 | """Test nonlocal CLI suggest mode functionality."""
174 |
175 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4)
176 | def test_cli_suggest_from_url(self, misc_info, run_cmdline_test):
177 | """Confirm reST-only suggest output works from URL."""
178 | with stdio_mgr() as (in_, out_, err_):
179 | run_cmdline_test(
180 | [
181 | "suggest",
182 | "-u",
183 | misc_info.remote_url.format("attrs"),
184 | "instance",
185 | "-t",
186 | "50",
187 | ]
188 | )
189 | assert p_instance_of.search(out_.getvalue())
190 |
191 | @pytest.mark.parametrize(
192 | "url",
193 | [
194 | "http://sphobjinv.readthedocs.io/en/v2.0/modules/",
195 | "http://sphobjinv.readthedocs.io/en/v2.0/modules/cmdline.html",
196 | (
197 | "http://sphobjinv.readthedocs.io/en/v2.0/modules/"
198 | "cmdline.html#sphobjinv.cmdline.do_convert"
199 | ),
200 | ],
201 | )
202 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4)
203 | def test_cli_suggest_from_docset_urls(self, url, run_cmdline_test, check):
204 | """Confirm reST-only suggest output works from URLs within a docset."""
205 | with stdio_mgr() as (in_, out_, err_):
206 | run_cmdline_test(["suggest", "-u", url, "inventory", "-at", "50"])
207 |
208 | check.is_true(p_inventory.search(out_.getvalue()))
209 | check.is_in("LIKELY", err_.getvalue())
210 | check.is_in(
211 | "(http://sphobjinv.readthedocs.io/en/v2.0/, None)", err_.getvalue()
212 | )
213 |
214 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4)
215 | def test_cli_suggest_from_typical_objinv_url(self, run_cmdline_test, check):
216 | """Confirm reST-only suggest works for direct objects.inv URL."""
217 | url = "http://sphobjinv.readthedocs.io/en/v2.0/objects.inv"
218 | with stdio_mgr() as (in_, out_, err_):
219 | run_cmdline_test(["suggest", "-u", url, "inventory", "-at", "50"])
220 |
221 | check.is_true(p_inventory.search(out_.getvalue()))
222 | check.is_in("PROBABLY", err_.getvalue())
223 | check.is_in(
224 | "(http://sphobjinv.readthedocs.io/en/v2.0/, None)", err_.getvalue()
225 | )
226 |
227 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4)
228 | def test_cli_suggest_from_django_objinv_url(self, run_cmdline_test, check):
229 | """Confirm reST-only suggest works for direct objects.inv URL."""
230 | url = "https://docs.djangoproject.com/en/4.1/_objects/"
231 | with stdio_mgr() as (in_, out_, err_):
232 | run_cmdline_test(["suggest", "-u", url, "route", "-a"])
233 |
234 | check.is_true(re.search("DATABASE_ROUTERS", out_.getvalue()))
235 | check.is_in("Cannot infer intersphinx_mapping", err_.getvalue())
236 |
--------------------------------------------------------------------------------
/tests/test_fixture.py:
--------------------------------------------------------------------------------
1 | r"""*Trivial fixture tests for* ``sphobjinv``.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 20 Mar 2019
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | http://www.github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import pytest
33 |
34 | pytestmark = pytest.mark.fixture
35 |
36 |
37 | def test_info_fixture(misc_info):
38 | """Confirm arbitrary member of misc_info fixture."""
39 | assert True in misc_info.byte_lines
40 |
41 |
42 | def test_populate_scratch(misc_info, scratch_path, check):
43 | """Ensure the scratch_path fixture populates the scratch dir correctly."""
44 | scr_base = misc_info.FNames.INIT.value
45 |
46 | for ext in [_.value for _ in misc_info.Extensions]:
47 | with check(msg=ext):
48 | assert (scratch_path / f"{scr_base}{ext}").is_file(), ext
49 |
50 |
51 | def test_sphinx_load(res_path, sphinx_load_test):
52 | """Confirm sphinx_load_test fixture works on known-good inventory."""
53 | sphinx_load_test(res_path / "objects_attrs.inv")
54 |
55 |
56 | def test_cli_invoke(run_cmdline_test):
57 | """Confirm CLI test with no args exits ok.
58 |
59 | Should just print help and exit.
60 |
61 | """
62 | run_cmdline_test([])
63 |
64 |
65 | def test_decomp_comp_fixture(misc_info, decomp_cmp_test, scratch_path):
66 | """Test decomp_cmp_test works in 'identity' case.
67 |
68 | Basically is telling filecmp.cmp to compare a reference inventory file
69 | with itself.
70 |
71 | """
72 | decomp_cmp_test(
73 | scratch_path / f"{misc_info.FNames.INIT.value}{misc_info.Extensions.DEC.value}"
74 | )
75 |
--------------------------------------------------------------------------------
/tests/test_flake8_ext.py:
--------------------------------------------------------------------------------
1 | r"""*Test(s) to ensure full loading of flake8 extensions*.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 27 Apr 2019
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | http://www.github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import re
33 | import subprocess as sp # noqa: S404
34 | import sys
35 | from pathlib import Path
36 |
37 | import pytest
38 |
39 | pytestmark = [pytest.mark.flake8_ext]
40 |
41 |
42 | @pytest.fixture(scope="module", autouse=True)
43 | def skip_if_no_flake8_ext(pytestconfig):
44 | """Skip test if --flake8_ext not provided.
45 |
46 | Auto-applied to all functions in module.
47 |
48 | """
49 | if not pytestconfig.getoption("--flake8_ext"):
50 | pytest.skip("'--flake8_ext' not specified") # pragma: no cover
51 |
52 |
53 | @pytest.mark.skipif(
54 | sys.version_info < (3, 6),
55 | reason="Some flake8 extensions require Python 3.6 or later",
56 | )
57 | def test_flake8_version_output(check):
58 | """Confirm that all desired plugins actually report as loaded."""
59 | p_pkgname = re.compile("^[0-9a-z_-]+", re.I)
60 | plugins = Path("requirements-flake8.txt").read_text().splitlines()[1:]
61 | plugins = [p_pkgname.search(p).group(0) for p in plugins]
62 |
63 | # This is fragile if anything ends up not having a prefix that needs
64 | # stripping
65 | plugins = [p.partition("flake8-")[-1] for p in plugins]
66 |
67 | flake8_ver_output = sp.check_output( # noqa: S607,S603
68 | ["flake8", "--version"], universal_newlines=True
69 | ) # noqa: S607,S603
70 |
71 | for p in plugins:
72 | with check(msg=p):
73 | assert p in flake8_ver_output.replace("_", "-").replace("\n", "")
74 |
--------------------------------------------------------------------------------
/tests/test_intersphinx.py:
--------------------------------------------------------------------------------
1 | r"""*Tests for intersphinx-related functionality for* ``sphobjinv``.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 21 Jun 2022
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | http://www.github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import pytest
33 |
34 | import sphobjinv.cli.suggest as soi_cli_suggest
35 |
36 |
37 | pytestmark = [pytest.mark.intersphinx, pytest.mark.local]
38 |
39 |
40 | @pytest.mark.parametrize(
41 | ("uri", "trimmed", "with_scheme"),
42 | [
43 | ("cli/implementation/parser.html#$", "cli/implementation/parser.html", False),
44 | (
45 | (
46 | "https://sphobjinv.readthedocs.io/en/stable/api/"
47 | "enum.html#sphobjinv.enum.HeaderFields"
48 | ),
49 | "//sphobjinv.readthedocs.io/en/stable/api/enum.html",
50 | False,
51 | ),
52 | (
53 | (
54 | "https://sphobjinv.readthedocs.io/en/stable/api/"
55 | "enum.html#sphobjinv.enum.HeaderFields"
56 | ),
57 | "https://sphobjinv.readthedocs.io/en/stable/api/enum.html",
58 | True,
59 | ),
60 | ],
61 | )
62 | def test_strip_netloc_path(uri, trimmed, with_scheme):
63 | """Confirm that object URI trimming is working."""
64 | assert trimmed == soi_cli_suggest._strip_url_to_netloc_path(
65 | uri, with_scheme=with_scheme
66 | )
67 |
68 |
69 | @pytest.mark.parametrize(
70 | ("url", "trimmed"),
71 | [
72 | (
73 | "https://sphobjinv.readthedocs.io/en/latest/objects.inv",
74 | "https://sphobjinv.readthedocs.io/en/latest/",
75 | )
76 | ],
77 | )
78 | def test_extract_objinv_url_base(url, trimmed):
79 | """Confirm that inventory URL trimming is working."""
80 | assert trimmed == soi_cli_suggest.extract_objectsinv_url_base(url)
81 |
--------------------------------------------------------------------------------
/tests/test_valid_objects.py:
--------------------------------------------------------------------------------
1 | r"""*Valid/invalid object data tests for* ``sphobjinv``.
2 |
3 | ``sphobjinv`` is a toolkit for manipulation and inspection of
4 | Sphinx |objects.inv| files.
5 |
6 | **Author**
7 | Brian Skinn (brian.skinn@gmail.com)
8 |
9 | **File Created**
10 | 13 Feb 2021
11 |
12 | **Copyright**
13 | \(c) Brian Skinn 2016-2025
14 |
15 | **Source Repository**
16 | http://www.github.com/bskinn/sphobjinv
17 |
18 | **Documentation**
19 | https://sphobjinv.readthedocs.io/en/stable
20 |
21 | **License**
22 | Code: `MIT License`_
23 |
24 | Docs & Docstrings: |CC BY 4.0|_
25 |
26 | See |license_txt|_ for full license terms.
27 |
28 | **Members**
29 |
30 | """
31 |
32 | import os.path as osp
33 | import zlib
34 | from io import BytesIO
35 |
36 | import pytest
37 | import sphinx
38 | from sphinx.util.inventory import InventoryFile as IFile
39 |
40 | import sphobjinv as soi
41 |
42 |
43 | @pytest.fixture(autouse=True)
44 | def skip_on_sphinx_version(sphinx_version):
45 | """Trigger test skip if Sphinx version is too low.
46 |
47 | Changes to the Sphinx InventoryFile regex &c. cause older
48 | versions of Sphinx to have different behavior.
49 |
50 | This skip *should* only trigger during the tox matrix of
51 | environments with various old dependency versions.
52 |
53 | """
54 | if sphinx_version < (3, 3, 0): # pragma: no cover
55 | pytest.skip("Sphinx version too low")
56 |
57 |
58 | # Once DataObjStr instance validation is in place, this will probably
59 | # be a good place to use hypothesis
60 | @pytest.mark.parametrize(
61 | ("name", "domain", "role", "prio", "uri", "dispname"),
62 | [
63 | ("foo", "py", "data", 1, "quux.html#$", "-"), # Priorities
64 | ("foo", "py", "data", 0, "quux.html#$", "-"),
65 | ("foo", "py", "data", -1, "quux.html#$", "-"),
66 | ("foo", "py", "data", -1235778, "quux.html#$", "-"),
67 | ("foo", "py", "data", 2214888, "quux.html#$", "-"),
68 | ("foo bar", "std", "term", 1, "quux.html#$", "-"), # Space in name
69 | ("foo\tbar", "std", "term", 1, "quux.html#$", "-"), # Valid but discouraged
70 | ("Index Page", "std", "doc", 1, "index.html", "-"),
71 | ("Index Page", "std", "doc", 1, "index.html", "Index Page Thing"),
72 | ("Index Page", "std", "doc", 1, "index.html", "Index\tPage\tThing"),
73 | ("Index Page", "std", "doc", 1, "", "-"), # Zero-length uri
74 | ("Index # Page", "std", "doc", 1, "index.html", "-"), # Symbol in name
75 | ("Thing \u33a4", "std", "ref", 1, "index.html#$", "-"), # Unicode in name
76 | ("Thing One", "std", "ref", 1, "index.html#$", "\u33a4"), # Unicode in dispname
77 | ("foo", "py", "da:ta", 1, "data.html#$", "-"), # Colon in role (used in Sphinx)
78 | ("foo", "py$", "data", 1, "data.html#$", "-"), # Valid but discouraged
79 | ("foo", "py\u33a4", "data", 1, "data.html#$", "-"), # Valid but discouraged
80 | ("foo", "py", "data$", 1, "data.html#$", "-"), # Valid but discouraged
81 | ("foo", "py", "data\u33a4", 1, "data.html#$", "-"), # Valid but discouraged
82 | ("foo", "py", "data", 1, "data/\u33a4.html#$", "-"), # Valid but discouraged
83 | (" foo", "py", "data", 1, "data.html#$", "-"), # Valid but discouraged
84 | # Colon in domain (invalid but undetectable)
85 | ("foo", "p:y", "data", 1, "data.html#$", "-"),
86 | ],
87 | )
88 | def test_dataobjstr_valid_objects(
89 | misc_info, sphinx_ifile_data_count, name, domain, role, prio, uri, dispname
90 | ):
91 | """Run sphobjinv/sphinx comparison on specific object data lines."""
92 | dos = soi.DataObjStr(
93 | name=name,
94 | domain=domain,
95 | role=role,
96 | priority=str(prio),
97 | uri=uri,
98 | dispname=dispname,
99 | )
100 |
101 | assert dos
102 |
103 | inv = soi.Inventory()
104 | inv.project = "Foo"
105 | inv.version = "1.0"
106 | inv.objects.append(
107 | soi.DataObjStr(
108 | name="bar", domain="py", role="data", priority="1", uri="$", dispname="-"
109 | )
110 | )
111 | inv.objects.append(dos)
112 |
113 | df = inv.data_file(contract=True)
114 |
115 | ifile_data = IFile.load(BytesIO(soi.compress(df)), "", osp.join)
116 |
117 | ifile_count = sphinx_ifile_data_count(ifile_data)
118 |
119 | assert inv.count == ifile_count
120 |
121 | domrole = "{dos.domain}:{dos.role}".format(dos=dos)
122 |
123 | assert domrole in ifile_data
124 | assert dos.name in ifile_data[domrole]
125 |
126 |
127 | @pytest.mark.parametrize(
128 | ("name", "domain", "role", "prio", "uri", "dispname"),
129 | [
130 | ("", "std", "doc", 1, "index.html", "-"), # Missing name
131 | ("foo ", "py", "data", 1, "data.html#$", "-"), # Name w/trailing space
132 | ("# Index Page", "std", "doc", 1, "index.html", "-"), # '#' @ name start
133 | ("X Y Z 0 foo", "std", "doc", 1, "index.html", "-"), # Int in name
134 | ("foo", "py thon", "data", 1, "data.html#$", "-"), # Space in domain
135 | ("foo", "", "data", 1, "data.html#$", "-"), # Missing domain
136 | ("foo", "py", "da ta", 1, "data.html#$", "-"), # Space in role
137 | ("foo", "py", "", 1, "data.html#$", "-"), # Missing role
138 | ("foo", "py", "data", 0.5, "data.html#$", "-"), # Non-integer prio
139 | ("foo", "py", "data", "", "data.html#$", "-"), # Missing prio
140 | ("foo", "py", "data", "quux", "data.html#$", "-"), # Non-numeric prio
141 | ("Index Page", "std", "doc", 1, "index.html", ""), # Missing dispname
142 | ("Index Page", "std", "doc", 1, "", ""), # Missing uri & dispname
143 | ],
144 | )
145 | def test_dataobjstr_invalid_objects(
146 | misc_info, sphinx_ifile_data_count, name, domain, role, prio, uri, dispname
147 | ):
148 | """Run sphobjinv/sphinx comparison on specific invalid data lines."""
149 | with pytest.raises((AssertionError, zlib.error)):
150 | test_dataobjstr_valid_objects(
151 | misc_info, sphinx_ifile_data_count, name, domain, role, prio, uri, dispname
152 | )
153 |
154 |
155 | def int_to_latin_1(val):
156 | """Provide the latin-1 string equivalent of an 8-bit int."""
157 | return bytes((val,)).decode("latin-1")
158 |
159 |
160 | def latin_1_id(val):
161 | """Provide the value-and-character string for a latin-1 int value."""
162 | return str(val) + "_" + int_to_latin_1(val)
163 |
164 |
165 | @pytest.mark.parametrize("leadint", range(255), ids=latin_1_id)
166 | def test_name_lead_chars(misc_info, sphinx_ifile_data_count, leadint):
167 | """Screen for valid/invalid first characters."""
168 | name = int_to_latin_1(leadint) + " foo"
169 |
170 | # For Sphinx < 8.2 expect only two fail cases, newline and '#'
171 | if leadint in (10, 35):
172 | pytest.xfail("Known invalid name lead char")
173 |
174 | # Sphinx >= 8.2 uses splitlines(), which strips more line boundary characters.
175 | # See https://github.com/bskinn/sphobjinv/issues/314
176 | if sphinx.version_info >= (8, 2) and leadint in (11, 12, 13, 28, 29, 30, 133):
177 | pytest.xfail(
178 | "Known invalid name lead char for Sphinx >= 8.2"
179 | ) # pragma: no cover
180 |
181 | test_dataobjstr_valid_objects(
182 | misc_info,
183 | sphinx_ifile_data_count,
184 | name=name,
185 | domain="py",
186 | role="data",
187 | prio=1,
188 | uri="data.html#$",
189 | dispname="-",
190 | )
191 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | minversion=2.0
3 | isolated_build=True
4 | envlist=
5 | # Test all Python versions on latest lib versions
6 | py3{9,10,11,12,13}-sphx_latest-attrs_latest-jsch_latest
7 | # Test leading Python version on current in-repo dev lib versions
8 | py313-sphx_dev-attrs_dev-jsch_dev
9 | # Scan across Sphinx versions
10 | py313-sphx_{1_6_x,1_x,2_x,4_x,5_x,6_x,7_x,dev}-attrs_latest-jsch_latest
11 | # sphx_3_x is incompatible with py310 due to a typing import. Test on py39 instead.
12 | py39-sphx_3_x-attrs_latest-jsch_latest
13 | # Scan attrs versions
14 | py313-sphx_latest-attrs_{19_2,19_3,20_3,21_3,22_2,23_2,24_3,dev}-jsch_latest
15 | # Scan jsonschema versions
16 | py313-sphx_latest-attrs_latest-jsch_{3_0,3_x,4_0,4_8,4_14,4_20,dev}
17 | # Earliest supported Python and lib versions all together
18 | py39-sphx_1_6_x-attrs_19_2-jsch_3_0
19 | # Spot matrix of early Python, Sphinx, attrs versions
20 | py3{9,10}-sphx_{1,2}_x-attrs_{19,20}_2-jsch_latest
21 | # Test the specific Sphinx threshold cases where behavior changed
22 | py312-sphx_{2_3_1,2_4_0,3_2_1,3_3_0,3_4_0,8_1_3,8_2_0}-attrs_latest-jsch_latest
23 | # Simple 'does the sdist install' check
24 | sdist_install
25 | # Lints
26 | flake8
27 | # Sphinx link check
28 | linkcheck
29 |
30 | [testenv]
31 | commands=
32 | python --version
33 | pip list
34 | # Want the tox *matrix* to ignore warnings since it's primarily
35 | # a compatibility check. The defaults for bare pytest enable -Werror
36 | pytest {posargs:--nonloc -Wignore}
37 | deps=
38 | sphx_1_6_x: sphinx<1.7
39 | sphx_1_x: sphinx<2
40 | sphx_2_x: sphinx<3
41 | sphx_3_x: sphinx<4
42 | sphx_4_x: sphinx<5
43 | sphx_5_x: sphinx<6
44 | sphx_6_x: sphinx<7
45 | sphx_7_x: sphinx<8
46 | sphx_2_3_1: sphinx==2.3.1
47 | sphx_2_4_0: sphinx==2.4.0
48 | sphx_3_2_1: sphinx==3.2.1
49 | sphx_3_3_0: sphinx==3.3.0
50 | sphx_3_4_0: sphinx==3.4.0
51 | sphx_8_1_3: sphinx==8.1.3
52 | sphx_8_2_0: sphinx==8.2.0
53 | sphx_latest: sphinx
54 | sphx_dev: git+https://github.com/sphinx-doc/sphinx
55 |
56 | attrs_19_2: attrs==19.2
57 | attrs_19_3: attrs==19.3
58 | attrs_20_3: attrs==20.3
59 | attrs_21_3: attrs==21.3
60 | attrs_22_2: attrs==22.2
61 | attrs_23_2: attrs==23.2
62 | attrs_24_3: attrs==24.3
63 | attrs_latest: attrs
64 | attrs_dev: git+https://github.com/python-attrs/attrs
65 |
66 | jsch_3_0: jsonschema==3.0
67 | jsch_3_x: jsonschema<4
68 | jsch_4_0: jsonschema<4.1
69 | jsch_4_8: jsonschema<4.9
70 | jsch_4_14: jsonschema<4.15
71 | jsch_4_20: jsonschema<4.21
72 | jsch_latest: jsonschema
73 | jsch_dev: git+https://github.com/Julian/jsonschema
74 |
75 | dictdiffer
76 | pytest>=4.4.0
77 | pytest-check>=1.1.2
78 | pytest-ordering
79 | pytest-retry
80 | pytest-timeout
81 | stdio-mgr>=1.0.1
82 | sphinx-issues
83 | sphinx-rtd-theme
84 | sphinxcontrib-programoutput
85 |
86 | [testenv:linux]
87 | platform=linux
88 | basepython=
89 | py313: python3.13
90 | py312: python3.12
91 | py311: python3.11
92 | py310: python3.10
93 | py39: python3.9
94 |
95 | [testenv:black]
96 | skip_install=True
97 | deps=black
98 | commands=
99 | black {posargs} .
100 |
101 | [testenv:flake8]
102 | skip_install=True
103 | deps=-rrequirements-flake8.txt
104 | commands=
105 | flake8 ./conftest.py src tests
106 |
107 | [testenv:flake8_noqa]
108 | skip_install=True
109 | deps=-rrequirements-flake8.txt
110 | commands=
111 | pip install flake8-noqa
112 | flake8 --color=never --exit-zero ./conftest.py tests src
113 |
114 | [testenv:interrogate]
115 | skip_install=True
116 | deps=interrogate
117 | commands=
118 | interrogate {posargs} conftest.py tests src
119 |
120 | [testenv:linkcheck]
121 | skip_install=True
122 | deps=-rrequirements-dev.txt
123 | allowlist_externals=
124 | make
125 | changedir=doc
126 | commands=
127 | make linkcheck
128 |
129 | [testenv:sdist_install]
130 | commands=
131 | python -Werror -c "import sphobjinv"
132 | deps=
133 |
134 | [pytest]
135 | markers =
136 | local: Tests not requiring Internet access
137 | nonloc: Tests requiring Internet access
138 | cli: Command-line interface tests
139 | api: Direct API tests
140 | intersphinx: Tests on intersphinx-related functionality
141 | fixture: Trivial tests for test suite fixtures
142 | testall: Tests that use *all* objects_xyz.inv files in tests/resource, if --testall is specified
143 | flake8_ext: Test checking that all desired plugins are active
144 | first: Inherited marker from `pytest-ordering`
145 | timeout: Inherited marker from `pytest-timeout`
146 |
147 | addopts = --strict-markers -rsxX -Werror
148 |
149 | norecursedirs = .* env* src *.egg dist build
150 |
151 | xfail_strict = True
152 |
153 |
154 | [flake8]
155 | exclude =
156 | src/sphobjinv/_vendored
157 |
158 | # W503: black formats binary operators to start of line
159 | # A005: Submodules are going to be shadowing builtins for the moment
160 | ignore = W503,A005
161 | show_source = True
162 | max_line_length = 88
163 | format = %(cyan)s%(path)s%(reset)s:%(yellow)s%(row)d%(reset)s:%(green)s%(col)d%(reset)s %(red)s(%(code)s)%(reset)s %(text)s
164 | rst-roles =
165 | attr,
166 | class,
167 | data,
168 | doc,
169 | exc,
170 | func,
171 | meth,
172 | mod,
173 | option,
174 | ref,
175 | rst-directives =
176 | doctest,
177 | versionadded,
178 | versionchanged,
179 | per_file_ignores =
180 | # D202: No-space-after-docstring is ugly when the first command is a class/def
181 | # S101: pytest uses asserts liberally
182 | # RST30x: linter can't know about substitutions/references in rst_epilog
183 | src/*: RST305,RST306
184 | tests/*: S101, RST305,RST306
185 | conftest.py: D202, S101, RST305,RST306
186 | # F401: MANY things imported but unused in __init__.py files
187 | src/sphobjinv/__init__.py: F401, RST305,RST306
188 | src/sphobjinv/cli/__init__.py: F401, RST305,RST306
189 | # PIE786: CLI uses 'except Exception:' as a catchall... to be changed, eventually
190 | src/sphobjinv/cli/*: PIE786, RST305,RST306
191 |
192 | #flake8-import-order
193 | import-order-style = smarkets
194 | application-import-names = sphobjinv
195 |
--------------------------------------------------------------------------------