mirror of
https://github.com/jorijn/meshcore-stats.git
synced 2026-03-28 17:42:55 +01:00
Compare commits
27 Commits
v0.2.10
...
jorijn-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7001ada265 | ||
|
|
18ca787f7f | ||
|
|
f727028246 | ||
|
|
6fc2e762cf | ||
|
|
df0c374b65 | ||
|
|
570a086c8c | ||
|
|
3a0306043c | ||
|
|
1775c491ad | ||
|
|
a3a5964488 | ||
|
|
3967fd032a | ||
|
|
97223f137c | ||
|
|
46fc383eaa | ||
|
|
f55c236080 | ||
|
|
b66f5380b6 | ||
|
|
83425a48f6 | ||
|
|
9cb95f8108 | ||
|
|
1f6e7c5093 | ||
|
|
57a53a8800 | ||
|
|
83cf2bf929 | ||
|
|
40d7d3b2fa | ||
|
|
d4b5885379 | ||
|
|
dd7ec5b46e | ||
|
|
e937f2b0b7 | ||
|
|
adc442351b | ||
|
|
3fa002d2a4 | ||
|
|
26d5125e15 | ||
|
|
fb627fdacd |
@@ -1,140 +0,0 @@
|
||||
---
|
||||
name: test-engineer
|
||||
description: Use this agent when you need to write, review, or improve tests for code. This includes unit tests, integration tests, end-to-end tests, and test infrastructure. Also use when evaluating test coverage, identifying untested edge cases, or refactoring existing tests for better maintainability. Examples:\n\n<example>\nContext: User has just written a new function and wants tests for it.\nuser: "I just wrote this battery voltage calculation function, can you help me test it?"\nassistant: "Let me use the test-engineer agent to create comprehensive tests for your battery calculation function."\n<commentary>\nSince the user wants tests written for new code, use the test-engineer agent to create thorough test cases covering normal operation, edge cases, and error conditions.\n</commentary>\n</example>\n\n<example>\nContext: User wants to improve existing test coverage.\nuser: "Our test coverage for the database module is only 60%, can you help improve it?"\nassistant: "I'll use the test-engineer agent to analyze the gaps in your database module tests and create additional test cases."\n<commentary>\nThe user needs help identifying untested code paths and writing new tests, which is exactly what the test-engineer agent specializes in.\n</commentary>\n</example>\n\n<example>\nContext: User has written code and the assistant should proactively suggest testing.\nuser: "Here's my new retry logic with exponential backoff"\nassistant: "I've reviewed your retry logic implementation. Now let me use the test-engineer agent to create tests that verify the backoff behavior, edge cases like max retries, and error handling."\n<commentary>\nAfter code is written, proactively use the test-engineer agent to ensure the new functionality has proper test coverage.\n</commentary>\n</example>
|
||||
model: opus
|
||||
---
|
||||
|
||||
You are an expert test engineer with deep expertise in software testing methodologies, test-driven development, and quality assurance. Your primary focus is Python testing (pytest, unittest, hypothesis), but you are also proficient in testing frameworks across JavaScript/TypeScript (Jest, Vitest, Mocha), Go, Rust, and other languages.
|
||||
|
||||
## Core Expertise
|
||||
|
||||
### Testing Principles
|
||||
- Write tests that are fast, isolated, repeatable, self-validating, and timely (F.I.R.S.T.)
|
||||
- Follow the Arrange-Act-Assert (AAA) pattern for clear test structure
|
||||
- Apply the testing pyramid: prioritize unit tests, supplement with integration tests, minimize end-to-end tests
|
||||
- Test behavior, not implementation details
|
||||
- Each test should verify one specific behavior
|
||||
|
||||
### Python Testing (Primary Focus)
|
||||
- **pytest**: fixtures, parametrization, markers, conftest.py organization, plugins
|
||||
- **unittest**: TestCase classes, setUp/tearDown, mock module
|
||||
- **hypothesis**: property-based testing, strategies, shrinking
|
||||
- **coverage.py**: measuring and improving test coverage
|
||||
- **mocking**: unittest.mock, pytest-mock, when and how to mock appropriately
|
||||
- **async testing**: pytest-asyncio, testing coroutines and async generators
|
||||
|
||||
### Test Categories You Handle
|
||||
1. **Unit Tests**: Isolated function/method testing with mocked dependencies
|
||||
2. **Integration Tests**: Testing component interactions, database operations, API calls
|
||||
3. **End-to-End Tests**: Full system testing, UI automation
|
||||
4. **Property-Based Tests**: Generating test cases to find edge cases
|
||||
5. **Regression Tests**: Preventing bug recurrence
|
||||
6. **Performance Tests**: Benchmarking, load testing considerations
|
||||
|
||||
## Your Approach
|
||||
|
||||
### When Writing Tests
|
||||
1. Identify the function/module's contract: inputs, outputs, side effects, exceptions
|
||||
2. List test cases covering:
|
||||
- Happy path (normal operation)
|
||||
- Edge cases (empty inputs, boundaries, None/null values)
|
||||
- Error conditions (invalid inputs, exceptions)
|
||||
- State transitions (if applicable)
|
||||
3. Write clear, descriptive test names that explain what is being tested
|
||||
4. Use fixtures for common setup, parametrize for similar test variations
|
||||
5. Keep tests independent - no test should depend on another's execution
|
||||
|
||||
### When Reviewing Tests
|
||||
1. Check for missing edge cases and error scenarios
|
||||
2. Identify flaky tests (time-dependent, order-dependent, external dependencies)
|
||||
3. Look for over-mocking that makes tests meaningless
|
||||
4. Verify assertions are specific and meaningful
|
||||
5. Ensure test names clearly describe what they verify
|
||||
6. Check for proper cleanup and resource management
|
||||
|
||||
### Test Naming Convention
|
||||
Use descriptive names that explain the scenario:
|
||||
- `test_<function>_<scenario>_<expected_result>`
|
||||
- Example: `test_calculate_battery_percentage_at_minimum_voltage_returns_zero`
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
### Test Structure
|
||||
```python
|
||||
def test_function_name_describes_behavior():
|
||||
# Arrange - set up test data and dependencies
|
||||
input_data = create_test_data()
|
||||
|
||||
# Act - call the function under test
|
||||
result = function_under_test(input_data)
|
||||
|
||||
# Assert - verify the expected outcome
|
||||
assert result == expected_value
|
||||
```
|
||||
|
||||
### Fixture Best Practices
|
||||
- Use fixtures for reusable setup, not for test logic
|
||||
- Prefer function-scoped fixtures unless sharing is necessary
|
||||
- Use `yield` for cleanup in fixtures
|
||||
- Document what each fixture provides
|
||||
|
||||
### Mocking Guidelines
|
||||
- Mock at the boundary (external services, databases, file systems)
|
||||
- Don't mock the thing you're testing
|
||||
- Verify mock calls when the interaction itself is the behavior being tested
|
||||
- Use `autospec=True` to catch interface mismatches
|
||||
|
||||
## Edge Cases to Always Consider
|
||||
|
||||
### For Numeric Functions
|
||||
- Zero, negative numbers, very large numbers
|
||||
- Floating point precision issues
|
||||
- Integer overflow (in typed languages)
|
||||
- Division by zero scenarios
|
||||
|
||||
### For String/Text Functions
|
||||
- Empty strings, whitespace-only strings
|
||||
- Unicode characters, emoji, RTL text
|
||||
- Very long strings
|
||||
- Special characters and escape sequences
|
||||
|
||||
### For Collections
|
||||
- Empty collections
|
||||
- Single-element collections
|
||||
- Very large collections
|
||||
- None/null elements within collections
|
||||
- Duplicate elements
|
||||
|
||||
### For Time/Date Functions
|
||||
- Timezone boundaries, DST transitions
|
||||
- Leap years, month boundaries
|
||||
- Unix epoch edge cases
|
||||
- Far future/past dates
|
||||
|
||||
### For I/O Operations
|
||||
- File not found, permission denied
|
||||
- Network timeouts, connection failures
|
||||
- Partial reads/writes
|
||||
- Concurrent access
|
||||
|
||||
## Output Format
|
||||
|
||||
When writing tests, provide:
|
||||
1. Complete, runnable test code
|
||||
2. Brief explanation of what each test verifies
|
||||
3. Any additional test cases that should be considered
|
||||
4. Required fixtures or test utilities
|
||||
|
||||
When reviewing tests, provide:
|
||||
1. Specific issues found with line references
|
||||
2. Missing test cases that should be added
|
||||
3. Suggested improvements with code examples
|
||||
4. Overall assessment of test quality and coverage
|
||||
|
||||
## Project-Specific Considerations
|
||||
|
||||
When working in projects with existing test conventions:
|
||||
- Follow the established test file organization
|
||||
- Use existing fixtures and utilities where appropriate
|
||||
- Match the naming conventions already in use
|
||||
- Respect any project-specific testing requirements from documentation like CLAUDE.md
|
||||
47
.github/workflows/docker-publish.yml
vendored
47
.github/workflows/docker-publish.yml
vendored
@@ -20,6 +20,17 @@ on:
|
||||
# Daily at 4 AM UTC - rebuild with fresh base image
|
||||
- cron: "0 4 * * *"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- Dockerfile
|
||||
- .dockerignore
|
||||
- docker/**
|
||||
- pyproject.toml
|
||||
- uv.lock
|
||||
- src/**
|
||||
- scripts/**
|
||||
- .github/workflows/docker-publish.yml
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
push:
|
||||
@@ -45,6 +56,7 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name != 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
@@ -197,7 +209,7 @@ jobs:
|
||||
# Vulnerability scanning
|
||||
- name: Run Trivy vulnerability scanner
|
||||
if: "!(github.event_name == 'schedule' && steps.get-version.outputs.skip == 'true')"
|
||||
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # v0.33.1
|
||||
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
|
||||
with:
|
||||
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image-tag.outputs.tag }}
|
||||
format: "sarif"
|
||||
@@ -207,7 +219,7 @@ jobs:
|
||||
|
||||
- name: Upload Trivy scan results
|
||||
if: "!(github.event_name == 'schedule' && steps.get-version.outputs.skip == 'true')"
|
||||
uses: github/codeql-action/upload-sarif@6e4b8622b82fab3c6ad2a7814fad1effc7615bc8 # v3.28.4
|
||||
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
sarif_file: "trivy-results.sarif"
|
||||
continue-on-error: true
|
||||
@@ -228,8 +240,37 @@ jobs:
|
||||
# Attestation (releases only)
|
||||
- name: Generate attestation
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/attest-build-provenance@46a583fd92dfbf46b772907a9740f888f4324bb9 # v3.1.0
|
||||
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0
|
||||
with:
|
||||
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
subject-digest: ${{ steps.build-release.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
build-pr:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Build image (PR)
|
||||
id: build-pr
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
load: true
|
||||
push: false
|
||||
tags: meshcore-stats:pr-${{ github.event.pull_request.number }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Smoke test (PR)
|
||||
run: |
|
||||
docker run --rm meshcore-stats:pr-${{ github.event.pull_request.number }} \
|
||||
python -c "from meshmon.db import init_db; from meshmon.env import get_config; print('Smoke test passed')"
|
||||
|
||||
2
.github/workflows/release-please.yml
vendored
2
.github/workflows/release-please.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Release Please
|
||||
uses: googleapis/release-please-action@c3fc4de07084f75a2b61a5b933069bda6edf3d5c # v4
|
||||
uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4
|
||||
with:
|
||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
||||
config-file: release-please-config.json
|
||||
|
||||
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
@@ -17,12 +17,12 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11", "3.12"]
|
||||
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
@@ -68,8 +68,8 @@ jobs:
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload coverage HTML report
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: always() && matrix.python-version == '3.12'
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
if: always() && matrix.python-version == '3.14'
|
||||
with:
|
||||
name: coverage-report-html-${{ matrix.python-version }}
|
||||
path: htmlcov/
|
||||
@@ -77,8 +77,8 @@ jobs:
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload coverage XML report
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: always() && matrix.python-version == '3.12'
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
if: always() && matrix.python-version == '3.14'
|
||||
with:
|
||||
name: coverage-report-xml-${{ matrix.python-version }}
|
||||
path: coverage.xml
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-${{ matrix.python-version }}
|
||||
@@ -97,17 +97,17 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
with:
|
||||
enable-cache: true
|
||||
python-version: "3.12"
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Install linters
|
||||
run: uv sync --locked --extra dev --no-install-project
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.2.10"
|
||||
".": "0.2.13"
|
||||
}
|
||||
|
||||
@@ -254,6 +254,7 @@ Example: `fix(charts): prevent crash when no data points available`
|
||||
2. release-please creates/updates a "Release PR" with:
|
||||
- Updated `CHANGELOG.md`
|
||||
- Updated version in `src/meshmon/__init__.py`
|
||||
- Updated `uv.lock` (project version entry)
|
||||
3. When the Release PR is merged:
|
||||
- A GitHub Release is created
|
||||
- A git tag (e.g., `v0.2.0`) is created
|
||||
@@ -365,10 +366,11 @@ Jobs configured in `docker/ofelia.ini`:
|
||||
| Release | `X.Y.Z`, `X.Y`, `latest` |
|
||||
| Nightly (4 AM UTC) | Rebuilds all version tags + `nightly`, `nightly-YYYYMMDD` |
|
||||
| Manual | `sha-xxxxxx` |
|
||||
| Pull request | Builds image (linux/amd64) without pushing and runs a smoke test |
|
||||
|
||||
**Nightly rebuilds** ensure version tags always include the latest OS security patches. This is a common pattern used by official Docker images (nginx, postgres, node). Users needing reproducibility should pin by SHA digest or use dated nightly tags.
|
||||
|
||||
All GitHub Actions are pinned by full SHA for security. Dependabot can be configured to update these automatically.
|
||||
GitHub Actions use version tags in workflows, and Renovate is configured in `renovate.json` to pin action digests, maintain lockfiles, and auto-merge patch + digest updates once required checks pass (with automatic rebases when behind `main`).
|
||||
|
||||
The test and lint workflow (`.github/workflows/test.yml`) installs dependencies with uv (`uv sync --locked --extra dev`) and runs commands via `uv run`, using `uv.lock` as the source of truth.
|
||||
|
||||
|
||||
45
CHANGELOG.md
45
CHANGELOG.md
@@ -4,6 +4,51 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
This changelog is automatically generated by [release-please](https://github.com/googleapis/release-please) based on [Conventional Commits](https://www.conventionalcommits.org/).
|
||||
|
||||
## [0.2.13](https://github.com/jorijn/meshcore-stats/compare/v0.2.12...v0.2.13) (2026-01-09)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* drop digest from compose image ([#59](https://github.com/jorijn/meshcore-stats/issues/59)) ([3a03060](https://github.com/jorijn/meshcore-stats/commit/3a0306043c8a8dfeb1b5b6df6fa988322cc64e98))
|
||||
|
||||
## [0.2.12](https://github.com/jorijn/meshcore-stats/compare/v0.2.11...v0.2.12) (2026-01-09)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* **deps:** lock file maintenance ([#52](https://github.com/jorijn/meshcore-stats/issues/52)) ([d4b5885](https://github.com/jorijn/meshcore-stats/commit/d4b5885379c06988bd8261039c67c6a6724b7704))
|
||||
* **deps:** lock file maintenance ([#58](https://github.com/jorijn/meshcore-stats/issues/58)) ([a3a5964](https://github.com/jorijn/meshcore-stats/commit/a3a5964488e7fbda5b6d792fa9f0f712e0a0d0c3))
|
||||
* **deps:** pin dependencies ([#55](https://github.com/jorijn/meshcore-stats/issues/55)) ([9cb95f8](https://github.com/jorijn/meshcore-stats/commit/9cb95f8108738ff21a8346f8922fcd218843fb7d))
|
||||
* **deps:** pin python docker tag to e8a1ad8 ([#57](https://github.com/jorijn/meshcore-stats/issues/57)) ([f55c236](https://github.com/jorijn/meshcore-stats/commit/f55c236080f6c9bc7a7f090f4382cd53281fc2ac))
|
||||
* **deps:** update actions/attest-build-provenance digest to 00014ed ([#40](https://github.com/jorijn/meshcore-stats/issues/40)) ([e937f2b](https://github.com/jorijn/meshcore-stats/commit/e937f2b0b7a34bb5c7f3f51b60a592f78a78079d))
|
||||
* **deps:** update actions/checkout action to v6 ([#48](https://github.com/jorijn/meshcore-stats/issues/48)) ([3967fd0](https://github.com/jorijn/meshcore-stats/commit/3967fd032ad95873bc50c438351ba52e6448a335))
|
||||
* **deps:** update actions/setup-python action to v6 ([#49](https://github.com/jorijn/meshcore-stats/issues/49)) ([97223f1](https://github.com/jorijn/meshcore-stats/commit/97223f137ca069f6f2632e2e849274cced91a8b3))
|
||||
* **deps:** update actions/upload-artifact action to v6 ([#50](https://github.com/jorijn/meshcore-stats/issues/50)) ([46fc383](https://github.com/jorijn/meshcore-stats/commit/46fc383eaa9cd99185a5b2112e58d5ff163f3185))
|
||||
* **deps:** update ghcr.io/astral-sh/uv docker tag to v0.9.22 ([#44](https://github.com/jorijn/meshcore-stats/issues/44)) ([83cf2bf](https://github.com/jorijn/meshcore-stats/commit/83cf2bf929bfba9f7019e78767abf04abe7700d2))
|
||||
* **deps:** update github/codeql-action action to v4 ([#51](https://github.com/jorijn/meshcore-stats/issues/51)) ([83425a4](https://github.com/jorijn/meshcore-stats/commit/83425a48f67a5d974065b9d33ad0a24a044d67d0))
|
||||
* **deps:** update github/codeql-action digest to ee117c9 ([#41](https://github.com/jorijn/meshcore-stats/issues/41)) ([dd7ec5b](https://github.com/jorijn/meshcore-stats/commit/dd7ec5b46e92365dbf2731f2378b2168c24f0b88))
|
||||
* **deps:** update nginx docker tag to v1.29 ([#47](https://github.com/jorijn/meshcore-stats/issues/47)) ([57a53a8](https://github.com/jorijn/meshcore-stats/commit/57a53a8800c9c97459ef5139310a8c23c7540943))
|
||||
* support python 3.14 in CI and docker ([#56](https://github.com/jorijn/meshcore-stats/issues/56)) ([b66f538](https://github.com/jorijn/meshcore-stats/commit/b66f5380b69108f22d53aaf1a48642c240788d3f))
|
||||
* switch to Renovate and pin uv image ([#38](https://github.com/jorijn/meshcore-stats/issues/38)) ([adc4423](https://github.com/jorijn/meshcore-stats/commit/adc442351bc84beb6216eafedd8e2eaa95109bfd))
|
||||
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
* **docker:** add PR build and smoke test ([#53](https://github.com/jorijn/meshcore-stats/issues/53)) ([40d7d3b](https://github.com/jorijn/meshcore-stats/commit/40d7d3b2faef5ae7c268cd1ecc9616d1dd421f12))
|
||||
* switch actions to version tags for renovate digests ([#54](https://github.com/jorijn/meshcore-stats/issues/54)) ([1f6e7c5](https://github.com/jorijn/meshcore-stats/commit/1f6e7c50935265579be4faadeb5dc88c4098a71c))
|
||||
|
||||
## [0.2.11](https://github.com/jorijn/meshcore-stats/compare/v0.2.10...v0.2.11) (2026-01-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docker:** skip project install in uv sync ([#35](https://github.com/jorijn/meshcore-stats/issues/35)) ([26d5125](https://github.com/jorijn/meshcore-stats/commit/26d5125e15a78fd7b3fddd09292b4aff6efd23b7))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* **release:** track uv.lock in release-please ([#33](https://github.com/jorijn/meshcore-stats/issues/33)) ([fb627fd](https://github.com/jorijn/meshcore-stats/commit/fb627fdacd1b58d0c8fc10b8d3d8738a1bdce799))
|
||||
|
||||
## [0.2.10](https://github.com/jorijn/meshcore-stats/compare/v0.2.9...v0.2.10) (2026-01-08)
|
||||
|
||||
|
||||
|
||||
16
Dockerfile
16
Dockerfile
@@ -1,7 +1,12 @@
|
||||
# =============================================================================
|
||||
# Stage 0: uv binary
|
||||
# =============================================================================
|
||||
FROM ghcr.io/astral-sh/uv:0.9.24@sha256:816fdce3387ed2142e37d2e56e1b1b97ccc1ea87731ba199dc8a25c04e4997c5 AS uv
|
||||
|
||||
# =============================================================================
|
||||
# Stage 1: Build dependencies
|
||||
# =============================================================================
|
||||
FROM python:3.12-slim-bookworm AS builder
|
||||
FROM python:3.14-slim-bookworm@sha256:e8a1ad81a9fef9dc56372fb49b50818cac71f5fae238b21d7738d73ccae8f803 AS builder
|
||||
|
||||
# Ofelia version and checksums (verified from GitHub releases)
|
||||
ARG OFELIA_VERSION=0.3.12
|
||||
@@ -37,15 +42,18 @@ RUN python -m venv /opt/venv
|
||||
ENV PATH="/opt/venv/bin:$PATH" \
|
||||
UV_PROJECT_ENVIRONMENT=/opt/venv
|
||||
|
||||
# Copy uv binary from pinned image
|
||||
COPY --from=uv /uv /usr/local/bin/uv
|
||||
|
||||
# Install Python dependencies
|
||||
COPY pyproject.toml uv.lock ./
|
||||
RUN pip install --no-cache-dir --upgrade pip uv && \
|
||||
uv sync --frozen --no-dev
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
uv sync --frozen --no-dev --no-install-project
|
||||
|
||||
# =============================================================================
|
||||
# Stage 2: Runtime
|
||||
# =============================================================================
|
||||
FROM python:3.12-slim-bookworm
|
||||
FROM python:3.14-slim-bookworm@sha256:e8a1ad81a9fef9dc56372fb49b50818cac71f5fae238b21d7738d73ccae8f803
|
||||
|
||||
# OCI Labels
|
||||
LABEL org.opencontainers.image.source="https://github.com/jorijn/meshcore-stats"
|
||||
|
||||
@@ -162,7 +162,7 @@ For environments where Docker is not available.
|
||||
|
||||
#### Requirements
|
||||
|
||||
- Python 3.10+
|
||||
- Python 3.11+ (3.14 recommended)
|
||||
- SQLite3
|
||||
- [uv](https://github.com/astral-sh/uv)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ services:
|
||||
# MeshCore Stats - Data collection and rendering
|
||||
# ==========================================================================
|
||||
meshcore-stats:
|
||||
image: ghcr.io/jorijn/meshcore-stats:0.2.10 # x-release-please-version
|
||||
image: ghcr.io/jorijn/meshcore-stats:0.2.13 # x-release-please-version
|
||||
container_name: meshcore-stats
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -78,7 +78,7 @@ services:
|
||||
# nginx - Static site server
|
||||
# ==========================================================================
|
||||
nginx:
|
||||
image: nginx:1.27-alpine
|
||||
image: nginx:1.29-alpine@sha256:c083c3799197cfff91fe5c3c558db3d2eea65ccbbfd419fa42a64d2c39a24027
|
||||
container_name: meshcore-stats-nginx
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "meshcore-stats"
|
||||
version = "0.2.10"
|
||||
version = "0.2.13"
|
||||
description = "MeshCore LoRa mesh network monitoring and statistics"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
"type": "generic",
|
||||
"path": "docker-compose.yml",
|
||||
"glob": false
|
||||
},
|
||||
{
|
||||
"jsonpath": "$.package[?(@.name.value=='meshcore-stats')].version",
|
||||
"path": "uv.lock",
|
||||
"type": "toml"
|
||||
}
|
||||
],
|
||||
"changelog-sections": [
|
||||
|
||||
39
renovate.json
Normal file
39
renovate.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:best-practices"
|
||||
],
|
||||
"rebaseWhen": "behind-base-branch",
|
||||
"automergeType": "pr",
|
||||
"platformAutomerge": true,
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true
|
||||
},
|
||||
"dependencyDashboard": true,
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"pinDigests": true
|
||||
},
|
||||
{
|
||||
"matchManagers": [
|
||||
"docker-compose"
|
||||
],
|
||||
"matchPackageNames": [
|
||||
"ghcr.io/jorijn/meshcore-stats"
|
||||
],
|
||||
"pinDigests": false
|
||||
},
|
||||
{
|
||||
"description": "Auto-merge patch and digest updates once checks pass",
|
||||
"matchUpdateTypes": [
|
||||
"patch",
|
||||
"digest",
|
||||
"lockFileMaintenance"
|
||||
],
|
||||
"automerge": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
"""MeshCore network monitoring library."""
|
||||
|
||||
__version__ = "0.2.10" # x-release-please-version
|
||||
__version__ = "0.2.13" # x-release-please-version
|
||||
|
||||
42
uv.lock
generated
42
uv.lock
generated
@@ -752,7 +752,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "meshcore-stats"
|
||||
version = "0.2.10"
|
||||
version = "0.2.13"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "jinja2" },
|
||||
@@ -1275,28 +1275,28 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.10"
|
||||
version = "0.14.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d4/77/9a7fe084d268f8855d493e5031ea03fa0af8cc05887f638bf1c4e3363eb8/ruff-0.14.11.tar.gz", hash = "sha256:f6dc463bfa5c07a59b1ff2c3b9767373e541346ea105503b4c0369c520a66958", size = 5993417, upload-time = "2026-01-08T19:11:58.322Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/a6/a4c40a5aaa7e331f245d2dc1ac8ece306681f52b636b40ef87c88b9f7afd/ruff-0.14.11-py3-none-linux_armv6l.whl", hash = "sha256:f6ff2d95cbd335841a7217bdfd9c1d2e44eac2c584197ab1385579d55ff8830e", size = 12951208, upload-time = "2026-01-08T19:12:09.218Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/5c/360a35cb7204b328b685d3129c08aca24765ff92b5a7efedbdd6c150d555/ruff-0.14.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f6eb5c1c8033680f4172ea9c8d3706c156223010b8b97b05e82c59bdc774ee6", size = 13330075, upload-time = "2026-01-08T19:12:02.549Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/9e/0cc2f1be7a7d33cae541824cf3f95b4ff40d03557b575912b5b70273c9ec/ruff-0.14.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2fc34cc896f90080fca01259f96c566f74069a04b25b6205d55379d12a6855e", size = 12257809, upload-time = "2026-01-08T19:12:00.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/e5/5faab97c15bb75228d9f74637e775d26ac703cc2b4898564c01ab3637c02/ruff-0.14.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53386375001773ae812b43205d6064dae49ff0968774e6befe16a994fc233caa", size = 12678447, upload-time = "2026-01-08T19:12:13.899Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/33/e9767f60a2bef779fb5855cab0af76c488e0ce90f7bb7b8a45c8a2ba4178/ruff-0.14.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a697737dce1ca97a0a55b5ff0434ee7205943d4874d638fe3ae66166ff46edbe", size = 12758560, upload-time = "2026-01-08T19:11:42.55Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/84/4c6cf627a21462bb5102f7be2a320b084228ff26e105510cd2255ea868e5/ruff-0.14.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6845ca1da8ab81ab1dce755a32ad13f1db72e7fba27c486d5d90d65e04d17b8f", size = 13599296, upload-time = "2026-01-08T19:11:30.371Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/e1/92b5ed7ea66d849f6157e695dc23d5d6d982bd6aa8d077895652c38a7cae/ruff-0.14.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e36ce2fd31b54065ec6f76cb08d60159e1b32bdf08507862e32f47e6dde8bcbf", size = 15048981, upload-time = "2026-01-08T19:12:04.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/df/c1bd30992615ac17c2fb64b8a7376ca22c04a70555b5d05b8f717163cf9f/ruff-0.14.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590bcc0e2097ecf74e62a5c10a6b71f008ad82eb97b0a0079e85defe19fe74d9", size = 14633183, upload-time = "2026-01-08T19:11:40.069Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/e9/fe552902f25013dd28a5428a42347d9ad20c4b534834a325a28305747d64/ruff-0.14.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53fe71125fc158210d57fe4da26e622c9c294022988d08d9347ec1cf782adafe", size = 14050453, upload-time = "2026-01-08T19:11:37.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/93/f36d89fa021543187f98991609ce6e47e24f35f008dfe1af01379d248a41/ruff-0.14.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a35c9da08562f1598ded8470fcfef2afb5cf881996e6c0a502ceb61f4bc9c8a3", size = 13757889, upload-time = "2026-01-08T19:12:07.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/9f/c7fb6ecf554f28709a6a1f2a7f74750d400979e8cd47ed29feeaa1bd4db8/ruff-0.14.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0f3727189a52179393ecf92ec7057c2210203e6af2676f08d92140d3e1ee72c1", size = 13955832, upload-time = "2026-01-08T19:11:55.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/a0/153315310f250f76900a98278cf878c64dfb6d044e184491dd3289796734/ruff-0.14.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:eb09f849bd37147a789b85995ff734a6c4a095bed5fd1608c4f56afc3634cde2", size = 12586522, upload-time = "2026-01-08T19:11:35.356Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/2b/a73a2b6e6d2df1d74bf2b78098be1572191e54bec0e59e29382d13c3adc5/ruff-0.14.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c61782543c1231bf71041461c1f28c64b961d457d0f238ac388e2ab173d7ecb7", size = 12724637, upload-time = "2026-01-08T19:11:47.796Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/41/09100590320394401cd3c48fc718a8ba71c7ddb1ffd07e0ad6576b3a3df2/ruff-0.14.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82ff352ea68fb6766140381748e1f67f83c39860b6446966cff48a315c3e2491", size = 13145837, upload-time = "2026-01-08T19:11:32.87Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/d8/e035db859d1d3edf909381eb8ff3e89a672d6572e9454093538fe6f164b0/ruff-0.14.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:728e56879df4ca5b62a9dde2dd0eb0edda2a55160c0ea28c4025f18c03f86984", size = 13850469, upload-time = "2026-01-08T19:12:11.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/02/bb3ff8b6e6d02ce9e3740f4c17dfbbfb55f34c789c139e9cd91985f356c7/ruff-0.14.11-py3-none-win32.whl", hash = "sha256:337c5dd11f16ee52ae217757d9b82a26400be7efac883e9e852646f1557ed841", size = 12851094, upload-time = "2026-01-08T19:11:45.163Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/f1/90ddc533918d3a2ad628bc3044cdfc094949e6d4b929220c3f0eb8a1c998/ruff-0.14.11-py3-none-win_amd64.whl", hash = "sha256:f981cea63d08456b2c070e64b79cb62f951aa1305282974d4d5216e6e0178ae6", size = 14001379, upload-time = "2026-01-08T19:11:52.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/1c/1dbe51782c0e1e9cfce1d1004752672d2d4629ea46945d19d731ad772b3b/ruff-0.14.11-py3-none-win_arm64.whl", hash = "sha256:649fb6c9edd7f751db276ef42df1f3df41c38d67d199570ae2a7bd6cbc3590f0", size = 12938644, upload-time = "2026-01-08T19:11:50.027Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user