zenml-nightly 0.83.1.dev20250625__py3-none-any.whl → 0.83.1.dev20250626__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.
Files changed (28) hide show
  1. zenml/VERSION +1 -1
  2. zenml/cli/base.py +3 -2
  3. zenml/cli/service_connectors.py +5 -12
  4. zenml/cli/stack.py +1 -5
  5. zenml/cli/utils.py +8 -52
  6. zenml/client.py +32 -40
  7. zenml/integrations/aws/container_registries/aws_container_registry.py +3 -1
  8. zenml/integrations/databricks/orchestrators/databricks_orchestrator_entrypoint_config.py +8 -3
  9. zenml/integrations/integration.py +23 -58
  10. zenml/models/__init__.py +2 -0
  11. zenml/models/v2/core/service_connector.py +178 -108
  12. zenml/service_connectors/service_connector.py +11 -61
  13. zenml/service_connectors/service_connector_utils.py +4 -2
  14. zenml/utils/package_utils.py +111 -1
  15. zenml/zen_server/routers/service_connectors_endpoints.py +7 -22
  16. zenml/zen_stores/migrations/versions/5bb25e95849c_add_internal_secrets.py +62 -0
  17. zenml/zen_stores/rest_zen_store.py +57 -4
  18. zenml/zen_stores/schemas/secret_schemas.py +5 -0
  19. zenml/zen_stores/schemas/service_connector_schemas.py +16 -14
  20. zenml/zen_stores/secrets_stores/service_connector_secrets_store.py +4 -1
  21. zenml/zen_stores/sql_zen_store.py +214 -102
  22. zenml/zen_stores/zen_store_interface.py +9 -1
  23. {zenml_nightly-0.83.1.dev20250625.dist-info → zenml_nightly-0.83.1.dev20250626.dist-info}/METADATA +1 -1
  24. {zenml_nightly-0.83.1.dev20250625.dist-info → zenml_nightly-0.83.1.dev20250626.dist-info}/RECORD +27 -27
  25. zenml/utils/integration_utils.py +0 -34
  26. {zenml_nightly-0.83.1.dev20250625.dist-info → zenml_nightly-0.83.1.dev20250626.dist-info}/LICENSE +0 -0
  27. {zenml_nightly-0.83.1.dev20250625.dist-info → zenml_nightly-0.83.1.dev20250626.dist-info}/WHEEL +0 -0
  28. {zenml_nightly-0.83.1.dev20250625.dist-info → zenml_nightly-0.83.1.dev20250626.dist-info}/entry_points.txt +0 -0
zenml/VERSION CHANGED
@@ -1 +1 @@
1
- 0.83.1.dev20250625
1
+ 0.83.1.dev20250626
zenml/cli/base.py CHANGED
@@ -49,6 +49,7 @@ from zenml.integrations.registry import integration_registry
49
49
  from zenml.io import fileio
50
50
  from zenml.logger import get_logger
51
51
  from zenml.utils.io_utils import copy_dir, get_global_config_directory
52
+ from zenml.utils.package_utils import get_package_information
52
53
  from zenml.utils.server_utils import get_local_server
53
54
  from zenml.utils.yaml_utils import write_yaml
54
55
 
@@ -640,7 +641,7 @@ def info(
640
641
  }
641
642
 
642
643
  if all:
643
- user_info["packages"] = cli_utils.get_package_information()
644
+ user_info["packages"] = get_package_information()
644
645
  if packages:
645
646
  if user_info.get("packages"):
646
647
  if isinstance(user_info["packages"], dict):
@@ -650,7 +651,7 @@ def info(
650
651
  if p in packages
651
652
  }
652
653
  else:
653
- user_info["query_packages"] = cli_utils.get_package_information(
654
+ user_info["query_packages"] = get_package_information(
654
655
  list(packages)
655
656
  )
656
657
  if file:
@@ -899,14 +899,7 @@ def register_service_connector(
899
899
 
900
900
  # Prepare the rest of the variables to fall through to the
901
901
  # non-interactive configuration case
902
- parsed_args = connector_model.configuration
903
- parsed_args.update(
904
- {
905
- k: s.get_secret_value()
906
- for k, s in connector_model.secrets.items()
907
- if s is not None
908
- }
909
- )
902
+ parsed_args = connector_model.configuration.plain
910
903
  auto_configure = False
911
904
  no_verify = False
912
905
  expiration_seconds = connector_model.expiration_seconds
@@ -1137,7 +1130,7 @@ def describe_service_connector(
1137
1130
  try:
1138
1131
  connector = client.get_service_connector(
1139
1132
  name_id_or_prefix=name_id_or_prefix,
1140
- load_secrets=True,
1133
+ expand_secrets=show_secrets,
1141
1134
  )
1142
1135
  except KeyError as err:
1143
1136
  cli_utils.error(str(err))
@@ -1385,7 +1378,7 @@ def update_service_connector(
1385
1378
  connector = client.get_service_connector(
1386
1379
  name_id_or_prefix,
1387
1380
  allow_name_prefix_match=False,
1388
- load_secrets=True,
1381
+ expand_secrets=True,
1389
1382
  )
1390
1383
  except KeyError as e:
1391
1384
  cli_utils.error(str(e))
@@ -1445,7 +1438,7 @@ def update_service_connector(
1445
1438
  default=False,
1446
1439
  )
1447
1440
 
1448
- existing_config = connector.full_configuration
1441
+ existing_config = connector.configuration.plain
1449
1442
 
1450
1443
  if confirm:
1451
1444
  # Here we reconfigure the connector or update the existing
@@ -1582,7 +1575,7 @@ def update_service_connector(
1582
1575
  # Non-interactive configuration
1583
1576
 
1584
1577
  # Apply the configuration from the command line arguments
1585
- config_dict = connector.full_configuration
1578
+ config_dict = connector.configuration.plain
1586
1579
  config_dict.update(parsed_args)
1587
1580
 
1588
1581
  if not resource_type and not connector.is_multi_type:
zenml/cli/stack.py CHANGED
@@ -1783,14 +1783,10 @@ def _get_service_connector_info(
1783
1783
  answers = {}
1784
1784
  for req_field in required_fields:
1785
1785
  if connector_details:
1786
- if conf_value := connector_details.configuration.get(
1786
+ if conf_value := connector_details.configuration.get_plain(
1787
1787
  req_field, None
1788
1788
  ):
1789
1789
  answers[req_field] = conf_value
1790
- elif secret_value := connector_details.secrets.get(
1791
- req_field, None
1792
- ):
1793
- answers[req_field] = secret_value.get_secret_value()
1794
1790
  if req_field not in answers:
1795
1791
  answers[req_field] = Prompt.ask(
1796
1792
  f"Please enter value for `{req_field}`:",
zenml/cli/utils.py CHANGED
@@ -40,7 +40,6 @@ from typing import (
40
40
  )
41
41
 
42
42
  import click
43
- import pkg_resources
44
43
  import yaml
45
44
  from pydantic import BaseModel, SecretStr
46
45
  from rich import box, table
@@ -80,6 +79,7 @@ from zenml.stack import StackComponent
80
79
  from zenml.stack.flavor import Flavor
81
80
  from zenml.stack.stack_component import StackComponentConfig
82
81
  from zenml.utils import secret_utils
82
+ from zenml.utils.package_utils import requirement_installed
83
83
  from zenml.utils.time_utils import expires_in
84
84
  from zenml.utils.typing_utils import get_origin, is_union
85
85
 
@@ -1052,7 +1052,7 @@ def install_packages(
1052
1052
  # just return without doing anything
1053
1053
  return
1054
1054
 
1055
- if use_uv and not is_installed_in_python_environment("uv"):
1055
+ if use_uv and not requirement_installed("uv"):
1056
1056
  # If uv is installed globally, don't run as a python module
1057
1057
  command = []
1058
1058
  else:
@@ -1094,7 +1094,7 @@ def uninstall_package(package: str, use_uv: bool = False) -> None:
1094
1094
  package: The package to uninstall.
1095
1095
  use_uv: Whether to use uv for package uninstallation.
1096
1096
  """
1097
- if use_uv and not is_installed_in_python_environment("uv"):
1097
+ if use_uv and not requirement_installed("uv"):
1098
1098
  # If uv is installed globally, don't run as a python module
1099
1099
  command = []
1100
1100
  else:
@@ -1110,22 +1110,6 @@ def uninstall_package(package: str, use_uv: bool = False) -> None:
1110
1110
  subprocess.check_call(command)
1111
1111
 
1112
1112
 
1113
- def is_installed_in_python_environment(package: str) -> bool:
1114
- """Check if a package is installed in the current python environment.
1115
-
1116
- Args:
1117
- package: The package to check.
1118
-
1119
- Returns:
1120
- True if the package is installed, False otherwise.
1121
- """
1122
- try:
1123
- pkg_resources.get_distribution(package)
1124
- return True
1125
- except pkg_resources.DistributionNotFound:
1126
- return False
1127
-
1128
-
1129
1113
  def is_uv_installed() -> bool:
1130
1114
  """Check if uv is installed.
1131
1115
 
@@ -1141,7 +1125,7 @@ def is_pip_installed() -> bool:
1141
1125
  Returns:
1142
1126
  True if pip is installed, False otherwise.
1143
1127
  """
1144
- return is_installed_in_python_environment("pip")
1128
+ return requirement_installed("pip")
1145
1129
 
1146
1130
 
1147
1131
  def pretty_print_secret(
@@ -1830,7 +1814,7 @@ def print_service_connector_configuration(
1830
1814
 
1831
1815
  console.print(rich_table)
1832
1816
 
1833
- if len(connector.configuration) == 0 and len(connector.secrets) == 0:
1817
+ if len(connector.configuration) == 0:
1834
1818
  declare("No configuration options are set for this connector.")
1835
1819
 
1836
1820
  else:
@@ -1842,8 +1826,8 @@ def print_service_connector_configuration(
1842
1826
  rich_table.add_column("PROPERTY")
1843
1827
  rich_table.add_column("VALUE", overflow="fold")
1844
1828
 
1845
- config = connector.configuration.copy()
1846
- secrets = connector.secrets.copy()
1829
+ config = connector.configuration.non_secrets
1830
+ secrets = connector.configuration.secrets
1847
1831
  for key, value in secrets.items():
1848
1832
  if not show_secrets:
1849
1833
  config[key] = "[HIDDEN]"
@@ -2499,30 +2483,6 @@ def temporary_active_stack(
2499
2483
  Client().activate_stack(old_stack_id)
2500
2484
 
2501
2485
 
2502
- def get_package_information(
2503
- package_names: Optional[List[str]] = None,
2504
- ) -> Dict[str, str]:
2505
- """Get a dictionary of installed packages.
2506
-
2507
- Args:
2508
- package_names: Specific package names to get the information for.
2509
-
2510
- Returns:
2511
- A dictionary of the name:version for the package names passed in or
2512
- all packages and their respective versions.
2513
- """
2514
- import pkg_resources
2515
-
2516
- if package_names:
2517
- return {
2518
- pkg.key: pkg.version
2519
- for pkg in pkg_resources.working_set
2520
- if pkg.key in package_names
2521
- }
2522
-
2523
- return {pkg.key: pkg.version for pkg in pkg_resources.working_set}
2524
-
2525
-
2526
2486
  def print_user_info(info: Dict[str, Any]) -> None:
2527
2487
  """Print user information to the terminal.
2528
2488
 
@@ -2617,11 +2577,7 @@ def is_jupyter_installed() -> bool:
2617
2577
  Returns:
2618
2578
  bool: True if Jupyter notebook is installed, False otherwise.
2619
2579
  """
2620
- try:
2621
- pkg_resources.get_distribution("notebook")
2622
- return True
2623
- except pkg_resources.DistributionNotFound:
2624
- return False
2580
+ return requirement_installed("notebook")
2625
2581
 
2626
2582
 
2627
2583
  def multi_choice_prompt(
zenml/client.py CHANGED
@@ -5500,17 +5500,18 @@ class Client(metaclass=ClientMetaClass):
5500
5500
  self,
5501
5501
  name_id_or_prefix: Union[str, UUID],
5502
5502
  allow_name_prefix_match: bool = True,
5503
- load_secrets: bool = False,
5504
5503
  hydrate: bool = True,
5504
+ expand_secrets: bool = False,
5505
5505
  ) -> ServiceConnectorResponse:
5506
5506
  """Fetches a registered service connector.
5507
5507
 
5508
5508
  Args:
5509
5509
  name_id_or_prefix: The id of the service connector to fetch.
5510
5510
  allow_name_prefix_match: If True, allow matching by name prefix.
5511
- load_secrets: If True, load the secrets for the service connector.
5512
5511
  hydrate: Flag deciding whether to hydrate the output model(s)
5513
5512
  by including metadata fields in the response.
5513
+ expand_secrets: If True, expand the secrets for the service
5514
+ connector.
5514
5515
 
5515
5516
  Returns:
5516
5517
  The registered service connector.
@@ -5521,25 +5522,9 @@ class Client(metaclass=ClientMetaClass):
5521
5522
  name_id_or_prefix=name_id_or_prefix,
5522
5523
  allow_name_prefix_match=allow_name_prefix_match,
5523
5524
  hydrate=hydrate,
5525
+ expand_secrets=expand_secrets,
5524
5526
  )
5525
5527
 
5526
- if load_secrets and connector.secret_id:
5527
- client = Client()
5528
- try:
5529
- secret = client.get_secret(
5530
- name_id_or_prefix=connector.secret_id,
5531
- allow_partial_id_match=False,
5532
- allow_partial_name_match=False,
5533
- )
5534
- except KeyError as err:
5535
- logger.error(
5536
- "Unable to retrieve secret values associated with "
5537
- f"service connector '{connector.name}': {err}"
5538
- )
5539
- else:
5540
- # Add secret values to connector configuration
5541
- connector.secrets.update(secret.values)
5542
-
5543
5528
  return connector
5544
5529
 
5545
5530
  def list_service_connectors(
@@ -5558,8 +5543,8 @@ class Client(metaclass=ClientMetaClass):
5558
5543
  resource_id: Optional[str] = None,
5559
5544
  user: Optional[Union[UUID, str]] = None,
5560
5545
  labels: Optional[Dict[str, Optional[str]]] = None,
5561
- secret_id: Optional[Union[str, UUID]] = None,
5562
5546
  hydrate: bool = False,
5547
+ expand_secrets: bool = False,
5563
5548
  ) -> Page[ServiceConnectorResponse]:
5564
5549
  """Lists all registered service connectors.
5565
5550
 
@@ -5580,10 +5565,10 @@ class Client(metaclass=ClientMetaClass):
5580
5565
  user: Filter by user name/ID.
5581
5566
  name: The name of the service connector to filter by.
5582
5567
  labels: The labels of the service connector to filter by.
5583
- secret_id: Filter by the id of the secret that is referenced by the
5584
- service connector.
5585
5568
  hydrate: Flag deciding whether to hydrate the output model(s)
5586
5569
  by including metadata fields in the response.
5570
+ expand_secrets: If True, expand the secrets for the service
5571
+ connectors.
5587
5572
 
5588
5573
  Returns:
5589
5574
  A page of service connectors.
@@ -5603,11 +5588,11 @@ class Client(metaclass=ClientMetaClass):
5603
5588
  created=created,
5604
5589
  updated=updated,
5605
5590
  labels=labels,
5606
- secret_id=secret_id,
5607
5591
  )
5608
5592
  return self.zen_store.list_service_connectors(
5609
5593
  filter_model=connector_filter_model,
5610
5594
  hydrate=hydrate,
5595
+ expand_secrets=expand_secrets,
5611
5596
  )
5612
5597
 
5613
5598
  def update_service_connector(
@@ -5691,7 +5676,9 @@ class Client(metaclass=ClientMetaClass):
5691
5676
  connector_model = self.get_service_connector(
5692
5677
  name_id_or_prefix,
5693
5678
  allow_name_prefix_match=False,
5694
- load_secrets=True,
5679
+ # We need the existing secrets only if a new configuration is not
5680
+ # provided.
5681
+ expand_secrets=configuration is None,
5695
5682
  )
5696
5683
 
5697
5684
  connector_instance: Optional[ServiceConnector] = None
@@ -5736,23 +5723,16 @@ class Client(metaclass=ClientMetaClass):
5736
5723
  )
5737
5724
 
5738
5725
  # Validate and configure the resources
5739
- if configuration is not None:
5726
+ connector_update.validate_and_configure_resources(
5727
+ connector_type=connector,
5728
+ resource_types=resource_types,
5729
+ resource_id=resource_id,
5740
5730
  # The supplied configuration is a drop-in replacement for the
5741
- # existing configuration and secrets
5742
- connector_update.validate_and_configure_resources(
5743
- connector_type=connector,
5744
- resource_types=resource_types,
5745
- resource_id=resource_id,
5746
- configuration=configuration,
5747
- )
5748
- else:
5749
- connector_update.validate_and_configure_resources(
5750
- connector_type=connector,
5751
- resource_types=resource_types,
5752
- resource_id=resource_id,
5753
- configuration=connector_model.configuration,
5754
- secrets=connector_model.secrets,
5755
- )
5731
+ # existing configuration
5732
+ configuration=configuration
5733
+ if configuration is not None
5734
+ else connector_model.configuration,
5735
+ )
5756
5736
 
5757
5737
  # Add the labels
5758
5738
  if labels is not None:
@@ -5912,6 +5892,12 @@ class Client(metaclass=ClientMetaClass):
5912
5892
  list_resources=list_resources,
5913
5893
  )
5914
5894
  else:
5895
+ # Get the service connector model, with full secrets
5896
+ service_connector = self.get_service_connector(
5897
+ name_id_or_prefix=name_id_or_prefix,
5898
+ allow_name_prefix_match=False,
5899
+ expand_secrets=True,
5900
+ )
5915
5901
  connector_instance = (
5916
5902
  service_connector_registry.instantiate_connector(
5917
5903
  model=service_connector
@@ -6034,6 +6020,12 @@ class Client(metaclass=ClientMetaClass):
6034
6020
  # server-side implementation may not be able to do so
6035
6021
  connector_client.verify()
6036
6022
  else:
6023
+ # Get the service connector model, with full secrets
6024
+ service_connector = self.get_service_connector(
6025
+ name_id_or_prefix=name_id_or_prefix,
6026
+ allow_name_prefix_match=False,
6027
+ expand_secrets=True,
6028
+ )
6037
6029
  connector_instance = (
6038
6030
  service_connector_registry.instantiate_connector(
6039
6031
  model=service_connector
@@ -81,7 +81,9 @@ class AWSContainerRegistry(BaseContainerRegistry):
81
81
  """
82
82
  if self.connector:
83
83
  try:
84
- model = Client().get_service_connector(self.connector)
84
+ model = Client().get_service_connector(
85
+ self.connector, expand_secrets=True
86
+ )
85
87
  connector = service_connector_registry.instantiate_connector(
86
88
  model=model
87
89
  )
@@ -17,7 +17,10 @@ import os
17
17
  import sys
18
18
  from typing import Any, List, Set
19
19
 
20
- import pkg_resources
20
+ if sys.version_info < (3, 10):
21
+ from importlib_metadata import distribution
22
+ else:
23
+ from importlib.metadata import distribution
21
24
 
22
25
  from zenml.entrypoints.step_entrypoint_configuration import (
23
26
  StepEntrypointConfiguration,
@@ -81,8 +84,10 @@ class DatabricksEntrypointConfiguration(StepEntrypointConfiguration):
81
84
  """Runs the step."""
82
85
  # Get the wheel package and add it to the sys path
83
86
  wheel_package = self.entrypoint_args[WHEEL_PACKAGE_OPTION]
84
- distribution = pkg_resources.get_distribution(wheel_package)
85
- project_root = os.path.join(distribution.location, wheel_package)
87
+
88
+ dist = distribution(wheel_package)
89
+ project_root = os.path.join(dist.locate_file("."), wheel_package)
90
+
86
91
  if project_root not in sys.path:
87
92
  sys.path.insert(0, project_root)
88
93
  sys.path.insert(-1, project_root)
@@ -13,16 +13,14 @@
13
13
  # permissions and limitations under the License.
14
14
  """Base and meta classes for ZenML integrations."""
15
15
 
16
- import re
17
16
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, cast
18
17
 
19
- import pkg_resources
20
- from pkg_resources import Requirement
18
+ from packaging.requirements import Requirement
21
19
 
22
20
  from zenml.integrations.registry import integration_registry
23
21
  from zenml.logger import get_logger
24
22
  from zenml.stack.flavor import Flavor
25
- from zenml.utils.integration_utils import parse_requirement
23
+ from zenml.utils.package_utils import get_dependencies, requirement_installed
26
24
 
27
25
  if TYPE_CHECKING:
28
26
  from zenml.plugins.base_plugin_flavor import BasePluginFlavor
@@ -69,65 +67,32 @@ class Integration(metaclass=IntegrationMeta):
69
67
  Returns:
70
68
  True if all required packages are installed, False otherwise.
71
69
  """
72
- for r in cls.get_requirements():
73
- try:
74
- # First check if the base package is installed
75
- dist = pkg_resources.get_distribution(r)
76
-
77
- # Next, check if the dependencies (including extras) are
78
- # installed
79
- deps: List[Requirement] = []
80
-
81
- _, extras = parse_requirement(r)
82
- if extras:
83
- extra_list = extras[1:-1].split(",")
84
- for extra in extra_list:
85
- try:
86
- requirements = dist.requires(extras=[extra]) # type: ignore[arg-type]
87
- except pkg_resources.UnknownExtra as e:
88
- logger.debug(f"Unknown extra: {str(e)}")
89
- return False
90
- deps.extend(requirements)
91
- else:
92
- deps = dist.requires()
93
-
94
- for ri in deps:
95
- try:
96
- # Remove the "extra == ..." part from the requirement string
97
- cleaned_req = re.sub(
98
- r"; extra == \"\w+\"", "", str(ri)
99
- )
100
- pkg_resources.get_distribution(cleaned_req)
101
- except pkg_resources.DistributionNotFound as e:
102
- logger.debug(
103
- f"Unable to find required dependency "
104
- f"'{e.req}' for requirement '{r}' "
105
- f"necessary for integration '{cls.NAME}'."
106
- )
107
- return False
108
- except pkg_resources.VersionConflict as e:
109
- logger.debug(
110
- f"Package version '{e.dist}' does not match "
111
- f"version '{e.req}' required by '{r}' "
112
- f"necessary for integration '{cls.NAME}'."
113
- )
114
- return False
115
-
116
- except pkg_resources.DistributionNotFound as e:
117
- logger.debug(
118
- f"Unable to find required package '{e.req}' for "
119
- f"integration {cls.NAME}."
120
- )
121
- return False
122
- except pkg_resources.VersionConflict as e:
70
+ for requirement in cls.get_requirements():
71
+ parsed_requirement = Requirement(requirement)
72
+
73
+ if not requirement_installed(parsed_requirement):
123
74
  logger.debug(
124
- f"Package version '{e.dist}' does not match version "
125
- f"'{e.req}' necessary for integration {cls.NAME}."
75
+ "Requirement '%s' for integration '%s' is not installed "
76
+ "or installed with the wrong version.",
77
+ requirement,
78
+ cls.NAME,
126
79
  )
127
80
  return False
128
81
 
82
+ dependencies = get_dependencies(parsed_requirement)
83
+
84
+ for dependency in dependencies:
85
+ if not requirement_installed(dependency):
86
+ logger.debug(
87
+ "Requirement '%s' for integration '%s' is not "
88
+ "installed or installed with the wrong version.",
89
+ dependency,
90
+ cls.NAME,
91
+ )
92
+ return False
93
+
129
94
  logger.debug(
130
- f"Integration {cls.NAME} is installed correctly with "
95
+ f"Integration '{cls.NAME}' is installed correctly with "
131
96
  f"requirements {cls.get_requirements()}."
132
97
  )
133
98
  return True
zenml/models/__init__.py CHANGED
@@ -286,6 +286,7 @@ from zenml.models.v2.core.service_account import (
286
286
  ServiceAccountResponse,
287
287
  )
288
288
  from zenml.models.v2.core.service_connector import (
289
+ ServiceConnectorConfiguration,
289
290
  ServiceConnectorRequest,
290
291
  ServiceConnectorUpdate,
291
292
  ServiceConnectorFilter,
@@ -739,6 +740,7 @@ __all__ = [
739
740
  "ServiceAccountUpdate",
740
741
  "ServiceAccountRequest",
741
742
  "ServiceAccountResponse",
743
+ "ServiceConnectorConfiguration",
742
744
  "ServiceConnectorRequest",
743
745
  "ServiceConnectorUpdate",
744
746
  "ServiceConnectorFilter",