diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e0717a..bd4ca7f 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: Verify test imports + run: | + pip install pytest + python -c " + import pytest + import unittest + print('✅ Test 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.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 @@ -99,21 +143,31 @@ 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/* + - 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/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/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.txt b/requirements.txt new file mode 100644 index 0000000..b9a5daf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,15 @@ +# Dependencies for python-okx +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 ce2838d..57568e0 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,39 @@ +import os import setuptools + +# Get the directory where setup.py is located +HERE = os.path.dirname(os.path.abspath(__file__)) + +# Read version from package import okx -with open("README.md", "r",encoding="utf-8") as fh: + +# Read README +with open(os.path.join(HERE, "README.md"), "r", encoding="utf-8") as fh: long_description = fh.read() + +def parse_requirements(): + """Parse requirements from requirements.txt.""" + requirements = [] + req_path = os.path.join(HERE, "requirements.txt") + + if not os.path.exists(req_path): + return requirements + + with open(req_path, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + # Skip empty lines and comments + if not line or line.startswith("#"): + 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 +43,18 @@ 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(), +)