Skip to content

Commit

Permalink
expr: Convert date time accessors to properties
Browse files Browse the repository at this point in the history
  • Loading branch information
aditya-nambiar committed Sep 24, 2024
1 parent 195bdb8 commit c0fd502
Show file tree
Hide file tree
Showing 25 changed files with 109 additions and 47 deletions.
24 changes: 12 additions & 12 deletions docs/examples/api-reference/expressions/dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def test_year():
from fennel.expr import col

# docsnip-highlight next-line
expr = col("x").dt.year()
expr = col("x").dt.year

# year works for any datetime type or optional datetime type
assert expr.typeof(schema={"x": datetime}) == int
Expand All @@ -30,7 +30,7 @@ def test_year():

# also works with timezone aware datetimes
# docsnip-highlight next-line
expr = col("x").dt.year(timezone="US/Eastern")
expr = col("x").dt.year_with_tz(timezone="US/Eastern")
assert expr.eval(df, schema=schema).tolist() == [2023, 2024, 2024]
# /docsnip

Expand All @@ -40,7 +40,7 @@ def test_month():
from fennel.expr import col

# docsnip-highlight next-line
expr = col("x").dt.month()
expr = col("x").dt.month

# month works for any datetime type or optional datetime type
assert expr.typeof(schema={"x": datetime}) == int
Expand All @@ -61,7 +61,7 @@ def test_month():

# also works with timezone aware datetimes
# docsnip-highlight next-line
expr = col("x").dt.month(timezone="US/Eastern")
expr = col("x").dt.month_with_tz(timezone="US/Eastern")
assert expr.eval(df, schema=schema).tolist() == [12, 1, 1]
# /docsnip

Expand Down Expand Up @@ -94,7 +94,7 @@ def test_day():
from fennel.expr import col

# docsnip-highlight next-line
expr = col("x").dt.day()
expr = col("x").dt.day

# day works for any datetime type or optional datetime type
assert expr.typeof(schema={"x": datetime}) == int
Expand All @@ -115,7 +115,7 @@ def test_day():

# also works with timezone aware datetimes
# docsnip-highlight next-line
expr = col("x").dt.day(timezone="US/Eastern")
expr = col("x").dt.day_with_tz(timezone="US/Eastern")
assert expr.eval(df, schema=schema).tolist() == [31, 1, 1]
# /docsnip

Expand All @@ -125,7 +125,7 @@ def test_hour():
from fennel.expr import col

# docsnip-highlight next-line
expr = col("x").dt.hour()
expr = col("x").dt.hour

# hour works for any datetime type or optional datetime type
assert expr.typeof(schema={"x": datetime}) == int
Expand All @@ -146,7 +146,7 @@ def test_hour():

# also works with timezone aware datetimes
# docsnip-highlight next-line
expr = col("x").dt.hour(timezone="US/Eastern")
expr = col("x").dt.hour_with_tz(timezone="US/Eastern")
assert expr.eval(df, schema=schema).tolist() == [19, 5, 15]
# /docsnip

Expand All @@ -156,7 +156,7 @@ def test_minute():
from fennel.expr import col

# docsnip-highlight next-line
expr = col("x").dt.minute()
expr = col("x").dt.minute

# minute works for any datetime type or optional datetime type
assert expr.typeof(schema={"x": datetime}) == int
Expand All @@ -177,7 +177,7 @@ def test_minute():

# also works with timezone aware datetimes
# docsnip-highlight next-line
expr = col("x").dt.minute(timezone="US/Eastern")
expr = col("x").dt.minute_with_tz(timezone="US/Eastern")
assert expr.eval(df, schema=schema).tolist() == [0, 0, 20]
# /docsnip

Expand All @@ -187,7 +187,7 @@ def test_second():
from fennel.expr import col

# docsnip-highlight next-line
expr = col("x").dt.second()
expr = col("x").dt.second

# second works for any datetime type or optional datetime type
assert expr.typeof(schema={"x": datetime}) == int
Expand All @@ -208,7 +208,7 @@ def test_second():

# also works with timezone aware datetimes
# docsnip-highlight next-line
expr = col("x").dt.second(timezone="Asia/Kathmandu")
expr = col("x").dt.second_with_tz(timezone="Asia/Kathmandu")
assert expr.eval(df, schema=schema).tolist() == [1, 2, 3]
# /docsnip

Expand Down
3 changes: 3 additions & 0 deletions fennel/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## [1.5.30] - 2024-09-24
- Add hour, minute, second, millisecond, microsecond, day, week accessors as properties instead of methods.

## [1.5.29] - 2024-09-22
- Enable support for complex literals (e.g. list literals or struct literals)

Expand Down
34 changes: 34 additions & 0 deletions fennel/client_tests/test_featureset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import unittest
from datetime import datetime, timedelta, timezone
from typing import Optional, Dict, List
from fennel.lib import meta

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -1229,3 +1230,36 @@ class IndexFeatures:
message="third_commit",
)
assert response.status_code == requests.codes.OK, response.json()


@mock
def test_query_time_features(client):
@meta(owner="decision-systems@theporter.in")
@featureset
class QueryTimeFeatures:
query_time: datetime
hour_utc: int = F(col("query_time").dt.hour)
bucket_3hour_utc: int = F((col("hour_utc") / lit(3)).floor() + lit(1))
bucket_6hour_utc: int = F((col("hour_utc") / lit(6)).floor() + lit(1))
day_utc: int = F(col("query_time").dt.day)
day_of_week_utc: int
week_of_month_utc: int = F((col("day_utc") / lit(7)).ceil())
week_bucket_utc: int

@extractor(version=1)
@outputs("query_time")
def current_time(cls, ts: pd.Series):
return pd.Series(name="query_time", data=ts)

@extractor(version=8)
@outputs(
"day_of_week_utc",
"week_bucket_utc",
)
def time_feature_extractor(cls, ts: pd.Series) -> pd.DataFrame:
pass

client.commit(
featuresets=[QueryTimeFeatures],
message="first_commit",
)
68 changes: 55 additions & 13 deletions fennel/expr/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -777,26 +777,68 @@ def strftime(self, format: str, timezone: Optional[str] = "UTC") -> _String:
StringNoop(),
)

def year(self, timezone: Optional[str] = "UTC") -> _Number:
return self.parts(TimeUnit.YEAR, timezone)
@property
def hour(self) -> _Number:
return self.parts(TimeUnit.HOUR)

def month(self, timezone: Optional[str] = "UTC") -> _Number:
return self.parts(TimeUnit.MONTH, timezone)
def hour_with_tz(self, timezone: str) -> _Number:
return self.parts(TimeUnit.HOUR, timezone)

def week(self, timezone: Optional[str] = "UTC") -> _Number:
return self.parts(TimeUnit.WEEK, timezone)
@property
def minute(self) -> _Number:
return self.parts(TimeUnit.MINUTE)

def minute_with_tz(self, timezone: str) -> _Number:
return self.parts(TimeUnit.MINUTE, timezone)

@property
def second(self) -> _Number:
return self.parts(TimeUnit.SECOND)

def second_with_tz(self, timezone: str) -> _Number:
return self.parts(TimeUnit.SECOND, timezone)

@property
def millisecond(self) -> _Number:
return self.parts(TimeUnit.MILLISECOND)

def millisecond_with_tz(self, timezone: str) -> _Number:
return self.parts(TimeUnit.MILLISECOND, timezone)

@property
def microsecond(self) -> _Number:
return self.parts(TimeUnit.MICROSECOND)

def microsecond_with_tz(self, timezone: str) -> _Number:
return self.parts(TimeUnit.MICROSECOND, timezone)

@property
def day(self) -> _Number:
return self.parts(TimeUnit.DAY)

def day(self, timezone: Optional[str] = "UTC") -> _Number:
def day_with_tz(self, timezone: str) -> _Number:
return self.parts(TimeUnit.DAY, timezone)

def hour(self, timezone: Optional[str] = "UTC") -> _Number:
return self.parts(TimeUnit.HOUR, timezone)
@property
def week(self) -> _Number:
return self.parts(TimeUnit.WEEK)

def minute(self, timezone: Optional[str] = "UTC") -> _Number:
return self.parts(TimeUnit.MINUTE, timezone)
def week_with_tz(self, timezone: str) -> _Number:
return self.parts(TimeUnit.WEEK, timezone)

def second(self, timezone: Optional[str] = "UTC") -> _Number:
return self.parts(TimeUnit.SECOND, timezone)
@property
def month(self) -> _Number:
return self.parts(TimeUnit.MONTH)

def month_with_tz(self, timezone: str) -> _Number:
return self.parts(TimeUnit.MONTH, timezone)

@property
def year(self) -> _Number:
return self.parts(TimeUnit.YEAR)

def year_with_tz(self, timezone: str) -> _Number:
return self.parts(TimeUnit.YEAR, timezone)


#########################################################
Expand Down
6 changes: 3 additions & 3 deletions fennel/expr/test_expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,7 @@ def test_datetime():
cases = [
# Extract year from a datetime
ExprTestCase(
expr=(col("a").dt.year()),
expr=(col("a").dt.year),
df=pd.DataFrame(
{
"a": [
Expand All @@ -925,7 +925,7 @@ def test_datetime():
),
# Extract month from a datetime
ExprTestCase(
expr=(col("a").dt.month()),
expr=(col("a").dt.month),
df=pd.DataFrame(
{
"a": [
Expand All @@ -944,7 +944,7 @@ def test_datetime():
),
# Extract week from a datetime
ExprTestCase(
expr=(col("a").dt.week()),
expr=(col("a").dt.week),
df=pd.DataFrame(
{
"a": [
Expand Down
1 change: 1 addition & 0 deletions fennel/featuresets/featureset.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def feature(
if isinstance(args[0], Expr):
feature_obj._expr = args[0]
else:
print("Setting ref", args[0])

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (private)
as clear text.
This expression logs
sensitive data (private)
as clear text.
This expression logs
sensitive data (private)
as clear text.
feature_obj.ref = args[0]
else:
feature_obj.ref = None
Expand Down
1 change: 0 additions & 1 deletion fennel/gen/auth_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/connector_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/dataset_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/expectations_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/expr_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/featureset_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/format_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/http_auth_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/index_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/kinesis_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/metadata_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/pycode_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/schema_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/schema_registry_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion fennel/gen/services_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c0fd502

Please sign in to comment.