From c35c35545df392efebf7911b3014e1e264bf2339 Mon Sep 17 00:00:00 2001 From: "jason.hsu" Date: Mon, 22 Dec 2025 12:13:50 +0800 Subject: [PATCH 1/4] feat: add dependency file and cicd --- .github/workflows/ci.yml | 72 +++++++++++++++++++++++++++++++++++----- requirements-dev.txt | 19 +++++++++++ requirements.txt | 7 ++++ setup.py | 56 +++++++++++++++++++++++-------- 4 files changed, 131 insertions(+), 23 deletions(-) create mode 100644 requirements-dev.txt create mode 100644 requirements.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e0717a..9570f8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,48 @@ on: - 'releases/*' jobs: + # ============================================ + # DEPENDENCY CHECK JOB + # Ensures all imports are satisfied by requirements.txt + # ============================================ + dependency-check: + name: Dependency Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install only from requirements.txt (clean environment) + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Verify all production imports work + run: | + # This catches missing dependencies in requirements.txt + python -c " + import okx + from okx import Account, Trade, Funding, MarketData, PublicData + from okx import SubAccount, Convert, BlockTrading, CopyTrading + from okx import SpreadTrading, Grid, TradingData, Status + from okx.websocket import WsPublicAsync, WsPrivateAsync + print('✅ All imports successful') + print(f' okx version: {okx.__version__}') + " + + - name: Install dev dependencies and verify test imports + run: | + pip install -r requirements-dev.txt + python -c " + import pytest + import unittest + print('✅ Dev imports successful') + " + # ============================================ # LINT JOB # ============================================ @@ -31,11 +73,11 @@ jobs: - name: Install linting tools run: | python -m pip install --upgrade pip - pip install flake8 + pip install ruff - - name: Run flake8 + - name: Run ruff run: | - flake8 okx/ --max-line-length=120 --ignore=E501,W503,E203 --count --show-source --statistics + ruff check okx/ --ignore=E501 continue-on-error: true # Set to false once codebase is cleaned up # ============================================ @@ -44,10 +86,12 @@ jobs: test: name: Test (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest + needs: [dependency-check] # Only run tests if dependencies are valid strategy: - fail-fast: false + fail-fast: true + max-parallel: 1 matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.12"] steps: - uses: actions/checkout@v4 @@ -61,7 +105,7 @@ jobs: uses: actions/cache@v4 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements*.txt') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.python-version }}- ${{ runner.os }}-pip- @@ -69,15 +113,15 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + pip install -r requirements-dev.txt pip install -e . - pip install pytest pytest-cov pytest-asyncio websockets certifi - name: Run tests run: | python -m pytest test/unit/ -v --cov=okx --cov-report=term-missing --cov-report=xml - name: Upload coverage to Codecov - if: matrix.python-version == '3.11' + if: matrix.python-version == '3.12' uses: codecov/codecov-action@v4 with: file: ./coverage.xml @@ -110,10 +154,20 @@ jobs: - name: Check package run: twine check dist/* + - name: Test install from wheel (clean environment) + run: | + # Create a fresh venv and install the built wheel + python -m venv /tmp/test-install + /tmp/test-install/bin/pip install dist/*.whl + /tmp/test-install/bin/python -c " + import okx + from okx import Account, Trade, Funding + print(f'✅ Package installs correctly: okx {okx.__version__}') + " + - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: dist path: dist/ retention-days: 7 - diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..94c4dd4 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,19 @@ +# Development dependencies +-r requirements.txt + +# Testing +pytest>=7.0.0 +pytest-asyncio>=0.21.0 +pytest-cov>=4.0.0 + +# Linting & Formatting +ruff>=0.1.0 +mypy>=1.0.0 + +# Environment +python-dotenv>=1.0.0 + +# Build tools +build>=1.0.0 +twine>=4.0.0 + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f361bd9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +# Core dependencies for python-okx +httpx[http2]>=0.24.0 +requests>=2.25.0 +websockets>=10.0 +certifi>=2021.0.0 +loguru>=0.7.0 + diff --git a/setup.py b/setup.py index ce2838d..3dfb2d1 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,36 @@ import setuptools +from pathlib import Path + +# Read version from package import okx -with open("README.md", "r",encoding="utf-8") as fh: + +# Read README +with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read() + +def parse_requirements(filename): # type: (str) -> list + """Parse requirements from a requirements file.""" + requirements_path = Path(__file__).parent / filename + requirements = [] + + if not requirements_path.exists(): + return requirements + + with open(requirements_path, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + # Skip empty lines, comments, and -r includes + if not line or line.startswith("#") or line.startswith("-r"): + continue + # Handle inline comments + if "#" in line: + line = line.split("#")[0].strip() + requirements.append(line) + + return requirements + + setuptools.setup( name="python-okx", version=okx.__version__, @@ -12,21 +40,21 @@ long_description=long_description, long_description_content_type="text/markdown", url="https://okx.com/docs-v5/", - packages=setuptools.find_packages(), + packages=setuptools.find_packages(exclude=["test", "test.*", "example"]), + python_requires=">=3.7", classifiers=[ "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], - install_requires=[ - "importlib-metadata", - "httpx[http2]", - "keyring", - "loguru", - "requests", - "Twisted", - "pyOpenSSL", - "websockets", - "certifi" - ] -) \ No newline at end of file + install_requires=parse_requirements("requirements.txt"), + extras_require={ + "dev": parse_requirements("requirements-dev.txt"), + }, +) From a278d23e3ce1c70d7cb75efb6411c0df02a63edb Mon Sep 17 00:00:00 2001 From: "jason.hsu" Date: Mon, 22 Dec 2025 12:26:21 +0800 Subject: [PATCH 2/4] feat: rmv requirements-dev.txt --- .github/workflows/ci.yml | 10 +++--- README.md | 72 ++++++++++++++++++++++++++++++++++++---- requirements-dev.txt | 19 ----------- requirements.txt | 10 +++++- setup.py | 3 -- 5 files changed, 80 insertions(+), 34 deletions(-) delete mode 100644 requirements-dev.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9570f8d..83b0bc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,13 +47,13 @@ jobs: print(f' okx version: {okx.__version__}') " - - name: Install dev dependencies and verify test imports + - name: Verify test imports run: | - pip install -r requirements-dev.txt + pip install pytest python -c " import pytest import unittest - print('✅ Dev imports successful') + print('✅ Test imports successful') " # ============================================ @@ -105,7 +105,7 @@ jobs: uses: actions/cache@v4 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements*.txt') }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.python-version }}- ${{ runner.os }}-pip- @@ -113,7 +113,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements-dev.txt + pip install -r requirements.txt pip install -e . - name: Run tests diff --git a/README.md b/README.md index 19aac31..3e2d5b0 100644 --- a/README.md +++ b/README.md @@ -20,20 +20,80 @@ Make sure you update often and check the [Changelog](https://www.okx.com/docs-v5 ### Quick start #### Prerequisites -`python version:>=3.9` +`python version:>=3.7` -`WebSocketAPI: websockets package advise version 6.0` - -#### Step 1: register an account on OKX and apply for an API key +#### Step 1: Register an account on OKX and apply for an API key - Register for an account: https://www.okx.com/account/register - Apply for an API key: https://www.okx.com/account/users/myApi -#### Step 2: install python-okx +#### Step 2: Install python-okx -```python +```bash pip install python-okx ``` +### API Credentials + +#### Option 1: Hardcoded credentials + +```python +from okx import Account + +account = Account.AccountAPI( + api_key="your-api-key-here", + api_secret_key="your-api-secret-here", + passphrase="your-passphrase-here", + flag="1", # 0 = live trading, 1 = demo trading + debug=False +) +``` + +#### Option 2: Using `.env` file (recommended) + +Create a `.env` file in your project root: + +```bash +OKX_API_KEY=your-api-key-here +OKX_API_SECRET=your-api-secret-here +OKX_PASSPHRASE=your-passphrase-here +OKX_FLAG=1 +``` + +Then load it in your code: + +```python +import os +from dotenv import load_dotenv +from okx import Account + +load_dotenv() + +account = Account.AccountAPI( + api_key=os.getenv('OKX_API_KEY'), + api_secret_key=os.getenv('OKX_API_SECRET'), + passphrase=os.getenv('OKX_PASSPHRASE'), + flag=os.getenv('OKX_FLAG', '1'), + debug=False +) +``` + +### Development Setup + +For contributors or local development: + +```bash +# Clone the repository +git clone https://github.com/okxapi/python-okx.git +cd python-okx + +# Install dependencies +pip install -r requirements.txt +pip install -e . + +# Run tests +pytest test/unit/ -v +``` + #### Step 3: Run examples - Fill in API credentials in the corresponding examples diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 94c4dd4..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,19 +0,0 @@ -# Development dependencies --r requirements.txt - -# Testing -pytest>=7.0.0 -pytest-asyncio>=0.21.0 -pytest-cov>=4.0.0 - -# Linting & Formatting -ruff>=0.1.0 -mypy>=1.0.0 - -# Environment -python-dotenv>=1.0.0 - -# Build tools -build>=1.0.0 -twine>=4.0.0 - diff --git a/requirements.txt b/requirements.txt index f361bd9..2926476 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,15 @@ -# Core dependencies for python-okx +# Core dependencies httpx[http2]>=0.24.0 requests>=2.25.0 websockets>=10.0 certifi>=2021.0.0 loguru>=0.7.0 +python-dotenv>=1.0.0 +# Development & Testing +pytest>=7.0.0 +pytest-asyncio>=0.21.0 +pytest-cov>=4.0.0 +ruff>=0.1.0 +build>=1.0.0 +twine>=4.0.0 diff --git a/setup.py b/setup.py index 3dfb2d1..1eef4bb 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,4 @@ def parse_requirements(filename): # type: (str) -> list "Operating System :: OS Independent", ], install_requires=parse_requirements("requirements.txt"), - extras_require={ - "dev": parse_requirements("requirements-dev.txt"), - }, ) From 299acc477654c706314e189a2320203495f08f6d Mon Sep 17 00:00:00 2001 From: "jason.hsu" Date: Mon, 22 Dec 2025 12:34:08 +0800 Subject: [PATCH 3/4] fix: ci build failure --- .github/workflows/ci.yml | 6 +++--- requirements.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83b0bc3..bd4ca7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,13 +143,13 @@ jobs: with: python-version: "3.11" - - name: Install build tools + - name: Install dependencies run: | python -m pip install --upgrade pip - pip install build twine + pip install -r requirements.txt - name: Build package - run: python -m build + run: python -m build --no-isolation - name: Check package run: twine check dist/* diff --git a/requirements.txt b/requirements.txt index 2926476..b9a5daf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -# Core dependencies +# Dependencies for python-okx httpx[http2]>=0.24.0 requests>=2.25.0 websockets>=10.0 From 2b2936a6ac37ac5e0ea6b8c1a1aaa026d312be99 Mon Sep 17 00:00:00 2001 From: "jason.hsu" Date: Mon, 22 Dec 2025 12:38:52 +0800 Subject: [PATCH 4/4] fix: ci build failure --- MANIFEST.in | 3 +++ setup.py | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..23682e9 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include requirements.txt +include README.md + diff --git a/setup.py b/setup.py index 1eef4bb..57568e0 100644 --- a/setup.py +++ b/setup.py @@ -1,27 +1,30 @@ +import os import setuptools -from pathlib import Path + +# Get the directory where setup.py is located +HERE = os.path.dirname(os.path.abspath(__file__)) # Read version from package import okx # Read README -with open("README.md", "r", encoding="utf-8") as fh: +with open(os.path.join(HERE, "README.md"), "r", encoding="utf-8") as fh: long_description = fh.read() -def parse_requirements(filename): # type: (str) -> list - """Parse requirements from a requirements file.""" - requirements_path = Path(__file__).parent / filename +def parse_requirements(): + """Parse requirements from requirements.txt.""" requirements = [] + req_path = os.path.join(HERE, "requirements.txt") - if not requirements_path.exists(): + if not os.path.exists(req_path): return requirements - with open(requirements_path, "r", encoding="utf-8") as f: + with open(req_path, "r", encoding="utf-8") as f: for line in f: line = line.strip() - # Skip empty lines, comments, and -r includes - if not line or line.startswith("#") or line.startswith("-r"): + # Skip empty lines and comments + if not line or line.startswith("#"): continue # Handle inline comments if "#" in line: @@ -53,5 +56,5 @@ def parse_requirements(filename): # type: (str) -> list "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], - install_requires=parse_requirements("requirements.txt"), + install_requires=parse_requirements(), )