http2py 0.1.31__tar.gz → 0.1.34__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. http2py-0.1.34/.gitattributes +1 -0
  2. http2py-0.1.34/.github/workflows/ci.yml +236 -0
  3. http2py-0.1.34/.gitignore +112 -0
  4. {http2py-0.1.31 → http2py-0.1.34}/PKG-INFO +29 -13
  5. {http2py-0.1.31 → http2py-0.1.34}/README.md +4 -2
  6. http2py-0.1.34/conftest.py +18 -0
  7. {http2py-0.1.31 → http2py-0.1.34}/http2py/api_pkg_maker.py +68 -7
  8. {http2py-0.1.31 → http2py-0.1.34}/http2py/authentication.py +7 -0
  9. {http2py-0.1.31 → http2py-0.1.34}/http2py/cli_maker.py +41 -7
  10. {http2py-0.1.31 → http2py-0.1.34}/http2py/client.py +6 -0
  11. {http2py-0.1.31 → http2py-0.1.34}/http2py/constants.py +2 -0
  12. {http2py-0.1.31 → http2py-0.1.34}/http2py/decorators.py +7 -0
  13. {http2py-0.1.31 → http2py-0.1.34}/http2py/default_configs.py +2 -0
  14. http2py-0.1.34/http2py/example_cli.py +23 -0
  15. {http2py-0.1.31 → http2py-0.1.34}/http2py/global_state.py +2 -0
  16. {http2py-0.1.31 → http2py-0.1.34}/http2py/py2request.py +6 -6
  17. {http2py-0.1.31 → http2py-0.1.34}/http2py/testing_utils.py +6 -0
  18. http2py-0.1.34/http2py/tests/__init__.py +1 -0
  19. http2py-0.1.34/http2py/tests/conftest.py +44 -0
  20. http2py-0.1.34/http2py/tests/test_requirements.txt +1 -0
  21. {http2py-0.1.31 → http2py-0.1.34}/http2py/util.py +14 -4
  22. http2py-0.1.34/pyproject.toml +196 -0
  23. http2py-0.1.34/tests/cli_goldens/mk_cli.json +10 -0
  24. http2py-0.1.34/tests/spec_fixture.py +89 -0
  25. http2py-0.1.34/tests/test_ci_collection_contract.py +215 -0
  26. http2py-0.1.34/tests/test_cli_surface.py +171 -0
  27. http2py-0.1.34/tests/test_dependency_workarounds.py +85 -0
  28. http2py-0.1.34/tests/test_smoke.py +84 -0
  29. http2py-0.1.31/http2py/example_cli.py +0 -16
  30. http2py-0.1.31/http2py/tests/conftest.py +0 -30
  31. http2py-0.1.31/http2py.egg-info/PKG-INFO +0 -88
  32. http2py-0.1.31/http2py.egg-info/SOURCES.txt +0 -28
  33. http2py-0.1.31/http2py.egg-info/dependency_links.txt +0 -1
  34. http2py-0.1.31/http2py.egg-info/entry_points.txt +0 -2
  35. http2py-0.1.31/http2py.egg-info/not-zip-safe +0 -1
  36. http2py-0.1.31/http2py.egg-info/requires.txt +0 -7
  37. http2py-0.1.31/http2py.egg-info/top_level.txt +0 -1
  38. http2py-0.1.31/setup.cfg +0 -43
  39. http2py-0.1.31/setup.py +0 -3
  40. {http2py-0.1.31 → http2py-0.1.34}/LICENSE +0 -0
  41. {http2py-0.1.31 → http2py-0.1.34}/http2py/__init__.py +0 -0
  42. {http2py-0.1.31 → http2py-0.1.34}/http2py/http2py_cls.py +0 -0
  43. /http2py-0.1.31/http2py/tests/__init__.py → /http2py-0.1.34/http2py/resources/module_maker/func_tpl.txt +0 -0
  44. {http2py-0.1.31 → http2py-0.1.34}/http2py/tests/api_pkg_maker_test.py +0 -0
@@ -0,0 +1 @@
1
+ *.ipynb linguist-documentation
@@ -0,0 +1,236 @@
1
+ name: Continuous Integration (uv)
2
+ # NOTE: publishing uses `uv publish`, which reads UV_PUBLISH_TOKEN -- mapped here
3
+ # from secrets.PYPI_PASSWORD. This repo has no repository-level secrets; it
4
+ # inherits the organization-level PYPI_PASSWORD / SSH_PRIVATE_KEY (visibility:
5
+ # all), which is the same pair other migrated packages in this org publish with.
6
+ #
7
+ # The GitHub Pages job below is gated on [tool.wads.ci.docs].enabled, which is
8
+ # false here: docs/ and docsrc/ were removed from this repo, and the legacy CI's
9
+ # `epythet make . github` call against the now-absent docs is what broke Publish.
10
+ on: [push, pull_request]
11
+
12
+ # Note: Environment variables (PROJECT_NAME and vars from [tool.wads.ci.env])
13
+ # are set by the read-ci-config action in the setup job and made available
14
+ # to all subsequent jobs via GITHUB_ENV
15
+
16
+ jobs:
17
+ # First job: Read configuration from pyproject.toml
18
+ setup:
19
+ name: Read Configuration
20
+ runs-on: ubuntu-latest
21
+ outputs:
22
+ project-name: ${{ steps.config.outputs.project-name }}
23
+ python-versions: ${{ steps.config.outputs.python-versions }}
24
+ pytest-args: ${{ steps.config.outputs.pytest-args }}
25
+ coverage-enabled: ${{ steps.config.outputs.coverage-enabled }}
26
+ exclude-paths: ${{ steps.config.outputs.exclude-paths }}
27
+ test-on-windows: ${{ steps.config.outputs.test-on-windows }}
28
+ build-sdist: ${{ steps.config.outputs.build-sdist }}
29
+ build-wheel: ${{ steps.config.outputs.build-wheel }}
30
+ metrics-enabled: ${{ steps.config.outputs.metrics-enabled }}
31
+ metrics-config-path: ${{ steps.config.outputs.metrics-config-path }}
32
+ metrics-storage-branch: ${{ steps.config.outputs.metrics-storage-branch }}
33
+ metrics-python-version: ${{ steps.config.outputs.metrics-python-version }}
34
+ metrics-force-run: ${{ steps.config.outputs.metrics-force-run }}
35
+ ruff-enabled: ${{ steps.config.outputs.ruff-enabled }}
36
+ black-enabled: ${{ steps.config.outputs.black-enabled }}
37
+ mypy-enabled: ${{ steps.config.outputs.mypy-enabled }}
38
+ docs-enabled: ${{ steps.config.outputs.docs-enabled }}
39
+
40
+ steps:
41
+ - uses: actions/checkout@v6
42
+
43
+ - name: Set up uv
44
+ uses: astral-sh/setup-uv@v7
45
+
46
+ - name: Set up Python
47
+ run: uv python install 3.11
48
+
49
+ - name: Read CI Config
50
+ id: config
51
+ uses: i2mint/wads/actions/read-ci-config@master
52
+ with:
53
+ pyproject-path: .
54
+
55
+ # Second job: Validation using the config
56
+ validation:
57
+ name: Validation
58
+ if: "!contains(github.event.head_commit.message, '[skip ci]')"
59
+ needs: setup
60
+ runs-on: ubuntu-latest
61
+ strategy:
62
+ matrix:
63
+ python-version: ${{ fromJson(needs.setup.outputs.python-versions) }}
64
+
65
+ steps:
66
+ - uses: actions/checkout@v6
67
+
68
+ - name: Set up uv
69
+ uses: astral-sh/setup-uv@v7
70
+ with:
71
+ enable-cache: true
72
+
73
+ - name: Set up Python ${{ matrix.python-version }}
74
+ uses: i2mint/wads/actions/setup-python-uv@master
75
+ with:
76
+ python-version: ${{ matrix.python-version }}
77
+
78
+ - name: Install System Dependencies
79
+ uses: i2mint/wads/actions/install-system-deps@master
80
+ with:
81
+ pyproject-path: .
82
+
83
+ - name: Install Dependencies
84
+ uses: i2mint/wads/actions/install-deps-uv@master
85
+
86
+ - name: Format Source Code
87
+ if: needs.setup.outputs.ruff-enabled != 'false'
88
+ run: uvx ruff format .
89
+
90
+ - name: Format Source Code (black)
91
+ if: needs.setup.outputs.black-enabled == 'true'
92
+ run: uvx black .
93
+
94
+ - name: Lint Validation
95
+ if: needs.setup.outputs.ruff-enabled != 'false'
96
+ run: uvx ruff check --output-format=github ${{ needs.setup.outputs.project-name }}
97
+
98
+ - name: Type Check (mypy)
99
+ if: needs.setup.outputs.mypy-enabled == 'true'
100
+ run: uvx mypy ${{ needs.setup.outputs.project-name }}
101
+
102
+ - name: Run Tests
103
+ uses: i2mint/wads/actions/run-tests-uv@master
104
+ with:
105
+ root-dir: ${{ needs.setup.outputs.project-name }}
106
+ pytest-args: ${{ needs.setup.outputs.pytest-args }}
107
+ exclude-paths: ${{ needs.setup.outputs.exclude-paths }}
108
+ coverage: ${{ needs.setup.outputs.coverage-enabled }}
109
+
110
+ - name: Track Code Metrics
111
+ if: needs.setup.outputs.metrics-enabled == 'true'
112
+ uses: i2mint/umpyre/actions/track-metrics@master
113
+ continue-on-error: true
114
+ with:
115
+ github-token: ${{ secrets.GITHUB_TOKEN }}
116
+ config-path: ${{ needs.setup.outputs.metrics-config-path }}
117
+ storage-branch: ${{ needs.setup.outputs.metrics-storage-branch }}
118
+ python-version: ${{ needs.setup.outputs.metrics-python-version }}
119
+ force-run: ${{ needs.setup.outputs.metrics-force-run }}
120
+
121
+ # Optional Windows testing (if enabled in config)
122
+ windows-validation:
123
+ name: Windows Tests
124
+ if: "!contains(github.event.head_commit.message, '[skip ci]') && needs.setup.outputs.test-on-windows == 'true'"
125
+ needs: setup
126
+ runs-on: windows-latest
127
+ continue-on-error: true
128
+
129
+ steps:
130
+ - uses: actions/checkout@v6
131
+
132
+ - name: Set up uv
133
+ uses: astral-sh/setup-uv@v7
134
+ with:
135
+ enable-cache: true
136
+
137
+ - name: Set up Python
138
+ uses: i2mint/wads/actions/setup-python-uv@master
139
+ with:
140
+ python-version: ${{ fromJson(needs.setup.outputs.python-versions)[0] }}
141
+
142
+ - name: Install System Dependencies
143
+ uses: i2mint/wads/actions/install-system-deps@master
144
+ with:
145
+ pyproject-path: .
146
+
147
+ - name: Install Dependencies
148
+ uses: i2mint/wads/actions/install-deps-uv@master
149
+
150
+ - name: Run Tests
151
+ uses: i2mint/wads/actions/run-tests-uv@master
152
+ with:
153
+ root-dir: ${{ needs.setup.outputs.project-name }}
154
+ pytest-args: ${{ needs.setup.outputs.pytest-args }}
155
+ exclude-paths: ${{ needs.setup.outputs.exclude-paths }}
156
+
157
+ # Publishing job
158
+ publish:
159
+ name: Publish
160
+ permissions:
161
+ contents: write
162
+ if: "!contains(github.event.head_commit.message, '[skip ci]') && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main')"
163
+ needs: [setup, validation]
164
+ runs-on: ubuntu-latest
165
+
166
+ steps:
167
+ - uses: actions/checkout@v6
168
+ with:
169
+ fetch-depth: 0
170
+ token: ${{ secrets.GITHUB_TOKEN }}
171
+
172
+ - name: Set up uv
173
+ uses: astral-sh/setup-uv@v7
174
+
175
+ - name: Set up Python
176
+ uses: i2mint/wads/actions/setup-python-uv@master
177
+ with:
178
+ python-version: ${{ fromJson(needs.setup.outputs.python-versions)[0] }}
179
+ create-venv: "false"
180
+
181
+ - name: Format Source Code
182
+ if: needs.setup.outputs.ruff-enabled != 'false'
183
+ run: uvx ruff format .
184
+
185
+ - name: Format Source Code (black)
186
+ if: needs.setup.outputs.black-enabled == 'true'
187
+ run: uvx black .
188
+
189
+ - name: Update Version Number
190
+ id: version
191
+ uses: i2mint/isee/actions/bump-version-number@master
192
+
193
+ - name: Build Distribution
194
+ uses: i2mint/wads/actions/build-dist-uv@master
195
+ with:
196
+ sdist: ${{ needs.setup.outputs.build-sdist }}
197
+ wheel: ${{ needs.setup.outputs.build-wheel }}
198
+
199
+ - name: Publish to PyPI
200
+ uses: i2mint/wads/actions/pypi-publish-uv@master
201
+ with:
202
+ pypi-token: ${{ secrets.PYPI_PASSWORD }}
203
+
204
+ - name: Force SSH for git remote
205
+ run: git remote set-url origin git@github.com:${{ github.repository }}.git
206
+
207
+ - name: Commit Changes
208
+ uses: i2mint/wads/actions/git-commit@master
209
+ with:
210
+ commit-message: "**CI** Formatted code + Updated version to ${{ env.VERSION }} [skip ci]"
211
+ ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
212
+ push: true
213
+
214
+ - name: Tag Repository
215
+ uses: i2mint/wads/actions/git-tag@master
216
+ with:
217
+ tag: ${{ env.VERSION }}
218
+ message: "Release version ${{ env.VERSION }}"
219
+ push: true
220
+
221
+ # Optional GitHub Pages (skipped when [tool.wads.ci.docs].enabled = false)
222
+ github-pages:
223
+ name: Publish GitHub Pages
224
+ permissions:
225
+ contents: write
226
+ pages: write
227
+ id-token: write
228
+ if: "!contains(github.event.head_commit.message, '[skip ci]') && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && needs.setup.outputs.docs-enabled != 'false'"
229
+ needs: [setup, publish]
230
+ runs-on: ubuntu-latest
231
+
232
+ steps:
233
+ - uses: i2mint/epythet/actions/publish-github-pages@master
234
+ with:
235
+ github-token: ${{ secrets.GITHUB_TOKEN }}
236
+ ignore: "tests/,scrap/,examples/"
@@ -0,0 +1,112 @@
1
+ docs/*
2
+ # Byte-compiled / optimized / DLL files
3
+ __pycache__/
4
+ *.py[cod]
5
+ *$py.class
6
+
7
+ .DS_Store
8
+ # C extensions
9
+ *.so
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ .hypothesis/
50
+ .pytest_cache/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Django stuff:
57
+ *.log
58
+ local_settings.py
59
+ db.sqlite3
60
+
61
+ # Flask stuff:
62
+ instance/
63
+ .webassets-cache
64
+
65
+ # Scrapy stuff:
66
+ .scrapy
67
+
68
+ # Sphinx documentation
69
+ docs/_build/
70
+
71
+ # PyBuilder
72
+ target/
73
+
74
+ # Jupyter Notebook
75
+ .ipynb_checkpoints
76
+
77
+ # pyenv
78
+ .python-version
79
+
80
+ # celery beat schedule file
81
+ celerybeat-schedule
82
+
83
+ # SageMath parsed files
84
+ *.sage.py
85
+
86
+ # Environments
87
+ .env
88
+ .venv
89
+ env/
90
+ venv/
91
+ ENV/
92
+ env.bak/
93
+ venv.bak/
94
+
95
+ # Spyder project settings
96
+ .spyderproject
97
+ .spyproject
98
+
99
+ # Rope project settings
100
+ .ropeproject
101
+
102
+ # mkdocs documentation
103
+ /site
104
+
105
+ # mypy
106
+ .mypy_cache/
107
+
108
+ # PyCharm
109
+ .idea
110
+
111
+ # VS Code
112
+ .vscode
@@ -1,28 +1,44 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: http2py
3
- Version: 0.1.31
3
+ Version: 0.1.34
4
4
  Summary: Tools to create python binders to http web services.
5
- Home-page: https://github.com/i2mint/http2py
6
- License: Apache
7
- Keywords: webservice,http,requests,API
8
- Platform: any
9
- Description-Content-Type: text/markdown
5
+ Project-URL: Homepage, https://github.com/i2mint/http2py
6
+ Project-URL: Repository, https://github.com/i2mint/http2py
7
+ Author: Otosense
8
+ License-Expression: Apache-2.0
10
9
  License-File: LICENSE
10
+ Keywords: API,http,requests,webservice
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Internet :: WWW/HTTP
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: cw<0.2,>=0.1.1
20
+ Requires-Dist: dill
11
21
  Requires-Dist: glom
12
22
  Requires-Dist: i2
23
+ Requires-Dist: importlib-resources
13
24
  Requires-Dist: ju
25
+ Requires-Dist: pyyaml
14
26
  Requires-Dist: requests
15
- Requires-Dist: argh
16
- Requires-Dist: PyYAML
17
- Requires-Dist: importlib_resources
18
- Dynamic: license-file
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
29
+ Requires-Dist: pytest>=7.0; extra == 'dev'
30
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
31
+ Requires-Dist: setuptools; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ **Note: A new, lighterweight, more modern replacement: [ho](https://github.com/i2mint/ho)**
35
+
36
+ # (Http) Requests for humans
19
37
 
20
38
  [Documentation here](https://i2mint.github.io/http2py/)
21
39
 
22
40
  `the_one_where_we_have_an_http_service_that_we_want_call_through_normal_looking_python_functions`
23
41
 
24
- # (Http) Requests for humans
25
-
26
42
  Tools to create python binders to http web services.
27
43
  Here, we develop python data access to web content through http request such as web services or web pages.
28
44
 
@@ -1,9 +1,11 @@
1
+ **Note: A new, lighterweight, more modern replacement: [ho](https://github.com/i2mint/ho)**
2
+
3
+ # (Http) Requests for humans
4
+
1
5
  [Documentation here](https://i2mint.github.io/http2py/)
2
6
 
3
7
  `the_one_where_we_have_an_http_service_that_we_want_call_through_normal_looking_python_functions`
4
8
 
5
- # (Http) Requests for humans
6
-
7
9
  Tools to create python binders to http web services.
8
10
  Here, we develop python data access to web content through http request such as web services or web pages.
9
11
 
@@ -0,0 +1,18 @@
1
+ """Pytest configuration for the http2py repository.
2
+
3
+ ``http2py/tests`` stands up a real ``py2http`` web service, so it depends on a
4
+ test-only package that is not one of http2py's declared dependencies. Under
5
+ ``--doctest-modules`` an unimportable module is not a single failing test: it
6
+ aborts collection for the whole session. So that directory is excluded here.
7
+
8
+ CI excludes the same path via ``[tool.wads.ci.testing].exclude_paths`` in
9
+ pyproject.toml, which the wads ``run-tests-uv`` action turns into ``--ignore``
10
+ flags. Repeating it here is what makes a bare ``pytest`` (no flags, e.g. a local
11
+ run or an editor's test runner) behave the same way as CI.
12
+ ``tests/test_ci_collection_contract.py`` asserts the two lists stay in
13
+ agreement, so they cannot drift apart silently.
14
+ """
15
+
16
+ collect_ignore = [
17
+ "http2py/tests",
18
+ ]
@@ -1,12 +1,34 @@
1
- import argh
1
+ """Generate an installable python package from an OpenAPI specification.
2
+
3
+ Given a spec (or the URL of one), :func:`mk_api_pkg` writes a small source
4
+ distribution whose functions are bound to the service's routes.
5
+
6
+ The sdist is built by running ``setup.py sdist`` in a subprocess. This module
7
+ used to call ``setuptools.sandbox.run_setup``, which modern setuptools no longer
8
+ ships -- so merely *importing* it raised ``ImportError`` and the
9
+ ``api-pkg-maker`` console script could not start at all (i2mint/http2py#14).
10
+ The subprocess is also better behaved: ``sandbox.run_setup`` needed the process
11
+ to ``os.chdir`` into the build directory and never came back.
12
+
13
+ ``setuptools`` is still needed to *run* :func:`mk_api_pkg`, but it is
14
+ deliberately NOT a declared dependency of http2py: it is needed by this one
15
+ function, not by ``import http2py``, and a virtualenv created by ``uv`` does not
16
+ ship it. :func:`_check_build_backend` turns its absence into an actionable
17
+ message instead of a subprocess exit code.
18
+ """
19
+
2
20
  import os
3
21
  import shutil
22
+ import subprocess
23
+ import sys
4
24
  import tempfile
5
25
  from http2py.client import HttpClient
6
26
  from datetime import datetime, timezone
7
- from setuptools import sandbox
8
27
 
9
- OUTPUT_DIR = os.path.join(os.environ["HOME"], "http2py", "api_pkgs")
28
+ #: Where built packages are written. ``expanduser`` rather than
29
+ #: ``os.environ["HOME"]``: the latter is a module-import-time ``KeyError`` on
30
+ #: Windows, which would take the whole package down with it.
31
+ OUTPUT_DIR = os.path.join(os.path.expanduser("~"), "http2py", "api_pkgs")
10
32
 
11
33
  INIT_FILE_TPL = """from .funcs import {funcs}
12
34
  """
@@ -100,6 +122,8 @@ def mk_api_pkg(
100
122
  with open(filepath, "w") as file:
101
123
  print(content, file=file)
102
124
 
125
+ _check_build_backend()
126
+
103
127
  tempdir = tempfile.mkdtemp()
104
128
  try:
105
129
  api = HttpClient(openapi_spec=openapi_spec, url=openapi_url)
@@ -137,8 +161,20 @@ def mk_api_pkg(
137
161
  filepath=os.path.join(tempdir, f"setup.cfg"), content=setup_cfg_content
138
162
  )
139
163
  create_file(filepath=os.path.join(tempdir, f"setup.py"), content=SETUP_PY)
140
- os.chdir(tempdir)
141
- sandbox.run_setup("setup.py", ["sdist"])
164
+ build = subprocess.run(
165
+ [sys.executable, "setup.py", "sdist"],
166
+ cwd=tempdir,
167
+ capture_output=True,
168
+ text=True,
169
+ )
170
+ if build.returncode != 0:
171
+ # check=True would raise a CalledProcessError carrying only the exit
172
+ # status, discarding the captured output that says what went wrong.
173
+ raise RuntimeError(
174
+ f"`setup.py sdist` failed (exit {build.returncode}) while building "
175
+ f"{pkg_name!r}.\n--- stdout ---\n{build.stdout}"
176
+ f"\n--- stderr ---\n{build.stderr}"
177
+ )
142
178
  pkg_filename = f"{pkg_name}-{pkg_version}.tar.gz"
143
179
  if not os.path.exists(OUTPUT_DIR):
144
180
  os.makedirs(OUTPUT_DIR)
@@ -150,9 +186,34 @@ def mk_api_pkg(
150
186
  shutil.rmtree(tempdir)
151
187
 
152
188
 
189
+ def _check_build_backend():
190
+ """Fail early and legibly when the interpreter cannot build an sdist.
191
+
192
+ ``setup.py sdist`` needs setuptools in the *subprocess's* interpreter, which
193
+ is this one. Environments created by ``uv`` (and ``python -m venv
194
+ --without-pip``) do not have it. Without this check the user sees a bare
195
+ ``CalledProcessError`` with an exit status and no cause.
196
+ """
197
+ import importlib.util
198
+
199
+ if importlib.util.find_spec("setuptools") is None:
200
+ raise RuntimeError(
201
+ "mk_api_pkg builds a source distribution with `setup.py sdist`, "
202
+ f"which needs setuptools in {sys.executable}. Install it with: "
203
+ f"{sys.executable} -m pip install setuptools"
204
+ )
205
+
206
+
153
207
  def main():
154
- argh.dispatch_command(mk_api_pkg)
208
+ """Entry point for the ``api-pkg-maker`` console script; returns the exit code.
209
+
210
+ ``cw.dispatch`` returns the code rather than exiting, and
211
+ ``[project.scripts]`` wraps this in ``sys.exit(main())``.
212
+ """
213
+ import cw
214
+
215
+ return cw.dispatch(mk_api_pkg)
155
216
 
156
217
 
157
218
  if __name__ == "__main__":
158
- main()
219
+ raise SystemExit(main())
@@ -1,3 +1,10 @@
1
+ """Resolve credentials and build the auth callables that requests will use.
2
+
3
+ :func:`mk_auth` turns an auth specification -- inline values, environment
4
+ variables, or a JSON credentials file -- into something the request layer can
5
+ attach to outgoing calls.
6
+ """
7
+
1
8
  import json
2
9
  import os
3
10
  from pathlib import Path
@@ -1,4 +1,14 @@
1
- import argh
1
+ """Turn an http-bound python object into a command line interface.
2
+
3
+ Signatures are first made argparse-friendly (:func:`mk_argparse_friendly`),
4
+ then turned into a parser by :func:`mk_cli` and run by :func:`dispatch_cli`.
5
+
6
+ The parser is built by ``cw``. Its ``BY_NAME_IF_KWONLY`` naming is what
7
+ reproduces the grammar this module has always produced -- see
8
+ :data:`CLI_CONVENTION`.
9
+ """
10
+
11
+ import dataclasses
2
12
  from functools import wraps
3
13
  from glom import glom
4
14
  from inspect import signature
@@ -9,11 +19,30 @@ import requests
9
19
  from collections.abc import Callable, Iterable
10
20
  import yaml
11
21
 
22
+ import cw
23
+
12
24
  from i2.io_trans import JSONAnnotAndDfltIoTrans
13
25
  from i2.signatures import set_signature_of_func, Sig, KO
14
26
  from http2py import HttpClient
15
27
  from http2py.authentication import mk_auth, DFLT_CONFIG_FILENAME
16
28
 
29
+ #: How the command line is derived from a signature.
30
+ #:
31
+ #: Every function :func:`register_cli_method` builds has KEYWORD-ONLY parameters
32
+ #: only (plus ``*args``/``**kwargs`` on a no-argument route), because
33
+ #: :func:`Sig.merge_with_sig` is called with ``kind=KO``. For that shape,
34
+ #: ``BY_NAME_IF_KWONLY`` is what reproduces the grammar this module produced
35
+ #: under argh: every parameter is an option, required exactly when it has no
36
+ #: default -- ``get-thing -u UID -p PID [-l LIMIT]``.
37
+ #:
38
+ #: cw's ``ARGH`` default (``BY_NAME_IF_HAS_DEFAULT``) would NOT: it turns a
39
+ #: keyword-only parameter with no default into a *positional*, so every required
40
+ #: API argument would silently change spelling. That is not a cw bug -- it
41
+ #: faithfully reproduces argh's *explicit* ``BY_NAME_IF_HAS_DEFAULT`` policy.
42
+ #: This module never selected a policy, and argh's no-policy legacy mode is the
43
+ #: one that matches ``BY_NAME_IF_KWONLY`` here.
44
+ CLI_CONVENTION = dataclasses.replace(cw.ARGH, naming=cw.BY_NAME_IF_KWONLY)
45
+
17
46
 
18
47
  def mk_sig_argparse_friendly(sig):
19
48
  """Modifies a signature to change all leading underscores in param names
@@ -103,15 +132,20 @@ def mk_cli(
103
132
  for methodname, method in client_details.__dict__.items()
104
133
  if getattr(method, "method_spec", None)
105
134
  ]
106
- parser = argh.ArghParser()
107
- parser.add_commands(cli_methods)
108
- return parser
135
+ return cw.mk_parser(cli_methods, convention=CLI_CONVENTION)
109
136
 
110
137
 
111
138
  def dispatch_cli(*args, **kwargs):
112
- """Makes a CLI parser with mk_cli and then dispatches it (see documentation of mk_cli)"""
113
- parser = mk_cli(*args, **kwargs)
114
- parser.dispatch()
139
+ """Makes a CLI parser with mk_cli and then runs it (see documentation of mk_cli)
140
+
141
+ Returns ``None`` when the command ran, and raises ``SystemExit(2)`` on a
142
+ command-line error -- the same two outcomes the previous argh-based
143
+ implementation produced. ``cw.run`` *returns* the exit code rather than
144
+ exiting, so the non-zero case is re-raised here.
145
+ """
146
+ code = cw.run(mk_cli(*args, **kwargs))
147
+ if code:
148
+ raise SystemExit(code)
115
149
 
116
150
 
117
151
  set_signature_of_func(dispatch_cli, signature(mk_cli))
@@ -1,3 +1,9 @@
1
+ """The main entry point: a python object facading an http service.
2
+
3
+ :class:`HttpClient` takes an OpenAPI specification (a dict, or the URL of one)
4
+ and exposes each declared route as a normal python method.
5
+ """
6
+
1
7
  from glom import glom
2
8
  from requests import request, get, Session
3
9
  from i2.errors import AuthorizationError
@@ -1,3 +1,5 @@
1
+ """Content-type strings shared across the request and response machinery."""
2
+
1
3
  JSON_CONTENT_TYPE = "application/json"
2
4
  BINARY_CONTENT_TYPE = "application/octet-stream"
3
5
  FORM_CONTENT_TYPE = "multipart/form-data"
@@ -1,3 +1,10 @@
1
+ """Response handling: map http status codes to exceptions, decode the payload.
2
+
3
+ ``handle_json_resp`` / ``handle_binary_resp`` / ``handle_raw_resp`` wrap an
4
+ output transformation so it only ever sees a successful response of the
5
+ expected content type.
6
+ """
7
+
1
8
  from functools import partial
2
9
  from i2.errors import (
3
10
  AuthorizationError,
@@ -1,3 +1,5 @@
1
+ """Default output transformations used when a method spec does not supply one."""
2
+
1
3
  from http2py.decorators import (
2
4
  handle_raw_resp,
3
5
  handle_json_resp,