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.
- http2py-0.1.34/.gitattributes +1 -0
- http2py-0.1.34/.github/workflows/ci.yml +236 -0
- http2py-0.1.34/.gitignore +112 -0
- {http2py-0.1.31 → http2py-0.1.34}/PKG-INFO +29 -13
- {http2py-0.1.31 → http2py-0.1.34}/README.md +4 -2
- http2py-0.1.34/conftest.py +18 -0
- {http2py-0.1.31 → http2py-0.1.34}/http2py/api_pkg_maker.py +68 -7
- {http2py-0.1.31 → http2py-0.1.34}/http2py/authentication.py +7 -0
- {http2py-0.1.31 → http2py-0.1.34}/http2py/cli_maker.py +41 -7
- {http2py-0.1.31 → http2py-0.1.34}/http2py/client.py +6 -0
- {http2py-0.1.31 → http2py-0.1.34}/http2py/constants.py +2 -0
- {http2py-0.1.31 → http2py-0.1.34}/http2py/decorators.py +7 -0
- {http2py-0.1.31 → http2py-0.1.34}/http2py/default_configs.py +2 -0
- http2py-0.1.34/http2py/example_cli.py +23 -0
- {http2py-0.1.31 → http2py-0.1.34}/http2py/global_state.py +2 -0
- {http2py-0.1.31 → http2py-0.1.34}/http2py/py2request.py +6 -6
- {http2py-0.1.31 → http2py-0.1.34}/http2py/testing_utils.py +6 -0
- http2py-0.1.34/http2py/tests/__init__.py +1 -0
- http2py-0.1.34/http2py/tests/conftest.py +44 -0
- http2py-0.1.34/http2py/tests/test_requirements.txt +1 -0
- {http2py-0.1.31 → http2py-0.1.34}/http2py/util.py +14 -4
- http2py-0.1.34/pyproject.toml +196 -0
- http2py-0.1.34/tests/cli_goldens/mk_cli.json +10 -0
- http2py-0.1.34/tests/spec_fixture.py +89 -0
- http2py-0.1.34/tests/test_ci_collection_contract.py +215 -0
- http2py-0.1.34/tests/test_cli_surface.py +171 -0
- http2py-0.1.34/tests/test_dependency_workarounds.py +85 -0
- http2py-0.1.34/tests/test_smoke.py +84 -0
- http2py-0.1.31/http2py/example_cli.py +0 -16
- http2py-0.1.31/http2py/tests/conftest.py +0 -30
- http2py-0.1.31/http2py.egg-info/PKG-INFO +0 -88
- http2py-0.1.31/http2py.egg-info/SOURCES.txt +0 -28
- http2py-0.1.31/http2py.egg-info/dependency_links.txt +0 -1
- http2py-0.1.31/http2py.egg-info/entry_points.txt +0 -2
- http2py-0.1.31/http2py.egg-info/not-zip-safe +0 -1
- http2py-0.1.31/http2py.egg-info/requires.txt +0 -7
- http2py-0.1.31/http2py.egg-info/top_level.txt +0 -1
- http2py-0.1.31/setup.cfg +0 -43
- http2py-0.1.31/setup.py +0 -3
- {http2py-0.1.31 → http2py-0.1.34}/LICENSE +0 -0
- {http2py-0.1.31 → http2py-0.1.34}/http2py/__init__.py +0 -0
- {http2py-0.1.31 → http2py-0.1.34}/http2py/http2py_cls.py +0 -0
- /http2py-0.1.31/http2py/tests/__init__.py → /http2py-0.1.34/http2py/resources/module_maker/func_tpl.txt +0 -0
- {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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: http2py
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.34
|
|
4
4
|
Summary: Tools to create python binders to http web services.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
16
|
-
Requires-Dist:
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
113
|
-
|
|
114
|
-
|
|
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,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,
|