acryl-datahub 1.2.0__py3-none-any.whl → 1.2.0.1__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 acryl-datahub might be problematic. Click here for more details.

@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import abc
4
4
  from typing import (
5
+ TYPE_CHECKING,
6
+ Annotated,
5
7
  Any,
6
8
  ClassVar,
7
9
  Iterator,
@@ -15,7 +17,10 @@ from typing import (
15
17
  import pydantic
16
18
 
17
19
  from datahub.configuration.common import ConfigModel
18
- from datahub.configuration.pydantic_migration_helpers import PYDANTIC_VERSION_2
20
+ from datahub.configuration.pydantic_migration_helpers import (
21
+ PYDANTIC_SUPPORTS_CALLABLE_DISCRIMINATOR,
22
+ PYDANTIC_VERSION_2,
23
+ )
19
24
  from datahub.ingestion.graph.client import flexible_entity_type_to_graphql
20
25
  from datahub.ingestion.graph.filters import (
21
26
  FilterOperator,
@@ -42,12 +47,29 @@ class _BaseFilter(ConfigModel):
42
47
  populate_by_name = True
43
48
 
44
49
  @abc.abstractmethod
45
- def compile(self) -> _OrFilters:
46
- pass
50
+ def compile(self) -> _OrFilters: ...
47
51
 
48
52
  def dfs(self) -> Iterator[_BaseFilter]:
49
53
  yield self
50
54
 
55
+ @classmethod
56
+ def _field_discriminator(cls) -> str:
57
+ if cls is _BaseFilter:
58
+ raise ValueError("Cannot get discriminator for _BaseFilter")
59
+ if PYDANTIC_VERSION_2:
60
+ fields: dict = cls.model_fields # type: ignore
61
+ else:
62
+ fields = cls.__fields__ # type: ignore
63
+
64
+ # Assumes that there's only one field name per filter.
65
+ # If that's not the case, this method should be overridden.
66
+ if len(fields.keys()) != 1:
67
+ raise ValueError(
68
+ f"Found multiple fields that could be the discriminator for this filter: {list(fields.keys())}"
69
+ )
70
+ name, field = next(iter(fields.items()))
71
+ return field.alias or name # type: ignore
72
+
51
73
 
52
74
  class _EntityTypeFilter(_BaseFilter):
53
75
  """Filter for specific entity types.
@@ -74,15 +96,19 @@ class _EntityTypeFilter(_BaseFilter):
74
96
 
75
97
 
76
98
  class _EntitySubtypeFilter(_BaseFilter):
77
- entity_subtype: str = pydantic.Field(
99
+ entity_subtype: List[str] = pydantic.Field(
78
100
  description="The entity subtype to filter on. Can be 'Table', 'View', 'Source', etc. depending on the native platform's concepts.",
79
101
  )
80
102
 
103
+ @pydantic.validator("entity_subtype", pre=True)
104
+ def validate_entity_subtype(cls, v: str) -> List[str]:
105
+ return [v] if not isinstance(v, list) else v
106
+
81
107
  def _build_rule(self) -> SearchFilterRule:
82
108
  return SearchFilterRule(
83
109
  field="typeNames",
84
110
  condition="EQUAL",
85
- values=[self.entity_subtype],
111
+ values=self.entity_subtype,
86
112
  )
87
113
 
88
114
  def compile(self) -> _OrFilters:
@@ -196,6 +222,10 @@ class _CustomCondition(_BaseFilter):
196
222
  )
197
223
  return [{"and": [rule]}]
198
224
 
225
+ @classmethod
226
+ def _field_discriminator(cls) -> str:
227
+ return "_custom"
228
+
199
229
 
200
230
  class _And(_BaseFilter):
201
231
  """Represents an AND conjunction of filters."""
@@ -302,31 +332,69 @@ class _Not(_BaseFilter):
302
332
  yield from self.not_.dfs()
303
333
 
304
334
 
305
- # TODO: With pydantic 2, we can use a RootModel with a
306
- # discriminated union to make the error messages more informative.
307
- Filter = Union[
308
- _And,
309
- _Or,
310
- _Not,
311
- _EntityTypeFilter,
312
- _EntitySubtypeFilter,
313
- _StatusFilter,
314
- _PlatformFilter,
315
- _DomainFilter,
316
- _EnvFilter,
317
- _CustomCondition,
318
- ]
319
-
320
-
321
- # Required to resolve forward references to "Filter"
322
- if PYDANTIC_VERSION_2:
323
- _And.model_rebuild() # type: ignore
324
- _Or.model_rebuild() # type: ignore
325
- _Not.model_rebuild() # type: ignore
326
- else:
335
+ def _filter_discriminator(v: Any) -> Optional[str]:
336
+ if isinstance(v, _BaseFilter):
337
+ return v._field_discriminator()
338
+
339
+ if not isinstance(v, dict):
340
+ return None
341
+
342
+ keys = list(v.keys())
343
+ if len(keys) == 1:
344
+ return keys[0]
345
+ elif set(keys).issuperset({"field", "condition"}):
346
+ return _CustomCondition._field_discriminator()
347
+
348
+ return None
349
+
350
+
351
+ if TYPE_CHECKING or not PYDANTIC_SUPPORTS_CALLABLE_DISCRIMINATOR:
352
+ # The `not TYPE_CHECKING` bit is required to make the linter happy,
353
+ # since we currently only run mypy with pydantic v1.
354
+ Filter = Union[
355
+ _And,
356
+ _Or,
357
+ _Not,
358
+ _EntityTypeFilter,
359
+ _EntitySubtypeFilter,
360
+ _StatusFilter,
361
+ _PlatformFilter,
362
+ _DomainFilter,
363
+ _EnvFilter,
364
+ _CustomCondition,
365
+ ]
366
+
327
367
  _And.update_forward_refs()
328
368
  _Or.update_forward_refs()
329
369
  _Not.update_forward_refs()
370
+ else:
371
+ from pydantic import Discriminator, Tag
372
+
373
+ # TODO: Once we're fully on pydantic 2, we can use a RootModel here.
374
+ # That way we'd be able to attach methods to the Filter type.
375
+ # e.g. replace load_filters(...) with Filter.load(...)
376
+ Filter = Annotated[
377
+ Union[
378
+ Annotated[_And, Tag(_And._field_discriminator())],
379
+ Annotated[_Or, Tag(_Or._field_discriminator())],
380
+ Annotated[_Not, Tag(_Not._field_discriminator())],
381
+ Annotated[_EntityTypeFilter, Tag(_EntityTypeFilter._field_discriminator())],
382
+ Annotated[
383
+ _EntitySubtypeFilter, Tag(_EntitySubtypeFilter._field_discriminator())
384
+ ],
385
+ Annotated[_StatusFilter, Tag(_StatusFilter._field_discriminator())],
386
+ Annotated[_PlatformFilter, Tag(_PlatformFilter._field_discriminator())],
387
+ Annotated[_DomainFilter, Tag(_DomainFilter._field_discriminator())],
388
+ Annotated[_EnvFilter, Tag(_EnvFilter._field_discriminator())],
389
+ Annotated[_CustomCondition, Tag(_CustomCondition._field_discriminator())],
390
+ ],
391
+ Discriminator(_filter_discriminator),
392
+ ]
393
+
394
+ # Required to resolve forward references to "Filter"
395
+ _And.model_rebuild() # type: ignore
396
+ _Or.model_rebuild() # type: ignore
397
+ _Not.model_rebuild() # type: ignore
330
398
 
331
399
 
332
400
  def load_filters(obj: Any) -> Filter:
@@ -7,7 +7,7 @@ from typing import Any, Callable, Optional, Tuple, TypeVar
7
7
 
8
8
  import click
9
9
  import humanfriendly
10
- from packaging.version import Version
10
+ from packaging.version import InvalidVersion, Version
11
11
  from pydantic import BaseModel
12
12
 
13
13
  from datahub._version import __version__
@@ -28,6 +28,18 @@ class VersionStats(BaseModel, arbitrary_types_allowed=True):
28
28
  release_date: Optional[datetime] = None
29
29
 
30
30
 
31
+ def _safe_version_stats(version_string: str) -> Optional[VersionStats]:
32
+ """
33
+ Safely create a VersionStats object from a version string.
34
+ Returns None if the version string is invalid.
35
+ """
36
+ try:
37
+ return VersionStats(version=Version(version_string), release_date=None)
38
+ except InvalidVersion:
39
+ log.warning(f"Invalid version format received: {version_string!r}")
40
+ return None
41
+
42
+
31
43
  class ServerVersionStats(BaseModel):
32
44
  current: VersionStats
33
45
  latest: Optional[VersionStats] = None
@@ -233,10 +245,7 @@ async def _retrieve_version_stats(
233
245
  version=current_server_version, release_date=current_server_release_date
234
246
  ),
235
247
  current_server_default_cli_version=(
236
- VersionStats(
237
- version=Version(current_server_default_cli_version),
238
- release_date=None,
239
- )
248
+ _safe_version_stats(current_server_default_cli_version)
240
249
  if current_server_default_cli_version
241
250
  else None
242
251
  ),