flyte 2.0.0b19__py3-none-any.whl → 2.0.0b21__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flyte might be problematic. Click here for more details.

Files changed (37) hide show
  1. flyte/_bin/runtime.py +2 -2
  2. flyte/_code_bundle/_ignore.py +11 -3
  3. flyte/_code_bundle/_packaging.py +9 -5
  4. flyte/_code_bundle/_utils.py +2 -2
  5. flyte/_initialize.py +14 -7
  6. flyte/_interface.py +35 -2
  7. flyte/_internal/controllers/remote/__init__.py +0 -2
  8. flyte/_internal/controllers/remote/_controller.py +3 -3
  9. flyte/_internal/controllers/remote/_core.py +120 -92
  10. flyte/_internal/controllers/remote/_informer.py +15 -6
  11. flyte/_internal/imagebuild/docker_builder.py +58 -8
  12. flyte/_internal/imagebuild/remote_builder.py +9 -26
  13. flyte/_keyring/__init__.py +0 -0
  14. flyte/_keyring/file.py +85 -0
  15. flyte/_logging.py +19 -8
  16. flyte/_utils/coro_management.py +2 -1
  17. flyte/_version.py +3 -3
  18. flyte/config/_config.py +6 -4
  19. flyte/config/_reader.py +19 -4
  20. flyte/errors.py +9 -0
  21. flyte/git/_config.py +2 -0
  22. flyte/io/_dataframe/dataframe.py +3 -2
  23. flyte/io/_dir.py +72 -72
  24. flyte/models.py +6 -2
  25. flyte/remote/_client/auth/_authenticators/device_code.py +3 -4
  26. flyte/remote/_data.py +2 -3
  27. flyte/remote/_run.py +17 -1
  28. flyte/storage/_config.py +5 -1
  29. flyte/types/_type_engine.py +7 -0
  30. {flyte-2.0.0b19.data → flyte-2.0.0b21.data}/scripts/runtime.py +2 -2
  31. {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/METADATA +2 -1
  32. {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/RECORD +37 -35
  33. {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/entry_points.txt +3 -0
  34. {flyte-2.0.0b19.data → flyte-2.0.0b21.data}/scripts/debug.py +0 -0
  35. {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/WHEEL +0 -0
  36. {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/licenses/LICENSE +0 -0
  37. {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@ import tempfile
6
6
  import typing
7
7
  from pathlib import Path
8
8
  from string import Template
9
- from typing import ClassVar, Optional, Protocol, cast
9
+ from typing import ClassVar, List, Optional, Protocol, cast
10
10
 
11
11
  import aiofiles
12
12
  import click
@@ -263,7 +263,37 @@ class DockerIgnoreHandler:
263
263
 
264
264
  class CopyConfigHandler:
265
265
  @staticmethod
266
- async def handle(layer: CopyConfig, context_path: Path, dockerfile: str) -> str:
266
+ def list_dockerignore(root_path: Optional[Path], docker_ignore_file_path: Optional[Path]) -> List[str]:
267
+ patterns: List[str] = []
268
+ dockerignore_path: Optional[Path] = None
269
+ if root_path:
270
+ dockerignore_path = root_path / ".dockerignore"
271
+ # DockerIgnore layer should be first priority
272
+ if docker_ignore_file_path:
273
+ dockerignore_path = docker_ignore_file_path
274
+
275
+ # Return empty list if no .dockerignore file found
276
+ if not dockerignore_path or not dockerignore_path.exists() or not dockerignore_path.is_file():
277
+ logger.info(f".dockerignore file not found at path: {dockerignore_path}")
278
+ return patterns
279
+
280
+ try:
281
+ with open(dockerignore_path, "r", encoding="utf-8") as f:
282
+ for line in f:
283
+ stripped_line = line.strip()
284
+ # Skip empty lines, whitespace-only lines, and comments
285
+ if not stripped_line or stripped_line.startswith("#"):
286
+ continue
287
+ patterns.append(stripped_line)
288
+ except Exception as e:
289
+ logger.error(f"Failed to read .dockerignore file at {dockerignore_path}: {e}")
290
+ return []
291
+ return patterns
292
+
293
+ @staticmethod
294
+ async def handle(
295
+ layer: CopyConfig, context_path: Path, dockerfile: str, docker_ignore_file_path: Optional[Path]
296
+ ) -> str:
267
297
  # Copy the source config file or directory to the context path
268
298
  if layer.src.is_absolute() or ".." in str(layer.src):
269
299
  dst_path = context_path / str(layer.src.absolute()).replace("/", "./_flyte_abs_context/", 1)
@@ -272,18 +302,26 @@ class CopyConfigHandler:
272
302
 
273
303
  dst_path.parent.mkdir(parents=True, exist_ok=True)
274
304
  abs_path = layer.src.absolute()
305
+
275
306
  if layer.src.is_file():
276
307
  # Copy the file
277
308
  shutil.copy(abs_path, dst_path)
278
309
  elif layer.src.is_dir():
279
310
  # Copy the entire directory
280
- shutil.copytree(abs_path, dst_path, dirs_exist_ok=True)
311
+ from flyte._initialize import _get_init_config
312
+
313
+ init_config = _get_init_config()
314
+ root_path = init_config.root_dir if init_config else None
315
+ docker_ignore_patterns = CopyConfigHandler.list_dockerignore(root_path, docker_ignore_file_path)
316
+ shutil.copytree(
317
+ abs_path, dst_path, dirs_exist_ok=True, ignore=shutil.ignore_patterns(*docker_ignore_patterns)
318
+ )
281
319
  else:
282
- raise ValueError(f"Source path is neither file nor directory: {layer.src}")
320
+ logger.error(f"Source path not exists: {layer.src}")
321
+ return dockerfile
283
322
 
284
323
  # Add a copy command to the dockerfile
285
324
  dockerfile += f"\nCOPY {dst_path.relative_to(context_path)} {layer.dst}\n"
286
-
287
325
  return dockerfile
288
326
 
289
327
 
@@ -349,7 +387,9 @@ def _get_secret_mounts_layer(secrets: typing.Tuple[str | Secret, ...] | None) ->
349
387
  return secret_mounts_layer
350
388
 
351
389
 
352
- async def _process_layer(layer: Layer, context_path: Path, dockerfile: str) -> str:
390
+ async def _process_layer(
391
+ layer: Layer, context_path: Path, dockerfile: str, docker_ignore_file_path: Optional[Path]
392
+ ) -> str:
353
393
  match layer:
354
394
  case PythonWheels():
355
395
  # Handle Python wheels
@@ -385,7 +425,7 @@ async def _process_layer(layer: Layer, context_path: Path, dockerfile: str) -> s
385
425
 
386
426
  case CopyConfig():
387
427
  # Handle local files and folders
388
- dockerfile = await CopyConfigHandler.handle(layer, context_path, dockerfile)
428
+ dockerfile = await CopyConfigHandler.handle(layer, context_path, dockerfile, docker_ignore_file_path)
389
429
 
390
430
  case Commands():
391
431
  # Handle commands
@@ -512,6 +552,15 @@ class DockerImageBuilder(ImageBuilder):
512
552
  else:
513
553
  logger.info("Buildx builder already exists.")
514
554
 
555
+ def get_docker_ignore(self, image: Image) -> Optional[Path]:
556
+ """Get the .dockerignore file path from the image layers."""
557
+ # Look for DockerIgnore layer in the image layers
558
+ for layer in image._layers:
559
+ if isinstance(layer, DockerIgnore) and layer.path.strip():
560
+ return Path(layer.path)
561
+
562
+ return None
563
+
515
564
  async def _build_image(self, image: Image, *, push: bool = True, dry_run: bool = False) -> str:
516
565
  """
517
566
  if default image (only base image and locked), raise an error, don't have a dockerfile
@@ -541,8 +590,9 @@ class DockerImageBuilder(ImageBuilder):
541
590
  PYTHON_VERSION=f"{image.python_version[0]}.{image.python_version[1]}",
542
591
  )
543
592
 
593
+ docker_ignore_file_path = self.get_docker_ignore(image)
544
594
  for layer in image._layers:
545
- dockerfile = await _process_layer(layer, tmp_path, dockerfile)
595
+ dockerfile = await _process_layer(layer, tmp_path, dockerfile, docker_ignore_file_path)
546
596
 
547
597
  dockerfile += DOCKER_FILE_BASE_FOOTER.substitute(F_IMG_ID=image.uri)
548
598
 
@@ -10,7 +10,6 @@ from typing import TYPE_CHECKING, Optional, Tuple, cast
10
10
  from uuid import uuid4
11
11
 
12
12
  import aiofiles
13
- import click
14
13
 
15
14
  import flyte
16
15
  import flyte.errors
@@ -42,7 +41,6 @@ if TYPE_CHECKING:
42
41
  from flyte._protos.imagebuilder import definition_pb2 as image_definition_pb2
43
42
 
44
43
  IMAGE_TASK_NAME = "build-image"
45
- OPTIMIZE_TASK_NAME = "optimize-task"
46
44
  IMAGE_TASK_PROJECT = "system"
47
45
  IMAGE_TASK_DOMAIN = "production"
48
46
 
@@ -85,10 +83,10 @@ class RemoteImageChecker(ImageChecker):
85
83
  raise ValueError("remote client should not be None")
86
84
  cls._images_client = image_service_pb2_grpc.ImageServiceStub(cfg.client._channel)
87
85
  resp = await cls._images_client.GetImage(req)
88
- logger.warning(click.style(f"Image {resp.image.fqin} found. Skip building.", fg="blue"))
86
+ logger.warning(f"[blue]Image {resp.image.fqin} found. Skip building.[/blue]")
89
87
  return resp.image.fqin
90
88
  except Exception:
91
- logger.warning(click.style(f"Image {image_name} was not found or has expired.", fg="blue"))
89
+ logger.warning(f"[blue]Image {image_name} was not found or has expired.[/blue]", extra={"highlight": False})
92
90
  return None
93
91
 
94
92
 
@@ -110,37 +108,25 @@ class RemoteImageBuilder(ImageBuilder):
110
108
  domain=IMAGE_TASK_DOMAIN,
111
109
  auto_version="latest",
112
110
  ).override.aio(secrets=_get_build_secrets_from_image(image))
111
+
112
+ logger.warning("[bold blue]🐳 Submitting a new build...[/bold blue]")
113
113
  run = cast(
114
114
  Run,
115
115
  await flyte.with_runcontext(project=IMAGE_TASK_PROJECT, domain=IMAGE_TASK_DOMAIN).run.aio(
116
116
  entity, spec=spec, context=context, target_image=image_name
117
117
  ),
118
118
  )
119
- logger.warning(click.style("🐳 Submitting a new build...", fg="blue", bold=True))
119
+ logger.warning(f" Waiting for build to finish at: [bold cyan link={run.url}]{run.url}[/bold cyan link]")
120
120
 
121
- logger.warning(click.style("⏳ Waiting for build to finish at: " + click.style(run.url, fg="cyan"), bold=True))
122
121
  await run.wait.aio(quiet=True)
123
122
  run_details = await run.details.aio()
124
123
 
125
124
  elapsed = str(datetime.now(timezone.utc) - start).split(".")[0]
126
125
 
127
126
  if run_details.action_details.raw_phase == run_definition_pb2.PHASE_SUCCEEDED:
128
- logger.warning(click.style(f"✅ Build completed in {elapsed}!", bold=True, fg="green"))
129
- try:
130
- entity = remote.Task.get(
131
- name=OPTIMIZE_TASK_NAME,
132
- project=IMAGE_TASK_PROJECT,
133
- domain=IMAGE_TASK_DOMAIN,
134
- auto_version="latest",
135
- )
136
- await flyte.with_runcontext(project=IMAGE_TASK_PROJECT, domain=IMAGE_TASK_DOMAIN).run.aio(
137
- entity, target_image=image_name
138
- )
139
- except Exception as e:
140
- # Ignore the error if optimize is not enabled in the backend.
141
- logger.warning(f"Failed to run optimize task with error: {e}")
127
+ logger.warning(f"[bold green]✅ Build completed in {elapsed}![/bold green]")
142
128
  else:
143
- raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at {click.style(run.url, fg='cyan')}")
129
+ raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at [cyan]{run.url}[/cyan]")
144
130
 
145
131
  outputs = await run_details.outputs()
146
132
  return _get_fully_qualified_image_name(outputs)
@@ -183,11 +169,8 @@ async def _validate_configuration(image: Image) -> Tuple[str, Optional[str]]:
183
169
  context_size = tar_path.stat().st_size
184
170
  if context_size > 5 * 1024 * 1024:
185
171
  logger.warning(
186
- click.style(
187
- f"Context size is {context_size / (1024 * 1024):.2f} MB, which is larger than 5 MB. "
188
- "Upload and build speed will be impacted.",
189
- fg="yellow",
190
- )
172
+ f"[yellow]Context size is {context_size / (1024 * 1024):.2f} MB, which is larger than 5 MB. "
173
+ "Upload and build speed will be impacted.[/yellow]",
191
174
  )
192
175
  _, context_url = await remote.upload_file.aio(context_dst)
193
176
  else:
File without changes
flyte/_keyring/file.py ADDED
@@ -0,0 +1,85 @@
1
+ from base64 import decodebytes, encodebytes
2
+ from configparser import ConfigParser, NoOptionError, NoSectionError
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from keyring.backend import KeyringBackend
7
+ from keyring.errors import PasswordDeleteError
8
+
9
+ _FLYTE_KEYRING_PATH: Path = Path.home() / ".flyte" / "keyring.cfg"
10
+
11
+
12
+ class SimplePlainTextKeyring(KeyringBackend):
13
+ """Simple plain text keyring"""
14
+
15
+ priority = 0.5
16
+
17
+ def get_password(self, service: str, username: str) -> Optional[str]:
18
+ """Get password."""
19
+ if not self.file_path.exists():
20
+ return None
21
+
22
+ config = ConfigParser(interpolation=None)
23
+ config.read(self.file_path, encoding="utf-8")
24
+
25
+ try:
26
+ password_base64 = config.get(service, username).encode("utf-8")
27
+ return decodebytes(password_base64).decode("utf-8")
28
+ except (NoOptionError, NoSectionError):
29
+ return None
30
+
31
+ def delete_password(self, service: str, username: str) -> None:
32
+ """Delete password."""
33
+ if not self.file_path.exists():
34
+ raise PasswordDeleteError("Config file does not exist")
35
+
36
+ config = ConfigParser(interpolation=None)
37
+ config.read(self.file_path, encoding="utf-8")
38
+
39
+ try:
40
+ if not config.remove_option(service, username):
41
+ raise PasswordDeleteError("Password not found")
42
+ except NoSectionError:
43
+ raise PasswordDeleteError("Password not found")
44
+
45
+ with self.file_path.open("w", encoding="utf-8") as config_file:
46
+ config.write(config_file)
47
+
48
+ def set_password(self, service: str, username: str, password: str) -> None:
49
+ """Set password."""
50
+ if not username:
51
+ raise ValueError("Username must be provided")
52
+
53
+ file_path = self._ensure_file_path()
54
+ value = encodebytes(password.encode("utf-8")).decode("utf-8")
55
+
56
+ config = ConfigParser(interpolation=None)
57
+ config.read(file_path, encoding="utf-8")
58
+
59
+ if not config.has_section(service):
60
+ config.add_section(service)
61
+
62
+ config.set(service, username, value)
63
+
64
+ with file_path.open("w", encoding="utf-8") as config_file:
65
+ config.write(config_file)
66
+
67
+ def _ensure_file_path(self):
68
+ self.file_path.parent.mkdir(exist_ok=True, parents=True)
69
+ if not self.file_path.is_file():
70
+ self.file_path.touch(0o600)
71
+ return self.file_path
72
+
73
+ @property
74
+ def file_path(self) -> Path:
75
+ from flyte.config._reader import resolve_config_path
76
+
77
+ config_path = resolve_config_path()
78
+ if config_path and str(config_path.parent) == ".flyte":
79
+ # if the config is in a .flyte directory, use that as the path
80
+ return config_path.parent / "keyring.cfg"
81
+ # otherwise use the default path
82
+ return _FLYTE_KEYRING_PATH
83
+
84
+ def __repr__(self):
85
+ return f"<{self.__class__.__name__}> at {self.file_path}>"
flyte/_logging.py CHANGED
@@ -40,6 +40,21 @@ def log_format_from_env() -> str:
40
40
  return os.environ.get("LOG_FORMAT", "json")
41
41
 
42
42
 
43
+ def _get_console():
44
+ """
45
+ Get the console.
46
+ """
47
+ from rich.console import Console
48
+
49
+ try:
50
+ width = os.get_terminal_size().columns
51
+ except Exception as e:
52
+ logger.debug(f"Failed to get terminal size: {e}")
53
+ width = 160
54
+
55
+ return Console(width=width)
56
+
57
+
43
58
  def get_rich_handler(log_level: int) -> Optional[logging.Handler]:
44
59
  """
45
60
  Upgrades the global loggers to use Rich logging.
@@ -51,23 +66,19 @@ def get_rich_handler(log_level: int) -> Optional[logging.Handler]:
51
66
  return None
52
67
 
53
68
  import click
54
- from rich.console import Console
69
+ from rich.highlighter import NullHighlighter
55
70
  from rich.logging import RichHandler
56
71
 
57
- try:
58
- width = os.get_terminal_size().columns
59
- except Exception as e:
60
- logger.debug(f"Failed to get terminal size: {e}")
61
- width = 160
62
-
63
72
  handler = RichHandler(
64
73
  tracebacks_suppress=[click],
65
74
  rich_tracebacks=True,
66
75
  omit_repeated_times=False,
67
76
  show_path=False,
68
77
  log_time_format="%H:%M:%S.%f",
69
- console=Console(width=width),
78
+ console=_get_console(),
70
79
  level=log_level,
80
+ highlighter=NullHighlighter(),
81
+ markup=True,
71
82
  )
72
83
 
73
84
  formatter = logging.Formatter(fmt="%(filename)s:%(lineno)d - %(message)s")
@@ -11,7 +11,8 @@ async def run_coros(*coros: typing.Coroutine, return_when: str = asyncio.FIRST_C
11
11
  :param return_when:
12
12
  :return:
13
13
  """
14
- tasks: typing.List[asyncio.Task[typing.Never]] = [asyncio.create_task(c) for c in coros]
14
+ # tasks: typing.List[asyncio.Task[typing.Never]] = [asyncio.create_task(c) for c in coros] # Python 3.11+
15
+ tasks: typing.List[asyncio.Task] = [asyncio.create_task(c) for c in coros]
15
16
  done, pending = await asyncio.wait(tasks, return_when=return_when)
16
17
  # TODO we might want to handle asyncio.CancelledError here, for cases when the `action` is cancelled
17
18
  # and we want to propagate it to all tasks. Though the backend will handle it anyway,
flyte/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '2.0.0b19'
32
- __version_tuple__ = version_tuple = (2, 0, 0, 'b19')
31
+ __version__ = version = '2.0.0b21'
32
+ __version_tuple__ = version_tuple = (2, 0, 0, 'b21')
33
33
 
34
- __commit_id__ = commit_id = 'g172a8c6b7'
34
+ __commit_id__ = commit_id = 'g7f82f6ddd'
flyte/config/_config.py CHANGED
@@ -231,10 +231,12 @@ def auto(config_file: typing.Union[str, pathlib.Path, ConfigFile, None] = None)
231
231
  1. If specified, read the config from the provided file path.
232
232
  2. If not specified, the config file is searched in the default locations.
233
233
  a. ./config.yaml if it exists (current working directory)
234
- b. `UCTL_CONFIG` environment variable
235
- c. `FLYTECTL_CONFIG` environment variable
236
- d. ~/.union/config.yaml if it exists
237
- e. ~/.flyte/config.yaml if it exists
234
+ b. ./.flyte/config.yaml if it exists (current working directory)
235
+ c. <git_root>/.flyte/config.yaml if it exists
236
+ d. `UCTL_CONFIG` environment variable
237
+ e. `FLYTECTL_CONFIG` environment variable
238
+ f. ~/.union/config.yaml if it exists
239
+ g. ~/.flyte/config.yaml if it exists
238
240
  3. If any value is not found in the config file, the default value is used.
239
241
  4. For any value there are environment variables that match the config variable names, those will override
240
242
 
flyte/config/_reader.py CHANGED
@@ -135,15 +135,25 @@ class ConfigFile(object):
135
135
  return self._yaml_config
136
136
 
137
137
 
138
+ def _config_path_from_git_root() -> pathlib.Path | None:
139
+ from flyte.git import config_from_root
140
+
141
+ try:
142
+ return config_from_root().source
143
+ except RuntimeError:
144
+ return None
145
+
146
+
138
147
  def resolve_config_path() -> pathlib.Path | None:
139
148
  """
140
149
  Config is read from the following locations in order of precedence:
141
150
  1. ./config.yaml if it exists
142
151
  2. ./.flyte/config.yaml if it exists
143
- 3. `UCTL_CONFIG` environment variable
144
- 4. `FLYTECTL_CONFIG` environment variable
145
- 5. ~/.union/config.yaml if it exists
146
- 6. ~/.flyte/config.yaml if it exists
152
+ 3. <git_root>/.flyte/config.yaml if it exists
153
+ 4. `UCTL_CONFIG` environment variable
154
+ 5. `FLYTECTL_CONFIG` environment variable
155
+ 6. ~/.union/config.yaml if it exists
156
+ 7. ~/.flyte/config.yaml if it exists
147
157
  """
148
158
  current_location_config = Path("config.yaml")
149
159
  if current_location_config.exists():
@@ -155,6 +165,11 @@ def resolve_config_path() -> pathlib.Path | None:
155
165
  return dot_flyte_config
156
166
  logger.debug("No ./.flyte/config.yaml found")
157
167
 
168
+ git_root_config = _config_path_from_git_root()
169
+ if git_root_config:
170
+ return git_root_config
171
+ logger.debug("No .flyte/config.yaml found in git repo root")
172
+
158
173
  uctl_path_from_env = getenv(UCTL_CONFIG_ENV_VAR, None)
159
174
  if uctl_path_from_env:
160
175
  return pathlib.Path(uctl_path_from_env)
flyte/errors.py CHANGED
@@ -223,3 +223,12 @@ class RunAbortedError(RuntimeUserError):
223
223
 
224
224
  def __init__(self, message: str):
225
225
  super().__init__("RunAbortedError", message, "user")
226
+
227
+
228
+ class SlowDownError(RuntimeUserError):
229
+ """
230
+ This error is raised when the user tries to access a resource that does not exist or is invalid.
231
+ """
232
+
233
+ def __init__(self, message: str):
234
+ super().__init__("SlowDownError", message, "user")
flyte/git/_config.py CHANGED
@@ -14,4 +14,6 @@ def config_from_root(path: pathlib.Path | str = ".flyte/config.yaml") -> flyte.c
14
14
  if result.returncode != 0:
15
15
  raise RuntimeError(f"Failed to get git root directory: {result.stderr}")
16
16
  root = pathlib.Path(result.stdout.strip())
17
+ if not (root / path).exists():
18
+ raise RuntimeError(f"Config file {root / path} does not exist")
17
19
  return flyte.config.auto(root / path)
@@ -904,7 +904,8 @@ class DataFrameTransformerEngine(TypeTransformer[DataFrame]):
904
904
  # t1(input_a: DataFrame) # or
905
905
  # t1(input_a: Annotated[DataFrame, my_cols])
906
906
  if issubclass(expected_python_type, DataFrame):
907
- fdf = DataFrame(format=metad.structured_dataset_type.format)
907
+ fdf = DataFrame(format=metad.structured_dataset_type.format, uri=lv.scalar.structured_dataset.uri)
908
+ fdf._already_uploaded = True
908
909
  fdf._literal_sd = lv.scalar.structured_dataset
909
910
  fdf._metadata = metad
910
911
  return fdf
@@ -1012,7 +1013,7 @@ class DataFrameTransformerEngine(TypeTransformer[DataFrame]):
1012
1013
  def guess_python_type(self, literal_type: types_pb2.LiteralType) -> Type[DataFrame]:
1013
1014
  # todo: technically we should return the dataframe type specified in the constructor, but to do that,
1014
1015
  # we'd have to store that, which we don't do today. See possibly #1363
1015
- if literal_type.HasField("dataframe_type"):
1016
+ if literal_type.HasField("structured_dataset_type"):
1016
1017
  return DataFrame
1017
1018
  raise ValueError(f"DataFrameTransformerEngine cannot reverse {literal_type}")
1018
1019