diff --git a/.flake8 b/.flake8 deleted file mode 100644 index a3ea75b..0000000 --- a/.flake8 +++ /dev/null @@ -1,8 +0,0 @@ -[flake8] -max-line-length = 80 - -select = C,E,F,W,B,B950 -ignore = - W291, W293, W391, # whitespace - E501, # allow some beyond lines - W503, # line break before binary operator diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index abcf20e..d975a57 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -8,29 +8,58 @@ on: types: [created] jobs: - deploy: + ci-check: runs-on: ubuntu-latest - + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.12" steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + + - name: Install uv and set the python version + uses: astral-sh/setup-uv@v5 with: - python-version: "3.8" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Install Package + version: "0.7.13" + python-version: ${{ matrix.python-version }} + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Install the project + run: uv sync --all-extras --dev + + - name: Lint with ruff run: | - pip install -e ".[dev, ruamel]" + uv run ruff check + - name: Test with pytest run: | - pytest - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + uv run pytest + + + publish-pypi: + name: "Publish to PyPI" + needs: + - "ci-check" + runs-on: ubuntu-latest + environment: "publish-pypi" + permissions: + id-token: write # required for trusted publishing + steps: + - uses: actions/checkout@v4 + + - name: Install uv and set the python version + uses: astral-sh/setup-uv@v5 + with: + version: "0.7.13" + + - name: Install the project + run: | + uv sync --all-extras + + - name: Publish the project to PyPI run: | - python setup.py sdist bdist_wheel - twine upload dist/* + uv publish + # NOTE: This works because of trusted publishing diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index 8d676c5..84d269b 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -10,32 +10,33 @@ on: branches: [main] jobs: - build: + ci-check: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: [3.8, 3.9] - deps: - - dev + python-version: + - "3.10" + - "3.12" steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + + - name: Install uv and set the python version + uses: astral-sh/setup-uv@v5 with: + version: "0.7.13" python-version: ${{ matrix.python-version }} - - name: Install Package with dependencies ${{ matrix.deps }} - run: | - pip install -e ".[${{ matrix.deps }}]" - - name: Lint with flake8 + enable-cache: true + cache-dependency-glob: "uv.lock" + + + - name: Install the project + run: uv sync --all-extras --dev + + - name: Lint with ruff run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + uv run ruff check + - name: Test with pytest run: | - pytest - # - name: Test with mypy - # run: | - # mypy -m tsdata + uv run pytest diff --git a/.gitignore b/.gitignore index 650ba23..64eba64 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,10 @@ __pycache__ **.ipynb_checkpoints -# setuptools files +# Build files src/*.egg-info /build /dist + +# uv-specific files +/uv.lock \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..c8cfe39 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 47c98e2..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -global-include *.csv -global-include *.parquet -global-include *.nc \ No newline at end of file diff --git a/README.md b/README.md index 982b45d..db32ce1 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,6 @@ The `tsdata` package itself contains a way of loading these into Pandas. **NOTE**: The PyPI name is `py-tsdata` due to confict with a removed package. -**NOTE**: This package currently only includes the bare requirements. -However, I will keep adding data and functionality over time. - ## Installing You can install this as a regular Python package via pip: diff --git a/environment.yml b/environment.yml deleted file mode 100644 index c0e6d4f..0000000 --- a/environment.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: tsdata -channels: - - conda-forge -dependencies: - - python=3.9 - - pip=21.2 - - pip: - - --editable .[dev] diff --git a/pyproject.toml b/pyproject.toml index 9787c3b..b119e01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,43 @@ +[project] +name = "py-tsdata" +version = "0.3.4" +description = "Time Series Datasets" +keywords = ["time series", "data", "pandas"] +readme = "README.md" +license = { file = "LICENSE" } +authors = [{ name = "NowanIlfideme", email = "git@nowan.dev" }] +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Topic :: Software Development", + "Typing :: Typed", +] + +# Requirements +requires-python = ">=3.10" +dependencies = ["pandas>=2.0.0"] + +[project.optional-dependencies] + +[dependency-groups] +dev = [ + "mypy>=1.16.0", + "pandas-stubs>=2.2.3.250527", + "pytest>=8.4.0", + "ruff>=0.11.13", +] + [build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta" +requires = ["uv_build>=0.7.13,<0.8"] +build-backend = "uv_build" + +[tool.uv] +package = true +default-groups = ["dev"] + +[tool.uv.build-backend] +module-name = "tsdata" +module-root = "src" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 67a7cbb..0000000 --- a/setup.cfg +++ /dev/null @@ -1,56 +0,0 @@ -[bumpversion] -current_version = 0.3.4 -commit = False -tag = False - -[metadata] -name = py-tsdata -version = attr: tsdata.__version__ -author = Anatoly Makarevich -author_email = git@nowan.dev -description = Time Series Datasets -long_description = file: README.md -long_description_content_type = text/markdown -url = https://github.com/NowanIlfideme/tsdata -project_urls = - Bug Tracker = https://github.com/NowanIlfideme/tsdata/issues -license_files = LICENSE -platform = any -classifiers = - Programming Language :: Python :: 3 - License :: OSI Approved :: MIT License - Operating System :: OS Independent - -[options] -zip_safe = false -include_package_data = True -package_dir = - = src -packages = find: -python_requires = >=3.7 -install_requires = - pandas -tests_require = - pytest - -[options.packages.find] -where = src - -[options.package_data] -* = *.csv, *.parquet, *.nc - -[options.extras_require] -dev = - flake8 - black - isort - bump2version - pytest - mypy - -[bdist_wheel] -universal = true - -[bumpversion:file:setup.cfg] - -[bumpversion:file:src/tsdata/__init__.py] diff --git a/setup.py b/setup.py deleted file mode 100644 index d8fd8f6..0000000 --- a/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Minimal setup.py shim for setup.""" - -from setuptools import setup - -if __name__ == "__main__": - setup() diff --git a/src/tsdata/__init__.py b/src/tsdata/__init__.py index f7a40db..d174b2c 100644 --- a/src/tsdata/__init__.py +++ b/src/tsdata/__init__.py @@ -1,9 +1,5 @@ -"""Time Series Data package. +"""Time Series Data package.""" -The version is managed by bump2version. -""" +__all__ = ["__version__"] -from pathlib import Path - -__version__ = "0.3.4" -__root__ = Path(__file__).parent +from ._version import __version__ diff --git a/src/tsdata/_version.py b/src/tsdata/_version.py new file mode 100644 index 0000000..731e153 --- /dev/null +++ b/src/tsdata/_version.py @@ -0,0 +1,5 @@ +"""Version package.""" + +from importlib.metadata import version as _getversion + +__version__ = _getversion("py-tsdata") diff --git a/src/tsdata/py.typed b/src/tsdata/py.typed new file mode 100644 index 0000000..5540977 --- /dev/null +++ b/src/tsdata/py.typed @@ -0,0 +1,2 @@ +# This package includes type annotations +# See https://peps.python.org/pep-0561/ \ No newline at end of file diff --git a/src/tsdata/raw/fpp3/__init__.py b/src/tsdata/raw/fpp3/__init__.py index 25e8ccb..7121d75 100644 --- a/src/tsdata/raw/fpp3/__init__.py +++ b/src/tsdata/raw/fpp3/__init__.py @@ -4,14 +4,13 @@ """ from pathlib import Path -from typing import Dict import pandas as pd from ..registry import Loader, register_loader __all__ = [] -_funcs: Dict[str, Loader] = {} +_funcs: dict[str, Loader] = {} for _filename in Path(__file__).parent.glob("*.csv"): _name = _filename.stem diff --git a/src/tsdata/raw/registry.py b/src/tsdata/raw/registry.py index c6c1c74..2ae46b3 100644 --- a/src/tsdata/raw/registry.py +++ b/src/tsdata/raw/registry.py @@ -1,6 +1,8 @@ """Data registry. This is very much a work-in-progress.""" -from typing import Callable, Dict, List, Union +from collections.abc import Callable +from functools import partial +from typing import TypeVar, overload import pandas as pd @@ -8,10 +10,10 @@ Loader = Callable[[], pd.DataFrame] -__DATA_LOADERS__: Dict[str, Callable] = {} +__DATA_LOADERS__: dict[str, Callable] = {} -def available_data() -> List[str]: +def available_data() -> list[str]: """Returns all available datasets.""" global __DATA_LOADERS__ return list(sorted(__DATA_LOADERS__.keys())) @@ -32,7 +34,20 @@ def load_data(name: str) -> pd.DataFrame: return loader() -def register_loader(name_or_func: Union[str, Loader]) -> Callable[[Loader], Loader]: +TLoader = TypeVar("TLoader", bound=Loader) + + +@overload +def register_loader(name_or_func: str) -> Callable[[TLoader], TLoader]: ... + + +@overload +def register_loader(name_or_func: TLoader, *, name: str | None = None) -> TLoader: ... + + +def register_loader( + name_or_func: str | Loader, *, name: str | None = None +) -> Loader | Callable[[Loader], Loader]: """Decorator factory to register a data loader for a particular named dataset. Usage @@ -55,20 +70,17 @@ def my_dataset() -> pd.DataFrame: Both cases will register as: `df = load_data("my_dataset")` """ + global __DATA_LOADERS__ if isinstance(name_or_func, str): name = name_or_func - is_func = False - elif callable(name_or_func): - # FIXME: Check signature for a legal loader - name = name_or_func.__qualname__ - is_func = True - else: - raise TypeError(f"Expected str or callable, got {name_or_func!r}.") + return partial(register_loader, name=name) # type: ignore - def register(func: Loader) -> Loader: - """Registers a function as a loader for a particular dataset.""" - global __DATA_LOADERS__ + if callable(name_or_func): + # FIXME: Check signature for a legal loader + if name is None: + name = name_or_func.__qualname__ + func = name_or_func if name in __DATA_LOADERS__.keys(): raise ValueError(f"Attempted to redefine loader for {name!r}.") @@ -77,6 +89,4 @@ def register(func: Loader) -> Loader: return func - if is_func: - return register(name_or_func) - return register + raise TypeError(f"Expected str or callable, got {name_or_func!r}.")