Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 6 additions & 45 deletions .github/workflows/polars-ci.yml
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#15 에서 CI 임시 비활성화를 위해 조치해둔 수정사항을 원복시켜뒀습니다

Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ name: Polars CI

on:
pull_request:
types: [review_requested, ready_for_review, synchronize]
types: [review_requested, ready_for_review]
paths:
- ".github/workflows/polars-*.yml"
- "Cargo.toml"
- "Makefile"
- "core/**"
- "polars/**"

concurrency:
Expand All @@ -17,82 +18,42 @@ permissions:
contents: read

jobs:
preflight:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
validate: ${{ steps.filter.outputs.validate }}
steps:
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590
id: filter
with:
filters: |
validate:
- "Cargo.toml"
- "Makefile"
- "polars/**"

validate:
needs: preflight
if: ${{ always() }}
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Fail if preflight did not succeed
if: ${{ needs.preflight.result != 'success' }}
run: |
echo "preflight job did not succeed: ${{ needs.preflight.result }}"
exit 1

- name: Skip validation for non-polars changes
if: ${{ needs.preflight.result == 'success' && needs.preflight.outputs.validate != 'true' }}
run: echo "Skipping Polars CI validate job because this PR does not touch Cargo.toml, Makefile, or polars/**."

- if: ${{ needs.preflight.outputs.validate == 'true' }}
uses: actions/checkout@v6
- if: ${{ needs.preflight.outputs.validate == 'true' }}
uses: actions/setup-python@v6
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.10"
- if: ${{ needs.preflight.outputs.validate == 'true' }}
uses: astral-sh/setup-uv@v8.0.0
- if: ${{ needs.preflight.outputs.validate == 'true' }}
uses: dtolnay/rust-toolchain@stable
- uses: astral-sh/setup-uv@v8.0.0
- uses: dtolnay/rust-toolchain@stable

- name: Sync polars dependencies
if: ${{ needs.preflight.outputs.validate == 'true' }}
working-directory: polars
run: uv sync --group dev --no-install-project

- name: Test techr-core
if: ${{ needs.preflight.outputs.validate == 'true' }}
run: cargo test -p techr-core

- name: Build local extension for tests
if: ${{ needs.preflight.outputs.validate == 'true' }}
working-directory: polars
run: uv run maturin develop --uv

- name: Test polars package
if: ${{ needs.preflight.outputs.validate == 'true' }}
working-directory: polars
run: uv run pytest

- name: Build wheel and sdist
if: ${{ needs.preflight.outputs.validate == 'true' }}
working-directory: polars
run: uv run maturin build --release --sdist --out dist

- name: Check artifact contents
if: ${{ needs.preflight.outputs.validate == 'true' }}
run: python polars/scripts/check_artifacts.py polars/dist

- name: Smoke test built wheel
if: ${{ needs.preflight.outputs.validate == 'true' }}
run: |
wheel="$(python - <<'PY'
from pathlib import Path
Expand Down
2 changes: 1 addition & 1 deletion polars/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "polars_techr"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
description = "Polars expression plugins for techr indicators"
license = "MIT"
Expand Down
2 changes: 2 additions & 0 deletions polars/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ result = df.select(
)
```

Null input values are accepted. Indicators preserve row alignment and emit nulls according to the core rolling-window and seed recovery rules.

## Ichimoku Notes

- Standalone Ichimoku rolling-window lines such as `ichimoku_base_line` and `ichimoku_conversion_line` use `period`.
Expand Down
11 changes: 2 additions & 9 deletions polars/src/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,9 @@ struct IchimokuLaggingSpanKwargs {
base_line_period: u32,
}

fn series_to_f64_vec(series: &Series) -> PolarsResult<Vec<f64>> {
fn series_to_f64_vec(series: &Series) -> PolarsResult<Vec<Option<f64>>> {
let casted = series.cast(&DataType::Float64)?;
let values = casted.f64()?.to_vec_null_aware();
if let Some(values) = values.left() {
Ok(values)
} else {
Err(PolarsError::ComputeError(
"null values are not supported yet".into(),
))
}
Ok(casted.f64()?.to_vec())
}

fn option_vec_to_series(values: Vec<Option<f64>>) -> Series {
Expand Down
62 changes: 34 additions & 28 deletions polars/tests/test_indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,41 +284,47 @@ def test_multi_input_integer_columns_are_cast_to_float() -> None:
])


def test_single_input_null_values_raise_compute_error() -> None:
"""Reject null values for single-input indicators with a Polars compute error."""
@pytest.mark.parametrize("lazy", [False, True])
def test_single_input_null_values_follow_core_gap_semantics(lazy: bool) -> None:
"""Accept null values for single-input indicators."""
# given
df = pl.DataFrame({"close": [1.0, None, 3.0]})
df = pl.DataFrame({"close": [1.0, None, 3.0, 4.0]})

# when
result = select_expr(df, ta.sma(pl.col("close"), period=2), "sma", lazy)

# when / then
with pytest.raises(
pl.exceptions.ComputeError,
match="null values are not supported yet",
):
df.select(ta.sma(pl.col("close"), period=2).alias("sma"))
# then
assert_values_close(result.to_list(), [None, None, None, 3.5])


def test_multi_input_null_values_raise_compute_error() -> None:
"""Reject null values for multi-input indicators with a Polars compute error."""
@pytest.mark.parametrize("lazy", [False, True])
def test_multi_input_null_values_follow_core_gap_semantics(lazy: bool) -> None:
"""Accept null values for multi-input indicators."""
# given
df = pl.DataFrame(
{
"high": [11.0, 12.0, None],
"low": [1.0, 2.0, 3.0],
"close": [6.0, 7.0, 8.0],
"high": [5.0, 7.0, None, 10.0, 12.0],
"low": [1.0, 3.0, None, 6.0, 8.0],
"close": [4.0, 5.0, None, 8.0, 11.0],
}
)

# when / then
with pytest.raises(
pl.exceptions.ComputeError,
match="null values are not supported yet",
):
df.select(
ta.stochf_percent_k(
pl.col("high"),
pl.col("low"),
pl.col("close"),
fastk_period=3,
fastd_period=2,
).alias("value")
)
# when
result = select_expr(
df,
ta.stochf_percent_k(
pl.col("high"),
pl.col("low"),
pl.col("close"),
fastk_period=2,
fastd_period=2,
),
"value",
lazy,
)

# then
assert_values_close(
result.to_list(),
[None, 66.66666666666666, None, None, 83.33333333333334],
)