Skip to content

Commit 07beee2

Browse files
committed
WIP feat: Add feast repo-upgrade for automated repo upgrades
Signed-off-by: Achal Shah <achals@gmail.com>
1 parent 7a2c4cd commit 07beee2

3 files changed

Lines changed: 95 additions & 2 deletions

File tree

sdk/python/feast/cli.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
registry_dump,
4242
teardown,
4343
)
44+
from feast.repo_upgrade import RepoUpgrader
4445
from feast.utils import maybe_local_tz
4546

4647
_logger = logging.getLogger(__name__)
@@ -85,7 +86,7 @@ def cli(ctx: click.Context, chdir: Optional[str], log_level: str):
8586
try:
8687
level = getattr(logging, log_level.upper())
8788
logging.basicConfig(
88-
format="%(asctime)s %(levelname)s:%(message)s",
89+
format="%(asctime)s %(name)s %(levelname)s: %(message)s",
8990
datefmt="%m/%d/%Y %I:%M:%S %p",
9091
level=level,
9192
)
@@ -98,7 +99,6 @@ def cli(ctx: click.Context, chdir: Optional[str], log_level: str):
9899
if "feast" in logger_name:
99100
logger = logging.getLogger(logger_name)
100101
logger.setLevel(level)
101-
102102
except Exception as e:
103103
raise e
104104
pass
@@ -825,5 +825,25 @@ def validate(
825825
exit(1)
826826

827827

828+
@cli.command("repo-upgrade", cls=NoOptionDefaultFormat)
829+
@click.option(
830+
"--write",
831+
is_flag=True,
832+
default=False,
833+
help="Upgrade a feature repo to use the API expected by feast 0.23.",
834+
)
835+
@click.pass_context
836+
def repo_upgrade(ctx: click.Context, write: bool):
837+
"""
838+
Upgrade a feature repo in place.
839+
"""
840+
repo = ctx.obj["CHDIR"]
841+
cli_check_repo(repo)
842+
try:
843+
RepoUpgrader(repo, write).upgrade()
844+
except FeastProviderLoginError as e:
845+
print(str(e))
846+
847+
828848
if __name__ == "__main__":
829849
cli()

sdk/python/feast/repo_upgrade.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import logging
2+
from pathlib import Path
3+
from typing import Dict, List
4+
5+
from bowler import Query
6+
from fissix.pgen2 import token
7+
from fissix.pygram import python_symbols
8+
from fissix.pytree import Node
9+
10+
from feast.repo_operations import get_repo_files
11+
12+
SOURCES = {
13+
"FileSource",
14+
"BigQuerySource",
15+
"RedshiftSource",
16+
"SnowflakeSource",
17+
"KafkaSource",
18+
"KinesisSource",
19+
}
20+
21+
22+
class RepoUpgrader:
23+
def __init__(self, repo_path: str, write: bool):
24+
self.repo_path = repo_path
25+
self.write = write
26+
self.repo_files: List[str] = [
27+
str(p) for p in get_repo_files(Path(self.repo_path))
28+
]
29+
logging.getLogger("RefactoringTool").setLevel(logging.WARNING)
30+
31+
def upgrade(self):
32+
self.remove_date_partition_column()
33+
34+
def remove_date_partition_column(self):
35+
def _remove_date_partition_column(
36+
node: Node, capture: Dict[str, Node], filename: str
37+
) -> None:
38+
self.remove_argument_transform(node, "date_partition_column")
39+
40+
for s in SOURCES:
41+
Query(self.repo_files).select_class(s).is_call().modify(
42+
_remove_date_partition_column
43+
).execute(write=self.write, interactive=False)
44+
45+
@staticmethod
46+
def remove_argument_transform(node: Node, argument: str):
47+
"""
48+
Removes the specified argument.
49+
For example, if the argument is "join_key", this method transforms
50+
driver = Entity(
51+
name="driver_id",
52+
join_key="driver_id",
53+
)
54+
into
55+
driver = Entity(
56+
name="driver_id",
57+
)
58+
This method assumes that node represents a class call that already has an arglist.
59+
"""
60+
if len(node.children) < 2 or len(node.children[1].children) < 2:
61+
raise ValueError(f"Expected a class call with an arglist but got {node}.")
62+
class_args = node.children[1].children[1].children
63+
for i, class_arg in enumerate(class_args):
64+
if (
65+
class_arg.type == python_symbols.argument
66+
and class_arg.children[0].value == argument
67+
):
68+
class_args.pop(i)
69+
if i < len(class_args) and class_args[i].type == token.COMMA:
70+
class_args.pop(i)
71+
if i < len(class_args) and class_args[i].type == token.NEWLINE:
72+
class_args.pop(i)

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"uvicorn[standard]>=0.14.0,<1",
7777
"tensorflow-metadata>=1.0.0,<2.0.0",
7878
"dask>=2021.*,<2022.02.0",
79+
"bowler", # Needed for automatic repo upgrades
7980
]
8081

8182
GCP_REQUIRED = [

0 commit comments

Comments
 (0)