twrate 0.5.1__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 (48) hide show
  1. twrate-0.5.1/.github/dependabot.yml +11 -0
  2. twrate-0.5.1/.github/workflows/bump-version.yml +38 -0
  3. twrate-0.5.1/.github/workflows/cron.yml +25 -0
  4. twrate-0.5.1/.github/workflows/docker.yml +46 -0
  5. twrate-0.5.1/.github/workflows/python.yml +30 -0
  6. twrate-0.5.1/.github/workflows/release.yml +56 -0
  7. twrate-0.5.1/.gitignore +135 -0
  8. twrate-0.5.1/.pre-commit-config.yaml +24 -0
  9. twrate-0.5.1/AGENTS.md +191 -0
  10. twrate-0.5.1/Dockerfile +28 -0
  11. twrate-0.5.1/LICENSE +21 -0
  12. twrate-0.5.1/Makefile +19 -0
  13. twrate-0.5.1/PKG-INFO +197 -0
  14. twrate-0.5.1/README.md +181 -0
  15. twrate-0.5.1/example.py +16 -0
  16. twrate-0.5.1/pyproject.toml +74 -0
  17. twrate-0.5.1/scripts/db.py +108 -0
  18. twrate-0.5.1/skills/using-twrate-cli/SKILL.md +38 -0
  19. twrate-0.5.1/src/twrate/__init__.py +13 -0
  20. twrate-0.5.1/src/twrate/cli.py +133 -0
  21. twrate-0.5.1/src/twrate/fetcher.py +51 -0
  22. twrate-0.5.1/src/twrate/fetchers/__init__.py +0 -0
  23. twrate-0.5.1/src/twrate/fetchers/_ssl.py +20 -0
  24. twrate-0.5.1/src/twrate/fetchers/bot.py +64 -0
  25. twrate-0.5.1/src/twrate/fetchers/cathay.py +118 -0
  26. twrate-0.5.1/src/twrate/fetchers/cooperative.py +108 -0
  27. twrate-0.5.1/src/twrate/fetchers/dbs.py +79 -0
  28. twrate-0.5.1/src/twrate/fetchers/esun.py +77 -0
  29. twrate-0.5.1/src/twrate/fetchers/firstbank.py +97 -0
  30. twrate-0.5.1/src/twrate/fetchers/fubon.py +72 -0
  31. twrate-0.5.1/src/twrate/fetchers/hsbc.py +64 -0
  32. twrate-0.5.1/src/twrate/fetchers/kgi.py +79 -0
  33. twrate-0.5.1/src/twrate/fetchers/landbank.py +92 -0
  34. twrate-0.5.1/src/twrate/fetchers/line.py +40 -0
  35. twrate-0.5.1/src/twrate/fetchers/megabank.py +76 -0
  36. twrate-0.5.1/src/twrate/fetchers/nextbank.py +66 -0
  37. twrate-0.5.1/src/twrate/fetchers/sinopac.py +132 -0
  38. twrate-0.5.1/src/twrate/fetchers/taichung.py +81 -0
  39. twrate-0.5.1/src/twrate/fetchers/taishin.py +138 -0
  40. twrate-0.5.1/src/twrate/fetchers/yuanta.py +88 -0
  41. twrate-0.5.1/src/twrate/py.typed +0 -0
  42. twrate-0.5.1/src/twrate/types.py +97 -0
  43. twrate-0.5.1/tests/__init__.py +0 -0
  44. twrate-0.5.1/tests/data/dbs.json +120 -0
  45. twrate-0.5.1/tests/data/esunbank.json +1229 -0
  46. twrate-0.5.1/tests/data/sinopac.json +175 -0
  47. twrate-0.5.1/tests/test_fetcher.py +35 -0
  48. twrate-0.5.1/uv.lock +760 -0
@@ -0,0 +1,11 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ - package-ecosystem: "uv"
8
+ directory: "/"
9
+ schedule:
10
+ interval: "weekly"
11
+ open-pull-requests-limit: 0
@@ -0,0 +1,38 @@
1
+ name: Bump Version
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ bump-type:
7
+ description: "Bump type"
8
+ required: true
9
+ default: "patch"
10
+ type: choice
11
+ options:
12
+ - major
13
+ - minor
14
+ - patch
15
+
16
+ permissions:
17
+ contents: write
18
+
19
+ jobs:
20
+ bump-version:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - name: Checkout the code
24
+ uses: actions/checkout@v7
25
+ - uses: astral-sh/setup-uv@v7
26
+ - name: Bump version
27
+ id: bump
28
+ uses: callowayproject/bump-my-version@master
29
+ env:
30
+ BUMPVERSION_TAG: "true"
31
+ with:
32
+ args: ${{ inputs.bump-type }}
33
+ # Use a Personal Access Token (PAT) to ensure the publish workflow is triggered on tag push
34
+ github-token: ${{ secrets.PAT_TOKEN }}
35
+ - name: Check
36
+ if: steps.bump.outputs.bumped == 'true'
37
+ run: |
38
+ echo "Version was bumped from ${{ steps.bump.outputs.previous-version }} to ${{ steps.bump.outputs.current-version }}!"
@@ -0,0 +1,25 @@
1
+ name: Cron
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ # schedule:
6
+ # - cron: "0 0 * * *"
7
+
8
+ jobs:
9
+ cron:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.12"]
14
+ steps:
15
+ - uses: actions/checkout@v7
16
+ - uses: astral-sh/setup-uv@v7
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - name: Run
20
+ run: uv run python scripts/db.py
21
+ env:
22
+ INFLUXDB_URL: ${{ secrets.INFLUXDB_URL }}
23
+ INFLUXDB_TOKEN: ${{ secrets.INFLUXDB_TOKEN }}
24
+ INFLUXDB_ORG: ${{ secrets.INFLUXDB_ORG }}
25
+ INFLUXDB_BUCKET: ${{ secrets.INFLUXDB_BUCKET }}
@@ -0,0 +1,46 @@
1
+ name: Docker
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ # branches:
7
+ # - main
8
+ tags:
9
+ - "v*.*.*"
10
+
11
+ jobs:
12
+ docker:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ # https://docs.docker.com/build/ci/github-actions/manage-tags-labels/
16
+ - name: Docker meta
17
+ id: meta
18
+ uses: docker/metadata-action@v6
19
+ with:
20
+ images: |
21
+ ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}
22
+ flavor: |
23
+ latest=auto
24
+ tags: |
25
+ type=schedule
26
+ type=ref,event=branch
27
+ type=ref,event=pr
28
+ type=semver,pattern={{version}}
29
+ type=semver,pattern={{major}}.{{minor}}
30
+ type=semver,pattern={{major}}
31
+ type=sha
32
+ - name: Set up QEMU
33
+ uses: docker/setup-qemu-action@v4
34
+ - name: Set up Docker Buildx
35
+ uses: docker/setup-buildx-action@v4
36
+ - name: Login to Docker Hub
37
+ uses: docker/login-action@v4
38
+ with:
39
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
40
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
41
+ - name: Build and push
42
+ uses: docker/build-push-action@v7
43
+ with:
44
+ push: ${{ github.event_name != 'pull_request' }}
45
+ tags: ${{ steps.meta.outputs.tags }}
46
+ labels: ${{ steps.meta.outputs.labels }}
@@ -0,0 +1,30 @@
1
+ name: Python
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - main
8
+ pull_request:
9
+ branches:
10
+ - main
11
+
12
+ jobs:
13
+ python:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ matrix:
17
+ python-version: ["3.12"]
18
+ steps:
19
+ - uses: actions/checkout@v7
20
+ - uses: astral-sh/setup-uv@v7
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+ - name: Install dependencies
24
+ run: uv sync
25
+ - name: Lint
26
+ run: uv run ruff check .
27
+ - name: Type check
28
+ run: uv run ty check .
29
+ - name: Test
30
+ run: uv run pytest -v -s --cov=src --cov-report=xml tests
@@ -0,0 +1,56 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v7
16
+ - uses: astral-sh/setup-uv@v7
17
+ with:
18
+ python-version: "3.12"
19
+ - name: Build distributions
20
+ run: uv build
21
+ - name: Upload distributions
22
+ uses: actions/upload-artifact@v7
23
+ with:
24
+ name: dist
25
+ path: dist/*
26
+
27
+ publish:
28
+ runs-on: ubuntu-latest
29
+ needs: build
30
+ steps:
31
+ - uses: actions/download-artifact@v8
32
+ with:
33
+ name: dist
34
+ path: dist
35
+ - uses: astral-sh/setup-uv@v7
36
+ - name: Publish to PyPI
37
+ env:
38
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
39
+ run: uv publish
40
+
41
+ github-release:
42
+ runs-on: ubuntu-latest
43
+ needs: publish
44
+ steps:
45
+ - uses: actions/download-artifact@v8
46
+ with:
47
+ name: dist
48
+ path: dist
49
+ - name: Create GitHub release
50
+ env:
51
+ GH_TOKEN: ${{ github.token }}
52
+ run: |
53
+ gh release create "${{ github.ref_name }}" dist/* \
54
+ --repo "${{ github.repository }}" \
55
+ --title "${{ github.ref_name }}" \
56
+ --generate-notes
@@ -0,0 +1,135 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-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
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ .python-version
86
+
87
+ # pipenv
88
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
90
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
91
+ # install all needed dependencies.
92
+ #Pipfile.lock
93
+
94
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95
+ __pypackages__/
96
+
97
+ # Celery stuff
98
+ celerybeat-schedule
99
+ celerybeat.pid
100
+
101
+ # SageMath parsed files
102
+ *.sage.py
103
+
104
+ # Environments
105
+ .env
106
+ .venv
107
+ env/
108
+ venv/
109
+ ENV/
110
+ env.bak/
111
+ venv.bak/
112
+
113
+ # Spyder project settings
114
+ .spyderproject
115
+ .spyproject
116
+
117
+ # Rope project settings
118
+ .ropeproject
119
+
120
+ # mkdocs documentation
121
+ /site
122
+
123
+ # mypy
124
+ .mypy_cache/
125
+ .dmypy.json
126
+ dmypy.json
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+
131
+ # ruff
132
+ .ruff_cache/
133
+
134
+ # VS Code
135
+ .vscode/
@@ -0,0 +1,24 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v6.0.0
4
+ hooks:
5
+ - id: check-yaml
6
+ - id: end-of-file-fixer
7
+ - id: trailing-whitespace
8
+ - repo: https://github.com/astral-sh/ruff-pre-commit
9
+ rev: v0.14.10
10
+ hooks:
11
+ - id: ruff
12
+ args: [--fix]
13
+ - id: ruff-format
14
+ - repo: https://github.com/astral-sh/uv-pre-commit
15
+ rev: 0.9.18
16
+ hooks:
17
+ - id: uv-lock
18
+ - repo: local
19
+ hooks:
20
+ - id: ty-check
21
+ name: ty-check
22
+ entry: ty check .
23
+ language: python
24
+ pass_filenames: false
twrate-0.5.1/AGENTS.md ADDED
@@ -0,0 +1,191 @@
1
+ # Agents Architecture
2
+
3
+ ## Purpose
4
+
5
+ `twrate` uses a fetcher-based architecture: each async fetcher pulls exchange rates from one Taiwanese bank and returns a shared `Rate` model. Fetchers run concurrently for fast fan-out and easy comparison across sources.
6
+
7
+ ## Workflow
8
+
9
+ Use `uv` for all Python operations:
10
+
11
+ ```bash
12
+ # Fetch rates
13
+ uv run twrate USD
14
+
15
+ # Tests
16
+ uv run pytest
17
+
18
+ # Dependencies
19
+ uv sync
20
+ uv add <package-name>
21
+
22
+ # Run scripts / one-liners (always use uv)
23
+ uv run python script.py
24
+ uv run python -c "import httpx; print(httpx.__version__)"
25
+ ```
26
+
27
+ Why `uv`:
28
+ - Fast dependency management
29
+ - Managed virtualenv
30
+ - Reproducible via `uv.lock`
31
+
32
+ Important: Always use `uv run python` instead of `python` or `python3`.
33
+
34
+ ## Core Models
35
+
36
+ ### Rate (`types.py`)
37
+
38
+ ```python
39
+ class Rate(BaseModel):
40
+ exchange: Exchange # Bank identifier
41
+ source: str # Source currency code
42
+ target: str # Target currency (usually "TWD")
43
+ spot_buy: float | None
44
+ spot_sell: float | None
45
+ cash_buy: float | None
46
+ cash_sell: float | None
47
+ fetched_at: datetime
48
+ ```
49
+
50
+ Computed properties:
51
+ - `spot_mid`, `cash_mid`
52
+ - `spot_spread`, `cash_spread`
53
+ - `symbol`
54
+
55
+ ### Exchange (`types.py`)
56
+
57
+ Supported banks:
58
+ - `DBS`, `SINOPAC`, `BOT`, `ESUN`, `LINE`, `HSBC`, `NEXT`, `KGI`, `CATHAY`
59
+
60
+ ## Fetcher Contract
61
+
62
+ Each fetcher exposes `async def fetch_*_rates() -> list[Rate]` and follows:
63
+ 1. Single bank responsibility
64
+ 2. Consistent return type (`list[Rate]`)
65
+ 3. HTTP errors via `raise_for_status()`
66
+ 4. Pydantic for validation
67
+ 5. Async first (`httpx.AsyncClient`, `asyncio.gather()`)
68
+
69
+ ## Fetchers by Bank
70
+
71
+ - **BOT** (`bot.py`) — Text API `https://rate.bot.com.tw/xrt/fltxt/0/day`; UTF-8-SIG parsing; fixed columns; spot + cash.
72
+ - **DBS** (`dbs.py`) — JSON API `https://www.dbs.com.tw/tw-rates-api/v1/api/twrates/latestForexRates`; nested Pydantic models; cash often missing.
73
+ - **ESUN** (`esun.py`) — JSON POST `https://www.esunbank.com/api/client/ExchangeRate/LastRateInfo`; `/Date(ts)/` parsing; dash as missing.
74
+ - **LINE** (`line.py`) — HTML `https://www.linebank.com.tw/board-rate/exchange-rate`; USD/TWD only; spot only.
75
+ - **SINOPAC** (`sinopac.py`) — Two JSON endpoints; merge spot + cash via `merge_rates()`.
76
+ - **HSBC** (`hsbc.py`) — HTML `https://www.hsbc.com.tw/currency-rates/`; regex currency codes; dash as missing.
77
+ - **NEXT** (`nextbank.py`) — HTML `https://www.nextbank.com.tw/exchange-rates`; strict ISO currency validation.
78
+ - **KGI** (`kgi.py`) — HTML `https://www.kgibank.com.tw/zh-tw/personal/interest-rate/fx`; desktop/mobile tables; numeric-or-dash guard.
79
+ - **CATHAY** (`cathay.py`) — HTML `https://www.cathaybk.com.tw/cathaybk/personal/product/deposit/currency-billboard/`; clean CSS structure; spot + cash.
80
+
81
+ ## Dispatcher
82
+
83
+ `fetch_rates()` in `fetcher.py` routes to the correct fetcher by `Exchange`.
84
+
85
+ ```python
86
+ async def fetch_rates(exchange: Exchange) -> list[Rate]:
87
+ match exchange:
88
+ case Exchange.SINOPAC:
89
+ return await fetch_sinopac_rates()
90
+ case Exchange.ESUN:
91
+ return await fetch_esun_rates()
92
+ case Exchange.LINE:
93
+ return await fetch_line_rates()
94
+ case Exchange.BOT:
95
+ return await fetch_bot_rates()
96
+ case Exchange.DBS:
97
+ return await fetch_dbs_rates()
98
+ case Exchange.HSBC:
99
+ return await fetch_hsbc_rates()
100
+ case Exchange.NEXT:
101
+ return await fetch_nextbank_rates()
102
+ case Exchange.KGI:
103
+ return await fetch_kgi_rates()
104
+ case Exchange.CATHAY:
105
+ return await fetch_cathay_rates()
106
+ case _:
107
+ raise ValueError(f"Unsupported exchange: {exchange}")
108
+ ```
109
+
110
+ ## Adding a Fetcher
111
+
112
+ 1. Create a module in `src/twrate/fetchers/`.
113
+ 2. Add an `Exchange` enum value in `types.py`.
114
+ 3. Implement `async def fetch_*_rates()` returning `list[Rate]`.
115
+ 4. Wire it in `fetcher.py`.
116
+ 5. Add tests in `tests/`.
117
+
118
+ Template:
119
+
120
+ ```python
121
+ import httpx
122
+ from ..types import Exchange, Rate
123
+
124
+ async def fetch_example_rates() -> list[Rate]:
125
+ url = "https://example.bank.com/api/rates"
126
+
127
+ async with httpx.AsyncClient() as client:
128
+ resp = await client.get(url)
129
+ resp.raise_for_status()
130
+
131
+ rates = []
132
+ for item in resp.json():
133
+ rates.append(
134
+ Rate(
135
+ exchange=Exchange.EXAMPLE,
136
+ source=item["currency"],
137
+ target="TWD",
138
+ spot_buy=item["buy"],
139
+ spot_sell=item["sell"],
140
+ cash_buy=item.get("cash_buy"),
141
+ cash_sell=item.get("cash_sell"),
142
+ )
143
+ )
144
+
145
+ return rates
146
+ ```
147
+
148
+ ## Error Handling
149
+
150
+ - HTTP errors bubble up (`raise_for_status()`).
151
+ - Pydantic validation errors bubble up.
152
+ - Missing values use `None` (or parsed dash placeholders).
153
+
154
+ ## Testing
155
+
156
+ - Unit tests: `tests/test_fetcher.py`
157
+ - Sample data: `tests/data/`
158
+ - Integration tests may hit real endpoints.
159
+
160
+ ## Dependencies
161
+
162
+ - `httpx`
163
+ - `pydantic`
164
+ - `beautifulsoup4`
165
+ - `loguru`
166
+
167
+ ## Troubleshooting: JS-rendered Pages (Taishin Case)
168
+
169
+ Summary: Taishin’s rate table is client-side rendered, so `httpx + BeautifulSoup` returns no data. Playwright can render it but adds heavy deps.
170
+
171
+ Verification:
172
+ ```bash
173
+ uv run python -c "\
174
+ import httpx; from bs4 import BeautifulSoup; \
175
+ resp = httpx.get('https://www.taishinbank.com.tw/TSB/personal/deposit/lookup/realtime/'); \
176
+ soup = BeautifulSoup(resp.text, 'html.parser'); \
177
+ print(f'Tables: {len(soup.find_all(\"table\"))}'); \
178
+ print('USD in response:', '美元USD' in resp.text)\
179
+ "
180
+ ```
181
+
182
+ Decision: keep fetcher lightweight and accept no data for JS-only sites unless the source is critical.
183
+
184
+ ## Future Enhancements
185
+
186
+ - Retry with backoff
187
+ - Caching with TTL
188
+ - Rate limiting per bank
189
+ - Health checks
190
+ - Historical rates
191
+ - Optional Playwright for JS-heavy sites
@@ -0,0 +1,28 @@
1
+ # https://docs.astral.sh/uv/guides/integration/docker/#non-editable-installs
2
+ ARG PYTHON_VERSION=3.12
3
+ ARG DEBIAN_VERSION=bookworm
4
+ FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-${DEBIAN_VERSION}-slim AS uv
5
+
6
+ WORKDIR /app
7
+
8
+ ENV UV_COMPILE_BYTECODE=1
9
+ ENV UV_LINK_MODE=copy
10
+
11
+ RUN --mount=type=cache,target=/root/.cache/uv \
12
+ --mount=type=bind,source=uv.lock,target=uv.lock \
13
+ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
14
+ uv sync --frozen --no-install-project --no-dev --no-editable
15
+
16
+ ADD . /app
17
+ RUN --mount=type=cache,target=/root/.cache/uv \
18
+ uv sync --frozen --no-dev --no-editable
19
+
20
+ FROM python:${PYTHON_VERSION}-slim-${DEBIAN_VERSION}
21
+
22
+ WORKDIR /app
23
+
24
+ COPY --from=uv --chown=app:app /app/.venv /app/.venv
25
+
26
+ ENV PATH="/app/.venv/bin:$PATH"
27
+
28
+ ENTRYPOINT ["twrate"]
twrate-0.5.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 なるみ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
twrate-0.5.1/Makefile ADDED
@@ -0,0 +1,19 @@
1
+ format:
2
+ uv run ruff format .
3
+
4
+ lint:
5
+ uv run ruff check --fix .
6
+
7
+ type:
8
+ uv run mypy --install-types --non-interactive .
9
+
10
+ test:
11
+ uv run pytest -v -s --cov=src tests
12
+
13
+ publish:
14
+ uv build -f wheel
15
+ uv publish
16
+
17
+ all: format lint type test
18
+
19
+ .PHONY: lint test publish