11import logging
2- from typing import Iterator , Optional
2+ from typing import Optional
33
44from datamasque .client .base import BaseClient
55from datamasque .client .exceptions import DataMasqueApiError , DataMasqueException
6- from datamasque .client .models .discovery_config_library import DiscoveryConfigLibrary , DiscoveryConfigLibraryId
7- from datamasque .client .models .pagination import Page
6+ from datamasque .client .models .discovery_config_library import (
7+ DiscoveryConfigLibrary ,
8+ DiscoveryConfigLibraryId ,
9+ DiscoveryConfigType ,
10+ )
811
912logger = logging .getLogger (__name__ )
1013
1114
1215class DiscoveryConfigLibraryClient (BaseClient ):
1316 """Discovery config library CRUD API methods. Mixed into `DataMasqueClient`."""
1417
15- def iter_discovery_config_libraries (self ) -> Iterator [DiscoveryConfigLibrary ]:
16- """Lazily iterate all discovery config libraries via the paginated endpoint."""
17-
18- return self ._iter_paginated ("/api/discovery/config-libraries/" , model = DiscoveryConfigLibrary )
19-
2018 def list_discovery_config_libraries (self ) -> list [DiscoveryConfigLibrary ]:
2119 """
2220 Lists all (non-archived) discovery config libraries.
2321
22+ Unlike most list endpoints, this one is not paginated;
23+ the server returns every library in a single response.
2424 Note: the YAML content is not included in the list response for performance.
2525 Use `get_discovery_config_library` to retrieve the full library with its YAML body.
2626 """
2727
28- return list (self .iter_discovery_config_libraries ())
28+ response = self .make_request ("GET" , "/api/discovery/config-libraries/" )
29+ return [DiscoveryConfigLibrary .model_validate (item ) for item in response .json ()]
2930
3031 def get_discovery_config_library (self , library_id : DiscoveryConfigLibraryId ) -> DiscoveryConfigLibrary :
3132 """Retrieves a single discovery config library by ID, including its YAML content."""
3233
3334 response = self .make_request ("GET" , f"/api/discovery/config-libraries/{ library_id } /" )
3435 return DiscoveryConfigLibrary .model_validate (response .json ())
3536
36- def get_discovery_config_library_by_name (self , name : str , namespace : str = "" ) -> Optional [DiscoveryConfigLibrary ]:
37+ def find_discovery_config_library_ids (
38+ self , name : str , namespace : str , config_type : Optional [DiscoveryConfigType ]
39+ ) -> list [DiscoveryConfigLibraryId ]:
3740 """
38- Looks for a discovery config library matching the given name and namespace (case-sensitive, exact match) .
41+ Returns the IDs of libraries matching the given name, namespace, and (optionally) config type .
3942
40- Returns it (with full YAML content) if found, otherwise `None`.
43+ The name is filtered server-side via `name_exact` to keep the response small,
44+ but all three fields are matched client-side:
45+ the server's `namespace_exact` filter ignores empty strings (the default namespace),
46+ and delete-by-name must not trust a filter param the server may ignore.
4147 """
4248
4349 response = self .make_request (
4450 "GET" ,
4551 "/api/discovery/config-libraries/" ,
46- params = {"name_exact" : name , "namespace_exact" : namespace , "limit" : 1 },
52+ params = {"name_exact" : name },
4753 )
48- page = Page [DiscoveryConfigLibrary ].model_validate (response .json ())
49- if not page .results :
54+ entries = [DiscoveryConfigLibrary .model_validate (item ) for item in response .json ()]
55+ matches = [
56+ entry
57+ for entry in entries
58+ if entry .name == name
59+ and entry .namespace == namespace
60+ and (config_type is None or entry .config_type is config_type )
61+ ]
62+
63+ library_ids = []
64+ for entry in matches :
65+ if entry .id is None :
66+ raise DataMasqueApiError (
67+ "Server returned a discovery config library list entry without an `id`." ,
68+ response = response ,
69+ )
70+ library_ids .append (entry .id )
71+
72+ return library_ids
73+
74+ def get_discovery_config_library_by_name (
75+ self , name : str , namespace : str = "" , config_type : Optional [DiscoveryConfigType ] = None
76+ ) -> Optional [DiscoveryConfigLibrary ]:
77+ """
78+ Looks for a discovery config library matching the given name and namespace (case-sensitive, exact match).
79+
80+ Returns it (with full YAML content) if found, otherwise `None`.
81+
82+ A database and a file library may share a name;
83+ pass `config_type` to disambiguate.
84+ Raises `DataMasqueException` if `config_type` was not given and both exist.
85+ """
86+
87+ library_ids = self .find_discovery_config_library_ids (name , namespace , config_type )
88+ if not library_ids :
5089 return None
5190
52- library_id = page .results [0 ].id
53- if library_id is None :
54- raise DataMasqueApiError (
55- "Server returned a discovery config library list entry without an `id`." ,
56- response = response ,
91+ if len (library_ids ) > 1 :
92+ raise DataMasqueException (
93+ f'Multiple discovery config libraries are named "{ name } "; pass `config_type` to disambiguate.'
5794 )
5895
59- return self .get_discovery_config_library (library_id )
96+ return self .get_discovery_config_library (library_ids [ 0 ] )
6097
6198 def create_discovery_config_library (self , library : DiscoveryConfigLibrary ) -> DiscoveryConfigLibrary :
6299 """
63100 Creates a new discovery config library on the server.
64101
65- Sets the library's server-assigned fields (`id`, `is_valid`, `created`, `modified`) and returns the library.
102+ Sets the library's server-assigned fields
103+ (`id`, `is_valid`, `validation_error`, `created`, `modified`) and returns the library.
66104 """
67105
68106 data = library .model_dump (exclude_none = True , by_alias = True , mode = "json" )
69107 response = self .make_request ("POST" , "/api/discovery/config-libraries/" , data = data )
70108 created = DiscoveryConfigLibrary .model_validate (response .json ())
71109 library .id = created .id
72110 library .is_valid = created .is_valid
111+ library .validation_error = created .validation_error
73112 library .created = created .created
74113 library .modified = created .modified
75114 logger .info ('Creation of discovery config library "%s" successful' , library .name )
@@ -79,30 +118,39 @@ def update_discovery_config_library(self, library: DiscoveryConfigLibrary) -> Di
79118 """
80119 Performs a full update of the discovery config library.
81120
82- The library must have its `id` set (i.e., it must have been previously created or retrieved from the server).
121+ The library must have its `id` set (i.e., it must have been previously created or retrieved from the server)
122+ and its `yaml` content present.
123+ A library's `config_type` is fixed at creation and cannot be changed by an update.
83124 """
84125
85126 if library .id is None :
86127 raise ValueError ("Cannot update a discovery config library that has not been created yet (id is None)" )
87128
129+ if library .yaml is None :
130+ raise ValueError (
131+ "Cannot update a discovery config library without YAML content (yaml is None); "
132+ "list results omit YAML, so fetch the full library with `get_discovery_config_library` first"
133+ )
134+
88135 data = library .model_dump (exclude_none = True , by_alias = True , mode = "json" )
89136 response = self .make_request ("PUT" , f"/api/discovery/config-libraries/{ library .id } /" , data = data )
90137 updated = DiscoveryConfigLibrary .model_validate (response .json ())
91138 library .is_valid = updated .is_valid
139+ library .validation_error = updated .validation_error
92140 library .modified = updated .modified
93141 logger .debug ('Update of discovery config library "%s" successful' , library .name )
94142 return library
95143
96144 def create_or_update_discovery_config_library (self , library : DiscoveryConfigLibrary ) -> DiscoveryConfigLibrary :
97145 """
98- Creates the library if it doesn't exist , or updates it if one with the same name and namespace already exists .
146+ Creates the library, or updates the existing one with the same name, namespace, and config type .
99147
100148 Sets the library's `id` property.
101149 """
102150
103- existing = self .get_discovery_config_library_by_name (library .name , library .namespace )
104- if existing is not None :
105- library .id = existing . id
151+ library_ids = self .find_discovery_config_library_ids (library .name , library .namespace , library . config_type )
152+ if library_ids :
153+ library .id = library_ids [ 0 ]
106154 return self .update_discovery_config_library (library )
107155
108156 return self .create_discovery_config_library (library )
@@ -123,20 +171,21 @@ def delete_discovery_config_library_by_id_if_exists(
123171 self ._delete_if_exists (f"/api/discovery/config-libraries/{ library_id } /" , params = params )
124172
125173 def delete_discovery_config_library_by_name_if_exists (
126- self , name : str , namespace : str = "" , * , force : bool = False
174+ self ,
175+ name : str ,
176+ namespace : str = "" ,
177+ * ,
178+ config_type : Optional [DiscoveryConfigType ] = None ,
179+ force : bool = False ,
127180 ) -> None :
128181 """
129- Deletes the discovery config library with the given name and namespace.
182+ Deletes the discovery config library(s) with the given name and namespace.
130183
131- No-op if the library does not exist.
132- """
184+ No-op if no library matches.
133185
134- all_libraries = self .list_discovery_config_libraries ()
135- matching = [lib for lib in all_libraries if lib .name == name and lib .namespace == namespace ]
136- for lib in matching :
137- if lib .id is None :
138- raise DataMasqueException (
139- f'Server returned a discovery config library named "{ lib .name } " without an `id`.'
140- )
186+ When `config_type` is not given and both a database and a file library share the name,
187+ both are deleted.
188+ """
141189
142- self .delete_discovery_config_library_by_id_if_exists (lib .id , force = force )
190+ for library_id in self .find_discovery_config_library_ids (name , namespace , config_type ):
191+ self .delete_discovery_config_library_by_id_if_exists (library_id , force = force )
0 commit comments