acryl-datahub 1.0.0.3rc10__py3-none-any.whl → 1.0.0.3rc11__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.

Files changed (45) hide show
  1. {acryl_datahub-1.0.0.3rc10.dist-info → acryl_datahub-1.0.0.3rc11.dist-info}/METADATA +2471 -2418
  2. {acryl_datahub-1.0.0.3rc10.dist-info → acryl_datahub-1.0.0.3rc11.dist-info}/RECORD +45 -45
  3. {acryl_datahub-1.0.0.3rc10.dist-info → acryl_datahub-1.0.0.3rc11.dist-info}/WHEEL +1 -1
  4. datahub/_version.py +1 -1
  5. datahub/api/entities/forms/forms.py +2 -1
  6. datahub/cli/check_cli.py +3 -2
  7. datahub/cli/config_utils.py +2 -2
  8. datahub/cli/delete_cli.py +5 -4
  9. datahub/cli/exists_cli.py +2 -1
  10. datahub/cli/get_cli.py +2 -1
  11. datahub/cli/iceberg_cli.py +6 -5
  12. datahub/cli/ingest_cli.py +9 -6
  13. datahub/cli/migrate.py +4 -3
  14. datahub/cli/migration_utils.py +4 -3
  15. datahub/cli/put_cli.py +3 -2
  16. datahub/cli/specific/assertions_cli.py +2 -1
  17. datahub/cli/specific/datacontract_cli.py +3 -2
  18. datahub/cli/specific/dataproduct_cli.py +10 -9
  19. datahub/cli/specific/dataset_cli.py +4 -3
  20. datahub/cli/specific/forms_cli.py +2 -1
  21. datahub/cli/specific/group_cli.py +2 -1
  22. datahub/cli/specific/structuredproperties_cli.py +4 -3
  23. datahub/cli/specific/user_cli.py +2 -1
  24. datahub/cli/state_cli.py +2 -1
  25. datahub/cli/timeline_cli.py +2 -1
  26. datahub/emitter/rest_emitter.py +120 -42
  27. datahub/entrypoints.py +2 -1
  28. datahub/ingestion/graph/client.py +16 -9
  29. datahub/ingestion/graph/config.py +13 -0
  30. datahub/ingestion/run/pipeline.py +3 -2
  31. datahub/ingestion/run/pipeline_config.py +1 -1
  32. datahub/ingestion/sink/datahub_rest.py +5 -6
  33. datahub/ingestion/source/apply/datahub_apply.py +2 -1
  34. datahub/ingestion/source/ge_data_profiler.py +2 -1
  35. datahub/ingestion/source/metadata/lineage.py +2 -1
  36. datahub/ingestion/source/state_provider/datahub_ingestion_checkpointing_provider.py +2 -1
  37. datahub/integrations/assertion/common.py +3 -2
  38. datahub/sdk/main_client.py +2 -2
  39. datahub/secret/datahub_secret_store.py +2 -1
  40. datahub/telemetry/telemetry.py +2 -2
  41. datahub/upgrade/upgrade.py +10 -12
  42. datahub/utilities/server_config_util.py +378 -10
  43. {acryl_datahub-1.0.0.3rc10.dist-info → acryl_datahub-1.0.0.3rc11.dist-info}/entry_points.txt +0 -0
  44. {acryl_datahub-1.0.0.3rc10.dist-info → acryl_datahub-1.0.0.3rc11.dist-info}/licenses/LICENSE +0 -0
  45. {acryl_datahub-1.0.0.3rc10.dist-info → acryl_datahub-1.0.0.3rc11.dist-info}/top_level.txt +0 -0
@@ -34,7 +34,7 @@ from datahub.ingestion.api.sink import (
34
34
  WriteCallback,
35
35
  )
36
36
  from datahub.ingestion.api.workunit import MetadataWorkUnit
37
- from datahub.ingestion.graph.client import DatahubClientConfig
37
+ from datahub.ingestion.graph.config import ClientMode, DatahubClientConfig
38
38
  from datahub.metadata.com.linkedin.pegasus2avro.mxe import (
39
39
  MetadataChangeEvent,
40
40
  MetadataChangeProposal,
@@ -140,11 +140,7 @@ class DatahubRestSink(Sink[DatahubRestSinkConfig, DataHubRestSinkReport]):
140
140
  f"💥 Failed to connect to DataHub with {repr(self.emitter)}"
141
141
  ) from exc
142
142
 
143
- self.report.gms_version = (
144
- gms_config.get("versions", {})
145
- .get("acryldata/datahub", {})
146
- .get("version", None)
147
- )
143
+ self.report.gms_version = gms_config.service_version
148
144
  self.report.mode = self.config.mode
149
145
  self.report.max_threads = self.config.max_threads
150
146
  logger.debug("Setting env variables to override config")
@@ -180,6 +176,8 @@ class DatahubRestSink(Sink[DatahubRestSinkConfig, DataHubRestSinkReport]):
180
176
  disable_ssl_verification=config.disable_ssl_verification,
181
177
  openapi_ingestion=config.endpoint == RestSinkEndpoint.OPENAPI,
182
178
  default_trace_mode=config.default_trace_mode == RestTraceMode.ENABLED,
179
+ client_mode=config.client_mode,
180
+ datahub_component=config.datahub_component,
183
181
  )
184
182
 
185
183
  @property
@@ -190,6 +188,7 @@ class DatahubRestSink(Sink[DatahubRestSinkConfig, DataHubRestSinkReport]):
190
188
  # https://github.com/psf/requests/issues/1871#issuecomment-32751346
191
189
  thread_local = self._emitter_thread_local
192
190
  if not hasattr(thread_local, "emitter"):
191
+ self.config.client_mode = ClientMode.INGESTION
193
192
  thread_local.emitter = DatahubRestSink._make_emitter(self.config)
194
193
  return thread_local.emitter
195
194
 
@@ -18,6 +18,7 @@ from datahub.ingestion.api.source import MetadataWorkUnitProcessor, Source, Sour
18
18
  from datahub.ingestion.api.source_helpers import auto_workunit_reporter
19
19
  from datahub.ingestion.api.workunit import MetadataWorkUnit
20
20
  from datahub.ingestion.graph.client import DataHubGraph, get_default_graph
21
+ from datahub.ingestion.graph.config import ClientMode
21
22
  from datahub.metadata.schema_classes import (
22
23
  DomainsClass,
23
24
  GlossaryTermAssociationClass,
@@ -48,7 +49,7 @@ def apply_association_to_container(
48
49
  """
49
50
  urns: List[str] = [container_urn]
50
51
  if not graph:
51
- graph = get_default_graph()
52
+ graph = get_default_graph(ClientMode.INGESTION)
52
53
  logger.info(f"Using {graph}")
53
54
  urns.extend(
54
55
  graph.get_urns_by_filter(
@@ -51,6 +51,7 @@ from typing_extensions import Concatenate, ParamSpec
51
51
  from datahub.emitter import mce_builder
52
52
  from datahub.emitter.mce_builder import get_sys_time
53
53
  from datahub.ingestion.graph.client import get_default_graph
54
+ from datahub.ingestion.graph.config import ClientMode
54
55
  from datahub.ingestion.source.ge_profiling_config import GEProfilingConfig
55
56
  from datahub.ingestion.source.profiling.common import (
56
57
  Cardinality,
@@ -1569,7 +1570,7 @@ def _get_columns_to_ignore_sampling(
1569
1570
  name=dataset_name, platform=platform, env=env
1570
1571
  )
1571
1572
 
1572
- datahub_graph = get_default_graph()
1573
+ datahub_graph = get_default_graph(ClientMode.INGESTION)
1573
1574
 
1574
1575
  dataset_tags = datahub_graph.get_tags(dataset_urn)
1575
1576
  if dataset_tags:
@@ -36,6 +36,7 @@ from datahub.ingestion.api.source_helpers import (
36
36
  )
37
37
  from datahub.ingestion.api.workunit import MetadataWorkUnit
38
38
  from datahub.ingestion.graph.client import get_default_graph
39
+ from datahub.ingestion.graph.config import ClientMode
39
40
  from datahub.metadata.com.linkedin.pegasus2avro.dataset import (
40
41
  FineGrainedLineageDownstreamType,
41
42
  FineGrainedLineageUpstreamType,
@@ -210,7 +211,7 @@ def _get_lineage_mcp(
210
211
 
211
212
  # extract the old lineage and save it for the new mcp
212
213
  if preserve_upstream:
213
- client = get_default_graph()
214
+ client = get_default_graph(ClientMode.INGESTION)
214
215
 
215
216
  old_upstream_lineage = get_aspects_for_entity(
216
217
  client._session,
@@ -10,7 +10,8 @@ from datahub.ingestion.api.ingestion_job_checkpointing_provider_base import (
10
10
  IngestionCheckpointingProviderConfig,
11
11
  JobId,
12
12
  )
13
- from datahub.ingestion.graph.client import DatahubClientConfig, DataHubGraph
13
+ from datahub.ingestion.graph.client import DataHubGraph
14
+ from datahub.ingestion.graph.config import DatahubClientConfig
14
15
  from datahub.metadata.schema_classes import DatahubIngestionCheckpointClass
15
16
 
16
17
  logger = logging.getLogger(__name__)
@@ -3,6 +3,7 @@ from typing import List, Optional, Tuple, TypedDict
3
3
 
4
4
  from datahub.api.entities.assertion.assertion import BaseEntityAssertion
5
5
  from datahub.ingestion.graph.client import get_default_graph
6
+ from datahub.ingestion.graph.config import ClientMode
6
7
  from datahub.metadata.com.linkedin.pegasus2avro.dataset import DatasetProperties
7
8
  from datahub.metadata.com.linkedin.pegasus2avro.schema import SchemaMetadata
8
9
  from datahub.utilities.urns.urn import Urn
@@ -15,7 +16,7 @@ class ColumnDict(TypedDict):
15
16
 
16
17
  @lru_cache
17
18
  def get_qualified_name_from_datahub(urn: str) -> Optional[str]:
18
- with get_default_graph() as graph:
19
+ with get_default_graph(ClientMode.CLI) as graph:
19
20
  props: Optional[DatasetProperties] = graph.get_aspect(urn, DatasetProperties)
20
21
  if props is not None:
21
22
  return props.qualifiedName
@@ -24,7 +25,7 @@ def get_qualified_name_from_datahub(urn: str) -> Optional[str]:
24
25
 
25
26
  @lru_cache
26
27
  def get_schema_from_datahub(urn: str) -> Optional[List[ColumnDict]]:
27
- with get_default_graph() as graph:
28
+ with get_default_graph(ClientMode.INGESTION) as graph:
28
29
  schema: Optional[SchemaMetadata] = graph.get_aspect(urn, SchemaMetadata)
29
30
  if schema is not None:
30
31
  return [
@@ -4,7 +4,7 @@ from typing import Optional, overload
4
4
 
5
5
  from datahub.errors import SdkUsageError
6
6
  from datahub.ingestion.graph.client import DataHubGraph, get_default_graph
7
- from datahub.ingestion.graph.config import DatahubClientConfig
7
+ from datahub.ingestion.graph.config import ClientMode, DatahubClientConfig
8
8
  from datahub.sdk.entity_client import EntityClient
9
9
  from datahub.sdk.lineage_client import LineageClient
10
10
  from datahub.sdk.resolver_client import ResolverClient
@@ -84,7 +84,7 @@ class DataHubClient:
84
84
  # Inspired by the DockerClient.from_env() method.
85
85
  # TODO: This one also reads from ~/.datahubenv, so the "from_env" name might be a bit confusing.
86
86
  # That file is part of the "environment", but is not a traditional "env variable".
87
- graph = get_default_graph()
87
+ graph = get_default_graph(ClientMode.SDK)
88
88
 
89
89
  return cls(graph=graph)
90
90
 
@@ -3,7 +3,8 @@ from typing import Any, Dict, List, Optional, Union
3
3
 
4
4
  from pydantic import BaseModel, validator
5
5
 
6
- from datahub.ingestion.graph.client import DatahubClientConfig, DataHubGraph
6
+ from datahub.ingestion.graph.client import DataHubGraph
7
+ from datahub.ingestion.graph.config import DatahubClientConfig
7
8
  from datahub.secret.datahub_secrets_client import DataHubSecretsClient
8
9
  from datahub.secret.secret_store import SecretStore
9
10
 
@@ -352,10 +352,10 @@ class Telemetry:
352
352
  }
353
353
  else:
354
354
  return {
355
- "server_type": server.server_config.get("datahub", {}).get(
355
+ "server_type": server.server_config.raw_config.get("datahub", {}).get(
356
356
  "serverType", "missing"
357
357
  ),
358
- "server_version": server.server_config.get("versions", {})
358
+ "server_version": server.server_config.raw_config.get("versions", {})
359
359
  .get("acryldata/datahub", {})
360
360
  .get("version", "missing"),
361
361
  "server_id": server.server_id or "missing",
@@ -13,7 +13,9 @@ from pydantic import BaseModel
13
13
  from datahub._version import __version__
14
14
  from datahub.cli.config_utils import load_client_config
15
15
  from datahub.ingestion.graph.client import DataHubGraph
16
+ from datahub.ingestion.graph.config import ClientMode
16
17
  from datahub.utilities.perf_timer import PerfTimer
18
+ from datahub.utilities.server_config_util import RestServiceConfig
17
19
 
18
20
  log = logging.getLogger(__name__)
19
21
 
@@ -109,7 +111,7 @@ async def get_github_stats():
109
111
  return (latest_server_version, latest_server_date)
110
112
 
111
113
 
112
- async def get_server_config(gms_url: str, token: Optional[str]) -> dict:
114
+ async def get_server_config(gms_url: str, token: Optional[str]) -> RestServiceConfig:
113
115
  import aiohttp
114
116
 
115
117
  headers = {
@@ -124,7 +126,7 @@ async def get_server_config(gms_url: str, token: Optional[str]) -> dict:
124
126
  config_endpoint = f"{gms_url}/config"
125
127
  async with session.get(config_endpoint, headers=headers) as dh_response:
126
128
  dh_response_json = await dh_response.json()
127
- return dh_response_json
129
+ return RestServiceConfig(raw_config=dh_response_json)
128
130
 
129
131
 
130
132
  async def get_server_version_stats(
@@ -132,11 +134,12 @@ async def get_server_version_stats(
132
134
  ) -> Tuple[Optional[str], Optional[Version], Optional[datetime]]:
133
135
  import aiohttp
134
136
 
135
- server_config = None
137
+ server_config: Optional[RestServiceConfig] = None
136
138
  if not server:
137
139
  try:
138
140
  # let's get the server from the cli config
139
141
  client_config = load_client_config()
142
+ client_config.client_mode = ClientMode.CLI
140
143
  host = client_config.server
141
144
  token = client_config.token
142
145
  server_config = await get_server_config(host, token)
@@ -150,15 +153,10 @@ async def get_server_version_stats(
150
153
  server_version: Optional[Version] = None
151
154
  current_server_release_date = None
152
155
  if server_config:
153
- server_version_string = (
154
- server_config.get("versions", {})
155
- .get("acryldata/datahub", {})
156
- .get("version")
157
- )
158
- commit_hash = (
159
- server_config.get("versions", {}).get("acryldata/datahub", {}).get("commit")
160
- )
161
- server_type = server_config.get("datahub", {}).get("serverType", "unknown")
156
+ server_version_string = server_config.service_version
157
+ commit_hash = server_config.commit_hash
158
+ server_type = server_config.server_type
159
+
162
160
  if server_type == "quickstart" and commit_hash:
163
161
  async with aiohttp.ClientSession(
164
162
  headers={"Accept": "application/vnd.github.v3+json"}
@@ -1,23 +1,391 @@
1
- from typing import Any, Dict, Optional
1
+ import logging
2
+ import re
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+ from typing import (
6
+ Any,
7
+ Dict,
8
+ Optional,
9
+ Tuple,
10
+ Union,
11
+ )
2
12
 
13
+ import requests
14
+
15
+ from datahub.configuration.common import (
16
+ ConfigurationError,
17
+ )
3
18
  from datahub.telemetry.telemetry import suppress_telemetry
4
19
 
20
+ logger = logging.getLogger(__name__)
21
+
5
22
  # Only to be written to for logging server related information
6
23
  global_debug: Dict[str, Any] = {}
7
24
 
8
25
 
9
- def set_gms_config(config: Dict) -> Any:
26
+ def get_gms_config() -> Dict:
27
+ return global_debug.get("gms_config", {})
28
+
29
+
30
+ class ServiceFeature(Enum):
31
+ """
32
+ Enum representing supported features in the REST service.
33
+ """
34
+
35
+ OPEN_API_SDK = "openapi_sdk"
36
+ API_TRACING = "api_tracing"
37
+ NO_CODE = "no_code"
38
+ STATEFUL_INGESTION = "stateful_ingestion"
39
+ IMPACT_ANALYSIS = "impact_analysis"
40
+ PATCH_CAPABLE = "patch_capable"
41
+ CLI_TELEMETRY = "cli_telemetry"
42
+ DATAHUB_CLOUD = "datahub_cloud"
43
+ # Add more features as needed
44
+
45
+
46
+ _REQUIRED_VERSION_OPENAPI_TRACING = {
47
+ "acryl": (
48
+ 0,
49
+ 3,
50
+ 11,
51
+ 0,
52
+ ), # Requires v0.3.11.0 or higher for acryl versions
53
+ "cloud": (0, 3, 11, 0), # Special case for '-cloud' suffix
54
+ "any_suffix": (0, 3, 11, 0), # Generic requirement for any other suffix
55
+ "none": (1, 0, 1, 0), # Requirement for versions without suffix
56
+ }
57
+
58
+
59
+ @dataclass
60
+ class RestServiceConfig:
61
+ """
62
+ A class to represent REST service configuration with semantic version parsing capabilities.
63
+ """
64
+
65
+ session: Optional[requests.Session] = None
66
+ url: Optional[str] = None
67
+ raw_config: Dict[str, Any] = field(default_factory=dict)
68
+ _version_cache: Optional[Tuple[int, int, int, int]] = None
69
+
70
+ def fetch_config(self) -> Dict[str, Any]:
71
+ """
72
+ Fetch configuration from the server if not already loaded.
73
+
74
+ Returns:
75
+ The configuration dictionary
76
+
77
+ Raises:
78
+ ConfigurationError: If there's an error fetching or validating the configuration
79
+ """
80
+ if not self.raw_config:
81
+ if self.session is None or self.url is None:
82
+ raise ConfigurationError(
83
+ "Session and URL are required to load configuration"
84
+ )
85
+
86
+ response = self.session.get(self.url)
87
+
88
+ if response.status_code == 200:
89
+ config = response.json()
90
+
91
+ # Validate that we're connected to the correct service
92
+ if config.get("noCode") == "true":
93
+ self.raw_config = config
94
+ else:
95
+ raise ConfigurationError(
96
+ "You seem to have connected to the frontend service instead of the GMS endpoint. "
97
+ "The rest emitter should connect to DataHub GMS (usually <datahub-gms-host>:8080) or Frontend GMS API (usually <frontend>:9002/api/gms). "
98
+ "For Acryl users, the endpoint should be https://<name>.acryl.io/gms"
99
+ )
100
+ else:
101
+ logger.debug(
102
+ f"Unable to connect to {self.url} with status_code: {response.status_code}. Response: {response.text}"
103
+ )
104
+
105
+ if response.status_code == 401:
106
+ message = f"Unable to connect to {self.url} - got an authentication error: {response.text}."
107
+ else:
108
+ message = f"Unable to connect to {self.url} with status_code: {response.status_code}."
109
+
110
+ message += "\nPlease check your configuration and make sure you are talking to the DataHub GMS (usually <datahub-gms-host>:8080) or Frontend GMS API (usually <frontend>:9002/api/gms)."
111
+ raise ConfigurationError(message)
112
+
113
+ return self.raw_config
114
+
115
+ @property
116
+ def config(self) -> Dict[str, Any]:
117
+ """
118
+ Get the full configuration dictionary, loading it if necessary.
119
+
120
+ Returns:
121
+ The configuration dictionary
122
+ """
123
+ return self.fetch_config()
124
+
125
+ @property
126
+ def commit_hash(self) -> Optional[str]:
127
+ """
128
+ Get the commit hash for the current version.
129
+
130
+ Returns:
131
+ The commit hash or None if not found
132
+ """
133
+ versions = self.config.get("versions") or {}
134
+ datahub_info = versions.get("acryldata/datahub") or {}
135
+ return datahub_info.get("commit")
136
+
137
+ @property
138
+ def server_type(self) -> str:
139
+ """
140
+ Get the server type.
141
+
142
+ Returns:
143
+ The server type or "unknown" if not found
144
+ """
145
+ datahub = self.config.get("datahub") or {}
146
+ return datahub.get("serverType", "unknown")
147
+
148
+ @property
149
+ def service_version(self) -> Optional[str]:
150
+ """
151
+ Get the raw service version string.
152
+
153
+ Returns:
154
+ The version string or None if not found
155
+ """
156
+ config = self.fetch_config()
157
+ versions = config.get("versions") or {}
158
+ datahub_info = versions.get("acryldata/datahub") or {}
159
+ return datahub_info.get("version")
160
+
161
+ def _parse_version(
162
+ self, version_str: Optional[str] = None
163
+ ) -> Tuple[int, int, int, int]:
164
+ """
165
+ Parse a semantic version string into its components, ignoring rc and suffixes.
166
+ Supports standard three-part versions (1.0.0) and four-part versions (1.0.0.1).
167
+
168
+ Args:
169
+ version_str: Version string to parse. If None, uses the service version.
170
+
171
+ Returns:
172
+ Tuple of (major, minor, patch, build) version numbers where build is 0 for three-part versions
173
+
174
+ Raises:
175
+ ValueError: If the version string cannot be parsed
176
+ """
177
+ if version_str is None:
178
+ version_str = self.service_version
179
+
180
+ if not version_str:
181
+ return (0, 0, 0, 0)
182
+
183
+ # Remove 'v' prefix if present
184
+ if version_str.startswith("v"):
185
+ version_str = version_str[1:]
186
+
187
+ # Extract the semantic version part (before any rc or suffix)
188
+ # This pattern will match both three-part (1.0.0) and four-part (1.0.0.1) versions
189
+ match = re.match(r"(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?(?:rc\d+|-.*)?", version_str)
190
+ if not match:
191
+ raise ValueError(f"Invalid version format: {version_str}")
192
+
193
+ major = int(match.group(1))
194
+ minor = int(match.group(2))
195
+ patch = int(match.group(3))
196
+ build = (
197
+ int(match.group(4)) if match.group(4) else 0
198
+ ) # Default to 0 if not present
199
+
200
+ return (major, minor, patch, build)
201
+
202
+ @property
203
+ def parsed_version(self) -> Optional[Tuple[int, int, int, int]]:
204
+ """
205
+ Get the parsed semantic version of the service.
206
+ Uses caching for efficiency.
207
+
208
+ Returns:
209
+ Tuple of (major, minor, patch) version numbers
210
+ """
211
+ if self._version_cache is None:
212
+ self._version_cache = self._parse_version()
213
+ return self._version_cache
214
+
215
+ def is_version_at_least(
216
+ self, major: int, minor: int = 0, patch: int = 0, build: int = 0
217
+ ) -> bool:
218
+ """
219
+ Check if the service version is at least the specified version.
220
+
221
+ Args:
222
+ major: Major version to check against
223
+ minor: Minor version to check against
224
+ patch: Patch version to check against
225
+ build: Build version to check against (for four-part versions)
226
+
227
+ Returns:
228
+ True if the service version is at least the specified version
229
+ """
230
+ current_version = self.parsed_version or (0, 0, 0, 0)
231
+ requested_version = (major, minor, patch, build)
232
+
233
+ return current_version >= requested_version
234
+
235
+ @property
236
+ def is_no_code_enabled(self) -> bool:
237
+ """
238
+ Check if noCode is enabled.
239
+
240
+ Returns:
241
+ True if noCode is set to "true"
242
+ """
243
+ return self.config.get("noCode") == "true"
244
+
245
+ @property
246
+ def is_managed_ingestion_enabled(self) -> bool:
247
+ """
248
+ Check if managedIngestion is enabled.
249
+
250
+ Returns:
251
+ True if managedIngestion.enabled is True
252
+ """
253
+ managed_ingestion = self.config.get("managedIngestion") or {}
254
+ return managed_ingestion.get("enabled", False)
255
+
256
+ @property
257
+ def is_datahub_cloud(self) -> bool:
258
+ """
259
+ Check if DataHub Cloud is enabled.
260
+
261
+ Returns:
262
+ True if the server environment is not 'oss'
263
+ """
264
+ datahub_config = self.config.get("datahub") or {}
265
+ server_env = datahub_config.get("serverEnv")
266
+
267
+ # Return False if serverEnv is None or empty string
268
+ if not server_env:
269
+ return False
270
+
271
+ return server_env != "oss"
272
+
273
+ def supports_feature(self, feature: ServiceFeature) -> bool:
274
+ """
275
+ Determines whether a specific feature is supported based on service version.
276
+
277
+ Version categorization follows these rules:
278
+ 1. Has '-acryl' suffix (highest priority)
279
+ 2. Has a specific known suffix (e.g. '-other')
280
+ 3. Has some other suffix (catchall for any suffix)
281
+ 4. No suffix
282
+
283
+ Args:
284
+ feature: Feature enum value to check
285
+
286
+ Returns:
287
+ Boolean indicating whether the feature is supported
288
+ """
289
+ version = self.service_version
290
+ if not version:
291
+ return False
292
+
293
+ # Determine the suffix category
294
+ suffix_category = "none" # Default: no suffix
295
+
296
+ if "-" in version:
297
+ suffix = version.split("-", 1)[1]
298
+
299
+ if suffix == "acryl":
300
+ suffix_category = "acryl"
301
+ elif suffix == "cloud": # Example of a specific override
302
+ suffix_category = "cloud"
303
+ else:
304
+ suffix_category = "any_suffix" # Catchall for any other suffix
305
+
306
+ # Define feature requirements based on version scheme
307
+ # This can be expanded to include more features
308
+ feature_requirements = {
309
+ ServiceFeature.OPEN_API_SDK: _REQUIRED_VERSION_OPENAPI_TRACING,
310
+ ServiceFeature.API_TRACING: _REQUIRED_VERSION_OPENAPI_TRACING,
311
+ # Additional features can be defined here
312
+ }
313
+
314
+ # Special handling for features that rely on config flags instead of version
315
+ config_based_features = {
316
+ ServiceFeature.NO_CODE: lambda: self.is_no_code_enabled,
317
+ ServiceFeature.STATEFUL_INGESTION: lambda: self.config.get(
318
+ "statefulIngestionCapable", False
319
+ )
320
+ is True,
321
+ ServiceFeature.IMPACT_ANALYSIS: lambda: self.config.get(
322
+ "supportsImpactAnalysis", False
323
+ )
324
+ is True,
325
+ ServiceFeature.PATCH_CAPABLE: lambda: self.config.get("patchCapable", False)
326
+ is True,
327
+ ServiceFeature.CLI_TELEMETRY: lambda: (
328
+ self.config.get("telemetry") or {}
329
+ ).get("enabledCli", None),
330
+ # Add more config-based feature checks as needed
331
+ }
332
+
333
+ # Check if this is a config-based feature
334
+ if feature in config_based_features:
335
+ return config_based_features[feature]()
336
+
337
+ # Check if the feature exists in our requirements dictionary
338
+ if feature not in feature_requirements:
339
+ # Unknown feature, assume not supported
340
+ return False
341
+
342
+ # Get version requirements for this feature and version category
343
+ feature_reqs = feature_requirements[feature]
344
+ requirements = feature_reqs.get(suffix_category)
345
+
346
+ if not requirements:
347
+ # Fallback to the no-suffix requirements if specific requirements aren't defined
348
+ requirements = feature_reqs.get(
349
+ "none", (99, 99, 99, 99)
350
+ ) # Very high version if none defined
351
+
352
+ # Check if the current version meets the requirements
353
+ req_major, req_minor, req_patch, req_build = requirements
354
+ return self.is_version_at_least(req_major, req_minor, req_patch, req_build)
355
+
356
+ def __str__(self) -> str:
357
+ """
358
+ Return a string representation of the configuration as JSON.
359
+
360
+ Returns:
361
+ A string representation of the configuration dictionary
362
+ """
363
+ return str(self.config)
364
+
365
+ def __repr__(self) -> str:
366
+ """
367
+ Return a representation of the object that can be used to recreate it.
368
+
369
+ Returns:
370
+ A string representation that can be used with pprint
371
+ """
372
+ return str(self.config)
373
+
374
+
375
+ def set_gms_config(config: Union[Dict[str, Any], RestServiceConfig]) -> None:
10
376
  global_debug["gms_config"] = config
11
377
 
12
- cli_telemtry_enabled = is_cli_telemetry_enabled()
13
- if cli_telemtry_enabled is not None and not cli_telemtry_enabled:
378
+ config_obj = (
379
+ config
380
+ if isinstance(config, RestServiceConfig)
381
+ else RestServiceConfig(raw_config=config)
382
+ )
383
+
384
+ cli_telemetry_enabled = is_cli_telemetry_enabled(config_obj)
385
+ if cli_telemetry_enabled is not None and not cli_telemetry_enabled:
14
386
  # server requires telemetry to be disabled on client
15
387
  suppress_telemetry()
16
388
 
17
389
 
18
- def get_gms_config() -> Dict:
19
- return global_debug.get("gms_config", {})
20
-
21
-
22
- def is_cli_telemetry_enabled() -> Optional[bool]:
23
- return get_gms_config().get("telemetry", {}).get("enabledCli", None)
390
+ def is_cli_telemetry_enabled(config: RestServiceConfig) -> bool:
391
+ return config.supports_feature(ServiceFeature.CLI_TELEMETRY)