|
2 | 2 | import os |
3 | 3 | import random |
4 | 4 | import sys |
5 | | -from datetime import datetime, timedelta |
| 5 | +from datetime import timedelta |
| 6 | +from importlib.abc import Loader |
6 | 7 | from pathlib import Path |
7 | | -from textwrap import dedent |
8 | 8 | from typing import List, NamedTuple, Union |
9 | 9 |
|
10 | 10 | from feast import Entity, FeatureTable |
11 | | -from feast.driver_test_data import create_driver_hourly_stats_df |
12 | 11 | from feast.feature_view import FeatureView |
13 | 12 | from feast.infra.provider import get_provider |
14 | 13 | from feast.names import adjectives, animals |
@@ -64,7 +63,9 @@ def apply_total(repo_config: RepoConfig, repo_path: Path): |
64 | 63 | registry_path=registry_config.path, |
65 | 64 | cache_ttl=timedelta(seconds=registry_config.cache_ttl_seconds), |
66 | 65 | ) |
| 66 | + sys.dont_write_bytecode = True |
67 | 67 | repo = parse_repo(repo_path) |
| 68 | + sys.dont_write_bytecode = False |
68 | 69 |
|
69 | 70 | for entity in repo.entities: |
70 | 71 | registry.apply_entity(entity, project=project) |
@@ -159,69 +160,71 @@ def cli_check_repo(repo_path: Path): |
159 | 160 | sys.exit(1) |
160 | 161 |
|
161 | 162 |
|
162 | | -def init_repo(repo_path: Path, minimal: bool): |
163 | | - repo_config = repo_path / "feature_store.yaml" |
| 163 | +def init_repo(repo_name: str, template: str): |
| 164 | + import os |
| 165 | + from distutils.dir_util import copy_tree |
| 166 | + from pathlib import Path |
164 | 167 |
|
165 | | - if repo_config.exists(): |
166 | | - print("Feature repository is already initialized, nothing to do.") |
167 | | - sys.exit(1) |
| 168 | + from colorama import Fore, Style |
| 169 | + |
| 170 | + repo_path = Path(os.path.join(Path.cwd(), repo_name)) |
| 171 | + repo_path.mkdir(exist_ok=True) |
| 172 | + repo_config_path = repo_path / "feature_store.yaml" |
| 173 | + |
| 174 | + if repo_config_path.exists(): |
| 175 | + new_directory = os.path.relpath(repo_path, os.getcwd()) |
168 | 176 |
|
169 | | - project_id = generate_project_name() |
170 | | - |
171 | | - if minimal: |
172 | | - repo_config.write_text( |
173 | | - dedent( |
174 | | - f""" |
175 | | - project: {project_id} |
176 | | - registry: /path/to/registry.db |
177 | | - provider: local |
178 | | - online_store: |
179 | | - path: /path/to/online_store.db |
180 | | - """ |
181 | | - ) |
182 | | - ) |
183 | 177 | print( |
184 | | - "Generated example feature_store.yaml. Please edit registry and online_store" |
185 | | - "location before running apply" |
| 178 | + f"The directory {Style.BRIGHT + Fore.GREEN}{new_directory}{Style.RESET_ALL} contains an existing feature " |
| 179 | + f"store repository that may cause a conflict" |
186 | 180 | ) |
| 181 | + print() |
| 182 | + sys.exit(1) |
187 | 183 |
|
188 | | - else: |
189 | | - example_py = (Path(__file__).parent / "example_repo.py").read_text() |
| 184 | + # Copy template directory |
| 185 | + template_path = str(Path(Path(__file__).parent / "templates" / template).absolute()) |
| 186 | + if not os.path.exists(template_path): |
| 187 | + raise IOError(f"Could not find template {template}") |
| 188 | + copy_tree(template_path, str(repo_path)) |
| 189 | + |
| 190 | + # Seed the repository |
| 191 | + bootstrap_path = repo_path / "bootstrap.py" |
| 192 | + if os.path.exists(bootstrap_path): |
| 193 | + import importlib.util |
| 194 | + |
| 195 | + spec = importlib.util.spec_from_file_location("bootstrap", str(bootstrap_path)) |
| 196 | + bootstrap = importlib.util.module_from_spec(spec) |
| 197 | + assert isinstance(spec.loader, Loader) |
| 198 | + spec.loader.exec_module(bootstrap) |
| 199 | + bootstrap.bootstrap() # type: ignore |
| 200 | + os.remove(bootstrap_path) |
| 201 | + |
| 202 | + # Template the feature_store.yaml file |
| 203 | + feature_store_yaml_path = repo_path / "feature_store.yaml" |
| 204 | + replace_str_in_file( |
| 205 | + feature_store_yaml_path, "project: my_project", f"project: {repo_name}" |
| 206 | + ) |
190 | 207 |
|
191 | | - data_path = repo_path / "data" |
192 | | - data_path.mkdir(exist_ok=True) |
| 208 | + # Remove the __pycache__ folder if it exists |
| 209 | + import shutil |
193 | 210 |
|
194 | | - end_date = datetime.now().replace(microsecond=0, second=0, minute=0) |
195 | | - start_date = end_date - timedelta(days=15) |
| 211 | + shutil.rmtree(repo_path / "__pycache__", ignore_errors=True) |
196 | 212 |
|
197 | | - driver_entities = [1001, 1002, 1003, 1004, 1005] |
198 | | - driver_df = create_driver_hourly_stats_df(driver_entities, start_date, end_date) |
| 213 | + import click |
199 | 214 |
|
200 | | - driver_stats_path = data_path / "driver_stats.parquet" |
201 | | - driver_df.to_parquet( |
202 | | - path=str(driver_stats_path), allow_truncated_timestamps=True |
203 | | - ) |
| 215 | + click.echo() |
| 216 | + click.echo( |
| 217 | + f"Creating a new Feast repository in {Style.BRIGHT + Fore.GREEN}{repo_path}{Style.RESET_ALL}." |
| 218 | + ) |
| 219 | + click.echo() |
204 | 220 |
|
205 | | - with open(repo_path / "example.py", "wt") as f: |
206 | | - f.write(example_py.replace("%PARQUET_PATH%", str(driver_stats_path))) |
207 | | - |
208 | | - # Generate config |
209 | | - repo_config.write_text( |
210 | | - dedent( |
211 | | - f""" |
212 | | - project: {project_id} |
213 | | - registry: {"data/registry.db"} |
214 | | - provider: local |
215 | | - online_store: |
216 | | - path: {"data/online_store.db"} |
217 | | - """ |
218 | | - ) |
219 | | - ) |
220 | 221 |
|
221 | | - print("Generated feature_store.yaml and example features in example_repo.py") |
222 | | - print( |
223 | | - "Now try running `feast apply` to apply and `feast materialize` to sync data to the online store" |
224 | | - ) |
| 222 | +def replace_str_in_file(file_path, match_str, sub_str): |
| 223 | + with open(file_path, "r") as f: |
| 224 | + contents = f.read() |
| 225 | + contents = contents.replace(match_str, sub_str) |
| 226 | + with open(file_path, "wt") as f: |
| 227 | + f.write(contents) |
225 | 228 |
|
226 | 229 |
|
227 | 230 | def generate_project_name() -> str: |
|
0 commit comments