diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 000000000..0a4c1ac2e --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1,21 @@ +module.exports = { + extends: ["@commitlint/config-conventional"], + rules: { + // Configuration Format: [level, applicability, value] + // level: Error level, usually expressed as a number: + // 0 - disable rule + // 1 - Warning (does not prevent commits) + // 2 - Error (will block the commit) + // applicability: the conditions under which the rule applies, commonly used values: + // “always” - always apply the rule + // “never” - never apply the rule + // value: the specific value of the rule, e.g. a maximum length of 100. + // Refs: https://commitlint.js.org/reference/rules-configuration.html + "header-max-length": [2, "always", 100], + "type-enum": [ + 2, + "always", + ["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test", "Release-As"] + ] + } + }; diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index aeb55d24b..00847dbc9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,16 @@ + + + + + + + + + + + + + ## Description diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index dfdd7af34..000000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,6 +0,0 @@ -documentation: -- 'docs/**/*' -- '**/*.md' - -waiting for triage: -- any: ['**/*', '!docs/**/*', '!**/*.md'] diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml deleted file mode 100644 index 78ef55662..000000000 --- a/.github/workflows/labeler.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: "Add label automatically" -on: -- pull_request_target - -jobs: - triage: - permissions: - contents: read - pull-requests: write - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v4 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/lint_title.yml b/.github/workflows/lint_title.yml new file mode 100644 index 000000000..8c9cd8ff8 --- /dev/null +++ b/.github/workflows/lint_title.yml @@ -0,0 +1,35 @@ +name: Lint pull request title + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - edited + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +jobs: + lint-title: + runs-on: ubuntu-latest + steps: + # This step is necessary because the lint title uses the .commitlintrc.js file in the project root directory. + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '16' + + - name: Install commitlint + run: npm install --save-dev @commitlint/{config-conventional,cli} + + - name: Validate PR Title with commitlint + env: + BODY: ${{ github.event.pull_request.title }} + run: | + echo "$BODY" | npx commitlint --config .commitlintrc.js diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index ebaf2cffe..000000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,65 +0,0 @@ -# This workflows will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries - -name: Upload Python Package - -on: - release: - types: [published] - -jobs: - deploy_with_bdist_wheel: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-latest, macos-13, macos-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - exclude: - - os: macos-13 - python-version: "3.11" - - os: macos-13 - python-version: "3.12" - - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - make dev - - name: Build wheel on ${{ matrix.os }} - run: | - make build - - name: Upload to PyPi - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: | - twine check dist/*.whl - twine upload dist/*.whl --verbose - - deploy_with_manylinux: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Build wheel on Linux - uses: RalfG/python-wheels-manylinux-build@v0.7.1-manylinux2014_x86_64 - with: - python-versions: 'cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311 cp312-cp312' - build-requirements: 'numpy cython' - - name: Install dependencies - run: | - python -m pip install twine - - name: Upload to PyPi - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: | - twine check dist/pyqlib-*-manylinux*.whl - twine upload dist/pyqlib-*-manylinux*.whl --verbose diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml deleted file mode 100644 index 7eefec580..000000000 --- a/.github/workflows/release-drafter.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Release Drafter - -on: - push: - # branches to consider in the event; optional, defaults to all - branches: - - main - -permissions: - contents: read - -jobs: - update_release_draft: - permissions: - contents: write - pull-requests: read - runs-on: ubuntu-latest - steps: - # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v5.11.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..415bbffc8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,107 @@ +name: Release + +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + release: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release_please.outputs.release_created }} + + steps: + - name: Release please + id: release_please + uses: googleapis/release-please-action@v4 + with: + token: ${{ secrets.PAT }} + release-type: simple + + deploy_with_manylinux: + needs: release + permissions: + contents: write + pull-requests: read + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + if: needs.release.outputs.release_created == 'true' + with: + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + if: needs.release.outputs.release_created == 'true' + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Build wheel on Linux + if: needs.release.outputs.release_created == 'true' + uses: RalfG/python-wheels-manylinux-build@v0.7.1-manylinux2014_x86_64 + with: + python-versions: 'cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311 cp312-cp312' + build-requirements: 'numpy cython' + + - name: Install dependencies + if: needs.release.outputs.release_created == 'true' + run: | + python -m pip install twine + + - name: Upload to PyPi + if: needs.release.outputs.release_created == 'true' + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TESTPYPI }} + run: | + twine check dist/pyqlib-*-manylinux*.whl + twine upload --repository-url https://test.pypi.org/legacy/ dist/pyqlib-*-manylinux*.whl --verbose + + deploy_with_bdist_wheel: + needs: release + runs-on: ${{ matrix.os }} + strategy: + matrix: + # After testing, the whl files of pyqlib built by macos-14 and macos-15 in python environments of 3.8, 3.9, 3.10, 3.11, 3.12, + # the filenames are exactly duplicated, which will result in the duplicated whl files not being able to be uploaded to pypi, + # so we chose to just keep the latest macos-latest. macos-latest currently points to macos-15. + # Also, macos-13 will stop being supported on 2025-11-14. + # Refs: https://github.blog/changelog/2025-07-11-upcoming-changes-to-macos-hosted-runners-macos-latest-migration-and-xcode-support-policy-updates/ + os: [windows-latest, macos-latest] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + if: needs.release.outputs.release_created == 'true' + with: + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + if: needs.release.outputs.release_created == 'true' + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + if: needs.release.outputs.release_created == 'true' + run: | + make dev + + - name: Build wheel on ${{ matrix.os }} + if: needs.release.outputs.release_created == 'true' + run: | + make build + + - name: Upload to PyPi + if: needs.release.outputs.release_created == 'true' + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TESTPYPI }} + run: | + twine check dist/*.whl + twine upload --repository-url https://test.pypi.org/legacy/ dist/*.whl --verbose diff --git a/.github/workflows/test_qlib_from_pip.yml b/.github/workflows/test_qlib_from_pip.yml index f7abd3d99..4311e3239 100644 --- a/.github/workflows/test_qlib_from_pip.yml +++ b/.github/workflows/test_qlib_from_pip.yml @@ -21,7 +21,9 @@ jobs: steps: - name: Test qlib from pip - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 @@ -32,19 +34,9 @@ jobs: run: | python -m pip install --upgrade pip - # Will cancel this step when the next qlib version is released. The current qlib version is: 0.9.6 - - name: Installing pywinpt for windows - if: ${{ matrix.os == 'windows-latest' }} - run: | - python -m pip install pywinpty --only-binary=:all: - - # # joblib was released on 2025-05-04 with version 1.5.0, in which _backend_args was removed and replaced by _backend_kwargs. - # This change caused the application to fail, so the version of joblib is restricted here. - # This restriction will be removed in the next release. The current qlib version is: 0.9.6 - name: Qlib installation test run: | python -m pip install pyqlib - python -m pip install "joblib<=1.4.2" - name: Install Lightgbm for MacOS if: ${{ matrix.os == 'macos-14' || matrix.os == 'macos-15' }} @@ -53,12 +45,10 @@ jobs: brew install libomp || brew reinstall libomp python -m pip install --no-binary=:all: lightgbm - # When the new version is released it should be changed to: - # python -m qlib.cli.data qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn - name: Downloads dependencies data run: | cd .. - python -m qlib.run.get_data qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn + python -m qlib.cli.data qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn cd qlib - name: Test workflow by config diff --git a/.github/workflows/test_qlib_from_source.yml b/.github/workflows/test_qlib_from_source.yml index aa56183e7..a53c51805 100644 --- a/.github/workflows/test_qlib_from_source.yml +++ b/.github/workflows/test_qlib_from_source.yml @@ -22,7 +22,9 @@ jobs: steps: - name: Test qlib from source - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 diff --git a/.github/workflows/test_qlib_from_source_slow.yml b/.github/workflows/test_qlib_from_source_slow.yml index 147e916a6..4d4f184c8 100644 --- a/.github/workflows/test_qlib_from_source_slow.yml +++ b/.github/workflows/test_qlib_from_source_slow.yml @@ -22,7 +22,9 @@ jobs: steps: - name: Test qlib from source slow - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 @@ -37,8 +39,6 @@ jobs: run: | python scripts/get_data.py qlib_data --name qlib_data_simple --target_dir ~/.qlib/qlib_data/cn_data --interval 1d --region cn - # install.sh file contents from: https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh - # brew_install.sh file contents from: https://raw.githubusercontent.com/Microsoft/qlib/main/.github/brew_install.sh - name: Install Lightgbm for MacOS if: ${{ matrix.os == 'macos-14' || matrix.os == 'macos-15' }} run: | diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/pyproject.toml b/pyproject.toml index 3d65fbcba..f4de900cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools", "cython", "numpy>=1.24.0"] +requires = ["setuptools", "setuptools-scm", "cython", "numpy>=1.24.0"] build-backend = "setuptools.build_meta" [project] @@ -22,6 +22,7 @@ dynamic = ["version"] description = "A Quantitative-research Platform" requires-python = ">=3.8.0" readme = {file = "README.md", content-type = "text/markdown"} +license = { text = "MIT" } dependencies = [ "pyyaml", @@ -49,6 +50,7 @@ dependencies = [ "nbconvert", "pyarrow", "pydantic-settings", + "setuptools-scm", ] [project.optional-dependencies] @@ -108,3 +110,7 @@ license-files = [] [project.scripts] qrun = "qlib.cli.run:run" + +[tool.setuptools_scm] +local_scheme = "no-local-version" +version_scheme = "guess-next-dev" diff --git a/qlib/__init__.py b/qlib/__init__.py index 4957724a9..32d102dca 100644 --- a/qlib/__init__.py +++ b/qlib/__init__.py @@ -2,15 +2,19 @@ # Licensed under the MIT License. from pathlib import Path -__version__ = "0.9.7" +from setuptools_scm import get_version + +__version__ = get_version(root="..", relative_to=__file__) __version__bak = __version__ # This version is backup for QlibConfig.reset_qlib_version -import os -import re -from typing import Union -from ruamel.yaml import YAML import logging +import os import platform +import re import subprocess +from typing import Union + +from ruamel.yaml import YAML + from .log import get_module_logger diff --git a/setup.py b/setup.py index 96b2f59dc..326dac8ed 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,9 @@ -from setuptools import setup, Extension -import numpy import os +import numpy +from setuptools import Extension, setup +from setuptools_scm import get_version + def read(rel_path: str) -> str: here = os.path.abspath(os.path.dirname(__file__)) @@ -9,18 +11,10 @@ def read(rel_path: str) -> str: return fp.read() -def get_version(rel_path: str) -> str: - for line in read(rel_path).splitlines(): - if line.startswith("__version__"): - delim = '"' if '"' in line else "'" - return line.split(delim)[1] - raise RuntimeError("Unable to find version string.") - - NUMPY_INCLUDE = numpy.get_include() -VERSION = get_version("qlib/__init__.py") +VERSION = get_version(root=".", relative_to=__file__) setup( version=VERSION,