Skip to content

Commit 02bfd88

Browse files
gylimclaude
andcommitted
chore: prepare v0.1.0 for PyPI publishing
Add package metadata (license, authors, classifiers, URLs, keywords), version bump to 0.1.0, __version__ attribute, py.typed marker, .gitignore for build artifacts, CI and publish GitHub Actions workflows, and apply ruff lint/format fixes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5111a70 commit 02bfd88

16 files changed

Lines changed: 229 additions & 71 deletions

File tree

.github/workflows/ci.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: astral-sh/setup-uv@v5
15+
- run: uv python install 3.13
16+
- run: uv sync --group dev
17+
- run: uv run ruff check src/
18+
- run: uv run ruff format --check src/
19+
20+
test:
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v4
24+
- uses: astral-sh/setup-uv@v5
25+
- run: uv python install 3.13
26+
- run: uv sync --group dev
27+
- run: uv run pytest
28+
29+
build:
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
- uses: astral-sh/setup-uv@v5
34+
- run: uv python install 3.13
35+
- run: uv build --no-sources
36+
- run: uv run --isolated --no-project --with dist/*.whl -- python -c "import upbeat; print(upbeat.__version__)"

.github/workflows/publish.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
publish:
9+
runs-on: ubuntu-latest
10+
environment:
11+
name: pypi
12+
url: https://pypi.org/project/upbeat/
13+
permissions:
14+
id-token: write
15+
contents: read
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
20+
- name: Install uv
21+
uses: astral-sh/setup-uv@v5
22+
23+
- name: Install Python 3.13
24+
run: uv python install 3.13
25+
26+
- name: Verify version matches tag
27+
run: |
28+
TAG_VERSION="${GITHUB_REF_NAME#v}"
29+
PKG_VERSION=$(uv version --short)
30+
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
31+
echo "ERROR: Tag version ($TAG_VERSION) ≠ package version ($PKG_VERSION)"
32+
exit 1
33+
fi
34+
35+
- name: Build
36+
run: uv build --no-sources
37+
38+
- name: Smoke test
39+
run: uv run --isolated --no-project --with dist/*.whl -- python -c "import upbeat; print(upbeat.__version__)"
40+
41+
- name: Publish to PyPI
42+
run: uv publish

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
CLAUDE.local.md
2+
DEV_PLAN.md
3+
4+
upbit_reference
5+
.claude/
6+
.venv/
7+
8+
# Build artifacts
9+
dist/
10+
build/
11+
*.egg-info/
12+
*.egg
13+
14+
# Python
15+
__pycache__/
16+
*.py[cod]
17+
18+
# Testing
19+
.pytest_cache/
20+
.ruff_cache/
21+
.coverage
22+
htmlcov/
23+
24+
# OS
25+
.DS_Store

pyproject.toml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
[project]
22
name = "upbeat"
3-
version = "0.0.0"
3+
version = "0.1.0"
44
description = "Modern Python client for the Upbit cryptocurrency exchange API"
55
readme = "README.md"
66
requires-python = ">=3.13"
7+
license = "MIT"
8+
authors = [
9+
{ name = "GeunYoungLim", email = "interruping@naver.com" },
10+
]
11+
keywords = ["upbit", "cryptocurrency", "exchange", "api", "trading", "bitcoin", "async", "websocket"]
12+
classifiers = [
13+
"Development Status :: 3 - Alpha",
14+
"Intended Audience :: Developers",
15+
"License :: OSI Approved :: MIT License",
16+
"Programming Language :: Python :: 3",
17+
"Programming Language :: Python :: 3.13",
18+
"Topic :: Office/Business :: Financial",
19+
"Topic :: Software Development :: Libraries :: Python Modules",
20+
"Typing :: Typed",
21+
"Framework :: Pydantic :: 2",
22+
]
723
dependencies = [
824
"httpx>=0.28.1",
925
"pydantic>=2.12.5",
@@ -16,6 +32,12 @@ pandas = [
1632
"pandas>=2.0",
1733
]
1834

35+
[project.urls]
36+
Homepage = "https://github.com/interruping/upbeat"
37+
Repository = "https://github.com/interruping/upbeat"
38+
Issues = "https://github.com/interruping/upbeat/issues"
39+
Changelog = "https://github.com/interruping/upbeat/releases"
40+
1941
[build-system]
2042
requires = ["uv_build>=0.10.8,<0.11.0"]
2143
build-backend = "uv_build"

src/upbeat/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from importlib.metadata import version as _version
2+
13
from upbeat._auth import Credentials
24
from upbeat._client import AsyncUpbeat, Upbeat
35
from upbeat._config import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, Timeout, UpbeatConfig
@@ -72,7 +74,11 @@
7274
parse_message,
7375
)
7476

77+
__version__ = _version("upbeat")
78+
7579
__all__ = [
80+
# Version
81+
"__version__",
7682
# Client
7783
"AsyncUpbeat",
7884
"Upbeat",

src/upbeat/_client.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,7 @@ def with_options(
274274
new._credentials = self._credentials
275275
new._base_url = self._base_url
276276
new._timeout = self._timeout
277-
new._max_retries = (
278-
max_retries if max_retries is not None else self._max_retries
279-
)
277+
new._max_retries = max_retries if max_retries is not None else self._max_retries
280278
new._auto_throttle = (
281279
auto_throttle if auto_throttle is not None else self._auto_throttle
282280
)

src/upbeat/_convenience.py

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,14 @@
1818
import pandas as pd
1919

2020
_MINUTE_UNITS: dict[str, int] = {
21-
"1m": 1, "3m": 3, "5m": 5, "10m": 10,
22-
"15m": 15, "30m": 30, "60m": 60, "240m": 240,
21+
"1m": 1,
22+
"3m": 3,
23+
"5m": 5,
24+
"10m": 10,
25+
"15m": 15,
26+
"30m": 30,
27+
"60m": 60,
28+
"240m": 240,
2329
}
2430

2531

@@ -43,27 +49,40 @@ def get_candles(
4349
with Upbeat() as client:
4450
if interval in _MINUTE_UNITS:
4551
return client.quotation.get_candles_minutes(
46-
market=market, unit=_MINUTE_UNITS[interval], to=to, count=count, # type: ignore[arg-type]
52+
market=market,
53+
unit=_MINUTE_UNITS[interval],
54+
to=to,
55+
count=count, # type: ignore[arg-type]
4756
)
4857
if interval == "1s":
4958
return client.quotation.get_candles_seconds(
50-
market=market, to=to, count=count,
59+
market=market,
60+
to=to,
61+
count=count,
5162
)
5263
if interval == "1d":
5364
return client.quotation.get_candles_days(
54-
market=market, to=to, count=count,
65+
market=market,
66+
to=to,
67+
count=count,
5568
)
5669
if interval == "1w":
5770
return client.quotation.get_candles_weeks(
58-
market=market, to=to, count=count,
71+
market=market,
72+
to=to,
73+
count=count,
5974
)
6075
if interval == "1M":
6176
return client.quotation.get_candles_months(
62-
market=market, to=to, count=count,
77+
market=market,
78+
to=to,
79+
count=count,
6380
)
6481
if interval == "1y":
6582
return client.quotation.get_candles_years(
66-
market=market, to=to, count=count,
83+
market=market,
84+
to=to,
85+
count=count,
6786
)
6887
raise ValueError(
6988
f"Invalid interval: {interval!r}. "
@@ -81,27 +100,40 @@ def get_candles_df(
81100
with Upbeat() as client:
82101
if interval in _MINUTE_UNITS:
83102
return client.quotation.get_candles_minutes_df(
84-
market=market, unit=_MINUTE_UNITS[interval], to=to, count=count, # type: ignore[arg-type]
103+
market=market,
104+
unit=_MINUTE_UNITS[interval],
105+
to=to,
106+
count=count, # type: ignore[arg-type]
85107
)
86108
if interval == "1s":
87109
return client.quotation.get_candles_seconds_df(
88-
market=market, to=to, count=count,
110+
market=market,
111+
to=to,
112+
count=count,
89113
)
90114
if interval == "1d":
91115
return client.quotation.get_candles_days_df(
92-
market=market, to=to, count=count,
116+
market=market,
117+
to=to,
118+
count=count,
93119
)
94120
if interval == "1w":
95121
return client.quotation.get_candles_weeks_df(
96-
market=market, to=to, count=count,
122+
market=market,
123+
to=to,
124+
count=count,
97125
)
98126
if interval == "1M":
99127
return client.quotation.get_candles_months_df(
100-
market=market, to=to, count=count,
128+
market=market,
129+
to=to,
130+
count=count,
101131
)
102132
if interval == "1y":
103133
return client.quotation.get_candles_years_df(
104-
market=market, to=to, count=count,
134+
market=market,
135+
to=to,
136+
count=count,
105137
)
106138
raise ValueError(
107139
f"Invalid interval: {interval!r}. "
@@ -129,7 +161,11 @@ def get_trades(
129161
) -> list[Trade]:
130162
with Upbeat() as client:
131163
return client.quotation.get_trades(
132-
market, to=to, count=count, cursor=cursor, days_ago=days_ago,
164+
market,
165+
to=to,
166+
count=count,
167+
cursor=cursor,
168+
days_ago=days_ago,
133169
)
134170

135171

src/upbeat/_errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def from_header(cls, header_value: str) -> RemainingRequest:
3030

3131
# ── Base ─────────────────────────────────────────────────────────────────
3232

33+
3334
class UpbeatError(Exception):
3435
message: str
3536

@@ -40,6 +41,7 @@ def __init__(self, message: str) -> None:
4041

4142
# ── API errors ───────────────────────────────────────────────────────────
4243

44+
4345
class APIError(UpbeatError):
4446
pass
4547

@@ -164,6 +166,7 @@ class MinimumOrderError(BadRequestError):
164166

165167
# ── Connection errors ────────────────────────────────────────────────────
166168

169+
167170
class APIConnectionError(APIError):
168171
cause: Exception | None
169172

@@ -178,6 +181,7 @@ class APITimeoutError(APIConnectionError):
178181

179182
# ── WebSocket errors ─────────────────────────────────────────────────────
180183

184+
181185
class WebSocketError(UpbeatError):
182186
pass
183187

0 commit comments

Comments
 (0)