Skip to content

Commit 116eb89

Browse files
committed
B1- Feat.
1 parent 7baec9a commit 116eb89

25 files changed

Lines changed: 1629 additions & 43 deletions

.codecov.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
coverage:
2+
status:
3+
project:
4+
default:
5+
target: 100%
6+
precision: 2
7+
range: 80..100
8+
ignore:
9+
- "tests/*"
10+
- "scripts/*"

.coveragerc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[run]
2+
omit =
3+
*/tests/*
4+
5+
[report]
6+
exclude_lines =
7+
pragma: no cover
8+
if __name__ == .__main__:

.github/workflows/publish.yml

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
name: Build
2+
3+
on: push
4+
5+
# This workflow uses actions that are not certified by GitHub.
6+
# They are provided by a third-party and are governed by
7+
# separate terms of service, privacy policy, and support
8+
# documentation.
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
ensure-tag:
15+
runs-on: ubuntu-latest
16+
outputs:
17+
should_build: ${{ steps.detect.outputs.should_build }}
18+
# pyhttplib_version and release_display_name are computed in the build job
19+
# to keep version logic centralized in scripts/get_version.py
20+
# This job needs write permissions to push tags back to the repository
21+
permissions:
22+
contents: write
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
with:
27+
fetch-depth: 0
28+
29+
- name: Ensure release has a tag
30+
id: ensure_tag
31+
env:
32+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33+
run: |
34+
set -euo pipefail
35+
RELEASE_TAG="${{ github.event.release.tag_name }}"
36+
echo "Release tag (from event): '$RELEASE_TAG'"
37+
38+
if [ -n "$RELEASE_TAG" ]; then
39+
echo "Release already has tag: $RELEASE_TAG"
40+
echo "should_build=true" >> "$GITHUB_OUTPUT"
41+
exit 0
42+
fi
43+
44+
# Reuse the same detection logic the later step uses to find a candidate
45+
FOUND=0
46+
TAG=""
47+
for commit in $(git rev-list --max-count=200 HEAD); do
48+
msg=$(git log -1 --pretty=%B "$commit" | sed -n '1p' | tr -d '\r')
49+
if echo "$msg" | grep -q "-"; then
50+
candidate=$(echo "$msg" | cut -d'-' -f1 | tr -d '[:space:]')
51+
if echo "$candidate" | grep -E -q '^[0-9]+[A-Za-z]?$'; then
52+
FOUND=1
53+
TAG="$candidate"
54+
break
55+
fi
56+
fi
57+
done
58+
59+
if [ "$FOUND" -eq 0 ]; then
60+
echo "No matching commit found; should_build=false" >> "$GITHUB_OUTPUT"
61+
exit 0
62+
fi
63+
64+
echo "Found tag candidate from commits: $TAG"
65+
66+
# Push tag only if it doesn't exist remotely
67+
if git ls-remote --tags origin "refs/tags/$TAG" | grep -q "$TAG"; then
68+
echo "Tag $TAG already exists on remote"
69+
else
70+
echo "Creating tag: $TAG"
71+
git config user.name "github-actions[bot]"
72+
git config user.email "github-actions[bot]@users.noreply.github.com"
73+
git tag -a "$TAG" -m "Tag for release $TAG"
74+
git push origin "refs/tags/$TAG"
75+
fi
76+
echo "should_build=true" >> "$GITHUB_OUTPUT"
77+
# Export the chosen tag as a step output so later steps can use it
78+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
79+
80+
- name: Detect release candidate in commits
81+
id: detect
82+
run: |
83+
set -euo pipefail
84+
# If CI explicitly sets PYHTTPLIB_VERSION, build
85+
if [ -n "${PYHTTPLIB_VERSION-}" ]; then
86+
echo "should_build=true" >> "$GITHUB_OUTPUT"
87+
exit 0
88+
fi
89+
FOUND=0
90+
for commit in $(git rev-list --max-count=200 HEAD); do
91+
msg=$(git log -1 --pretty=%B "$commit" | sed -n '1p' | tr -d '\r')
92+
if echo "$msg" | grep -q "-"; then
93+
candidate=$(echo "$msg" | cut -d'-' -f1 | tr -d '[:space:]')
94+
if echo "$candidate" | grep -E -q '^[0-9]+[A-Za-z]?$'; then
95+
FOUND=1
96+
break
97+
fi
98+
fi
99+
done
100+
if [ "$FOUND" -eq 1 ]; then
101+
echo "should_build=true" >> "$GITHUB_OUTPUT"
102+
else
103+
echo "should_build=false" >> "$GITHUB_OUTPUT"
104+
fi
105+
106+
# The numeric package version is determined in the build job using
107+
# scripts/get_version.py; removing the set_version step keeps the
108+
# ensure-tag job focused on tagging only and avoids cross-job
109+
# heredoc/python invocation complexity.
110+
111+
build:
112+
runs-on: ubuntu-latest
113+
needs:
114+
- ensure-tag
115+
if: needs.ensure-tag.outputs.should_build == 'true'
116+
outputs:
117+
pyhttplib_version: ${{ steps.get_version.outputs.pyhttplib_version }}
118+
release_display_name: ${{ steps.get_version.outputs.release_display_name }}
119+
120+
steps:
121+
- uses: actions/checkout@v4
122+
123+
- uses: actions/setup-python@v5
124+
with:
125+
python-version: "3.x"
126+
127+
- name: Determine package version
128+
id: get_version
129+
run: |
130+
set -euo pipefail
131+
python -c "from scripts.get_version import get_pyhttplib_version,get_release_display_name; print(get_pyhttplib_version()); print(get_release_display_name())" > /tmp/_vers
132+
echo "pyhttplib_version=$(sed -n '1p' /tmp/_vers)" >> "$GITHUB_OUTPUT"
133+
echo "release_display_name=$(sed -n '2p' /tmp/_vers)" >> "$GITHUB_OUTPUT"
134+
135+
- name: Log resolved version
136+
run: |
137+
set -euo pipefail
138+
echo "Resolved PYHTTPLIB_VERSION=${{ steps.get_version.outputs.pyhttplib_version }}"
139+
echo "Resolved RELEASE_DISPLAY_NAME=${{ steps.get_version.outputs.release_display_name }}"
140+
# Append to the GitHub Actions job summary for quick visibility
141+
if [ -n "${GITHUB_STEP_SUMMARY-}" ]; then
142+
echo "### pyhttplib version info" >> "$GITHUB_STEP_SUMMARY"
143+
echo "* PYHTTPLIB_VERSION: ${{ steps.get_version.outputs.pyhttplib_version }}" >> "$GITHUB_STEP_SUMMARY"
144+
echo "* RELEASE_DISPLAY_NAME: ${{ steps.get_version.outputs.release_display_name }}" >> "$GITHUB_STEP_SUMMARY"
145+
fi
146+
147+
- name: Build release distributions
148+
run: |
149+
set -euo pipefail
150+
# Clean any previous build artifacts so we don't upload stale packages
151+
rm -rf dist || true
152+
python -m pip install --upgrade build
153+
python -m build
154+
155+
- name: Upload distributions
156+
uses: actions/upload-artifact@v4
157+
with:
158+
name: release-dists
159+
path: dist/
160+
161+
pypi-publish:
162+
runs-on: ubuntu-latest
163+
needs:
164+
- build
165+
if: needs.build.result == 'success' && needs.ensure-tag.outputs.should_build == 'true'
166+
167+
# Dedicated environments with protections for publishing are strongly recommended.
168+
# For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
169+
environment:
170+
name: pypi
171+
# OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
172+
url: https://pypi.org/project/pyhttplib
173+
#
174+
# ALTERNATIVE: if your GitHub Release name is the PyPI project version string
175+
# ALTERNATIVE: exactly, uncomment the following line instead:
176+
# url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }}
177+
178+
permissions:
179+
id-token: write
180+
181+
steps:
182+
- name: Checkout repository
183+
uses: actions/checkout@v4
184+
with:
185+
fetch-depth: 0
186+
- name: Determine version and check PyPI
187+
id: check_pypi
188+
run: |
189+
set -euo pipefail
190+
# Prefer the canonical version discovered during the build job.
191+
if [ -n "${{ needs.build.outputs.pyhttplib_version }}" ]; then
192+
VER="${{ needs.build.outputs.pyhttplib_version }}"
193+
echo "Using version from build job: $VER"
194+
else
195+
# Fall back to inferring from the sdist filename
196+
SDIST=$(ls dist/*.tar.gz 2>/dev/null | head -n 1 || true)
197+
if [ -z "$SDIST" ]; then
198+
echo "No sdist found in dist/; cannot determine version" >&2
199+
echo "skip_publish=true" >> "$GITHUB_OUTPUT"
200+
exit 0
201+
fi
202+
FNAME=$(basename "$SDIST")
203+
VER=$(echo "$FNAME" | sed -E 's/^[Pp][Yy][Hh][Tt][Tt][Pp][Ll][Ii][Bb]-//; s/\.tar\.gz$//')
204+
fi
205+
echo "version=$VER" >> "$GITHUB_OUTPUT"
206+
echo "Version to publish: $VER"
207+
208+
# Query PyPI for the version; 200 means it already exists.
209+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/pyhttplib/$VER/json" || true)
210+
if [ "$STATUS" = "200" ]; then
211+
echo "Version $VER already exists on PyPI; skipping publish"
212+
echo "skip_publish=true" >> "$GITHUB_OUTPUT"
213+
else
214+
echo "skip_publish=false" >> "$GITHUB_OUTPUT"
215+
fi
216+
217+
- name: Clean dist directory
218+
if: steps.check_pypi.outputs.skip_publish != 'true'
219+
run: |
220+
set -euo pipefail
221+
rm -rf dist || true
222+
223+
- name: Retrieve release distributions
224+
if: steps.check_pypi.outputs.skip_publish != 'true'
225+
uses: actions/download-artifact@v4
226+
with:
227+
name: release-dists
228+
path: dist/
229+
230+
- name: Select latest artifact and prune others
231+
if: steps.check_pypi.outputs.skip_publish != 'true'
232+
run: |
233+
set -euo pipefail
234+
echo "Listing dist contents before selection:"
235+
ls -l dist || true
236+
237+
# Find newest sdist by modification time
238+
SDIST=$(ls -t dist/*.tar.gz 2>/dev/null | head -n 1 || true)
239+
if [ -z "$SDIST" ]; then
240+
echo "No sdist found in dist/; nothing to publish" >&2
241+
ls -l dist || true
242+
exit 1
243+
fi
244+
245+
echo "Selected sdist: $SDIST"
246+
FNAME=$(basename "$SDIST")
247+
# Normalize project name prefix and strip .tar.gz to obtain version
248+
VER=$(echo "$FNAME" | sed -E 's/^[Pp][Yy]?[Nn]?[Ee]?[Tt]?[Ss]?[Pp]?[Ll]?[Ii]?[Tt]-//; s/\.tar\.gz$//')
249+
echo "Determined version: $VER"
250+
251+
# Remove other sdists not matching the selected version
252+
for f in dist/*.tar.gz; do
253+
if [ -f "$f" ] && [ "$f" != "$SDIST" ]; then
254+
echo "Removing extra sdist: $f"
255+
rm -f "$f"
256+
fi
257+
done
258+
259+
echo "dist/ contents after pruning:"
260+
ls -l dist || true
261+
262+
- name: Prune wheels by METADATA Version
263+
if: steps.check_pypi.outputs.skip_publish != 'true'
264+
env:
265+
SELECTED_VER: ${{ steps.check_pypi.outputs.version }}
266+
run: |
267+
set -euo pipefail
268+
python scripts/prune_wheels.py
269+
270+
- name: Publish to PyPI (OIDC)
271+
if: steps.check_pypi.outputs.skip_publish != 'true'
272+
uses: pypa/gh-action-pypi-publish@release/v1
273+
with:
274+
packages-dir: dist/

.github/workflows/pytest.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: pytest
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Set up Python
17+
uses: actions/setup-python@v4
18+
with:
19+
python-version: '3.11'
20+
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install pytest pytest-asyncio coverage codecov
25+
26+
- name: Run tests with coverage
27+
run: |
28+
python -m coverage run -m pytest
29+
python -m coverage xml -o coverage.xml
30+
python -m coverage report -m
31+
32+
- name: Upload coverage to Codecov
33+
uses: codecov/codecov-action@v4
34+
with:
35+
# Upload the generated coverage.xml. For public repos a token is
36+
# not required, but you can add CODECOV_TOKEN as a secret if needed.
37+
files: ./coverage.xml
38+
# Add a flag/name so uploads are easy to identify in Codecov UI
39+
flags: unittests
40+
name: pytest-coverage
41+
# Do not fail the CI if upload to Codecov has an issue (helps debug badge problems)
42+
fail_ci_if_error: false
43+
44+
- name: Show coverage.xml (first 40 lines) # helps debug Codecov uploads
45+
if: always()
46+
run: |
47+
echo '--- coverage.xml (preview) ---'
48+
head -n 40 coverage.xml || true

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
examples/chat/users.json
2+
*.pyc
3+
*.pyd
4+
*__*
5+
*-info*
6+
dist
7+
*cache
8+
*.gz
9+
*.whl
10+
dist/
11+
*.egg-info/
12+
.coverage

.vscode/settings.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
{
22
"python.analysis.typeCheckingMode": "strict",
3-
"python.REPL.enableREPLSmartSend": false
3+
"python.REPL.enableREPLSmartSend": false,
4+
"python.testing.pytestArgs": [
5+
"tests"
6+
],
7+
"python.testing.unittestEnabled": false,
8+
"python.testing.pytestEnabled": true,
9+
"python.defaultInterpreterPath": "d:\\GitHub\\http\\.venv\\Scripts\\python.exe"
410
}

0 commit comments

Comments
 (0)