scouter_types/contracts/
types.rs

1use std::fmt::Display;
2
3use crate::error::{ContractError, TypeError};
4use crate::sql::{TraceListItem, TraceMetricBucket, TraceSpan};
5use crate::{Alert, GenAIEvalTaskResult, GenAIEvalWorkflowResult};
6use crate::{
7    CustomInterval, DriftProfile, GenAIEvalRecord, Status, Tag, TagRecord, TraceBaggageRecord,
8};
9use crate::{DriftType, PyHelperFuncs, TimeInterval};
10use chrono::{DateTime, Utc};
11use pyo3::prelude::*;
12use scouter_semver::VersionType;
13use serde::Deserialize;
14use serde::Serialize;
15use std::collections::BTreeMap;
16use tracing::error;
17
18#[derive(Serialize, Deserialize, Debug, Clone)]
19pub struct ListProfilesRequest {
20    pub space: String,
21    pub name: String,
22    pub version: String,
23}
24
25#[derive(Serialize, Deserialize, Debug, Clone)]
26pub struct ListedProfile {
27    pub profile: DriftProfile,
28    pub active: bool,
29}
30
31#[pyclass]
32#[derive(Serialize, Deserialize, Debug, Clone)]
33pub struct GetProfileRequest {
34    pub name: String,
35    pub space: String,
36    pub version: String,
37    pub drift_type: DriftType,
38}
39
40#[pymethods]
41impl GetProfileRequest {
42    #[new]
43    #[pyo3(signature = (name, space, version, drift_type))]
44    pub fn new(name: String, space: String, version: String, drift_type: DriftType) -> Self {
45        GetProfileRequest {
46            name,
47            space,
48            version,
49            drift_type,
50        }
51    }
52}
53
54#[pyclass]
55#[derive(Serialize, Deserialize, Debug, Clone, Default)]
56pub struct DriftRequest {
57    // this is the uid for a specific space, name, version, drift_type profile
58    pub uid: String,
59    // This is the space name. Used for permission checks
60    pub space: String,
61    pub time_interval: TimeInterval,
62    pub max_data_points: i32,
63    pub start_custom_datetime: Option<DateTime<Utc>>,
64    pub end_custom_datetime: Option<DateTime<Utc>>,
65}
66
67#[pymethods]
68impl DriftRequest {
69    #[new]
70    #[pyo3(signature = (uid, space, time_interval, max_data_points, start_datetime=None, end_datetime=None))]
71    #[allow(clippy::too_many_arguments)]
72    pub fn new(
73        uid: String,
74        space: String,
75        time_interval: TimeInterval,
76        max_data_points: i32,
77        start_datetime: Option<DateTime<Utc>>,
78        end_datetime: Option<DateTime<Utc>>,
79    ) -> Result<Self, ContractError> {
80        // validate time interval
81        let custom_interval = match (start_datetime, end_datetime) {
82            (Some(begin), Some(end)) => Some(CustomInterval::new(begin, end)?),
83            _ => None,
84        };
85
86        Ok(DriftRequest {
87            uid,
88            space,
89            time_interval,
90            max_data_points,
91            start_custom_datetime: custom_interval.as_ref().map(|interval| interval.begin),
92            end_custom_datetime: custom_interval.as_ref().map(|interval| interval.end),
93        })
94    }
95}
96
97impl DriftRequest {
98    pub fn has_custom_interval(&self) -> bool {
99        self.start_custom_datetime.is_some() && self.end_custom_datetime.is_some()
100    }
101
102    pub fn to_custom_interval(&self) -> Option<CustomInterval> {
103        if self.has_custom_interval() {
104            Some(
105                CustomInterval::new(
106                    self.start_custom_datetime.unwrap(),
107                    self.end_custom_datetime.unwrap(),
108                )
109                .unwrap(),
110            )
111        } else {
112            None
113        }
114    }
115}
116
117#[derive(Serialize, Deserialize, Debug, Clone)]
118pub struct VersionRequest {
119    pub version: Option<String>,
120    pub version_type: VersionType,
121    pub pre_tag: Option<String>,
122    pub build_tag: Option<String>,
123}
124
125impl Default for VersionRequest {
126    fn default() -> Self {
127        VersionRequest {
128            version: None,
129            version_type: VersionType::Minor,
130            pre_tag: None,
131            build_tag: None,
132        }
133    }
134}
135
136#[pyclass]
137#[derive(Serialize, Deserialize, Debug, Clone)]
138pub struct ProfileRequest {
139    pub space: String,
140    pub drift_type: DriftType,
141    pub profile: String,
142    pub version_request: Option<VersionRequest>,
143
144    #[serde(default)]
145    pub active: bool,
146
147    #[serde(default)]
148    pub deactivate_others: bool,
149}
150
151#[pyclass]
152#[derive(Serialize, Deserialize, Debug, Clone)]
153pub struct ProfileStatusRequest {
154    pub name: String,
155    pub space: String,
156    pub version: String,
157    pub active: bool,
158    pub drift_type: Option<DriftType>,
159    pub deactivate_others: bool,
160}
161
162#[pymethods]
163impl ProfileStatusRequest {
164    #[new]
165    #[pyo3(signature = (name, space, version, drift_type=None, active=false, deactivate_others=false))]
166    pub fn new(
167        name: String,
168        space: String,
169        version: String,
170        drift_type: Option<DriftType>,
171        active: bool,
172        deactivate_others: bool,
173    ) -> Self {
174        ProfileStatusRequest {
175            name,
176            space,
177            version,
178            active,
179            drift_type,
180            deactivate_others,
181        }
182    }
183}
184
185#[derive(Serialize, Deserialize, Debug, Clone, Default)]
186#[pyclass]
187pub struct DriftAlertPaginationRequest {
188    pub uid: String,
189    pub active: Option<bool>,
190    pub limit: Option<i32>,
191    pub cursor_created_at: Option<DateTime<Utc>>,
192    pub cursor_id: Option<i32>,
193    pub direction: Option<String>, // "next" or "previous"
194    pub start_datetime: Option<DateTime<Utc>>,
195    pub end_datetime: Option<DateTime<Utc>>,
196}
197
198#[pymethods]
199impl DriftAlertPaginationRequest {
200    #[allow(clippy::too_many_arguments)]
201    #[new]
202    #[pyo3(signature = (uid, active=None, limit=None, cursor_created_at=None, cursor_id=None, direction=None, start_datetime=None, end_datetime=None))]
203    pub fn new(
204        uid: String,
205        active: Option<bool>,
206        limit: Option<i32>,
207        cursor_created_at: Option<DateTime<Utc>>,
208        cursor_id: Option<i32>,
209        direction: Option<String>,
210        start_datetime: Option<DateTime<Utc>>,
211        end_datetime: Option<DateTime<Utc>>,
212    ) -> Self {
213        DriftAlertPaginationRequest {
214            uid,
215            active,
216            limit,
217            cursor_created_at,
218            cursor_id,
219            direction,
220            start_datetime,
221            end_datetime,
222        }
223    }
224}
225
226#[derive(Serialize, Deserialize, Debug, Clone, Default)]
227#[pyclass]
228pub struct RecordCursor {
229    #[pyo3(get)]
230    pub created_at: DateTime<Utc>,
231
232    #[pyo3(get)]
233    pub id: i64,
234}
235
236#[pymethods]
237impl RecordCursor {
238    #[new]
239    pub fn new(created_at: DateTime<Utc>, id: i64) -> Self {
240        RecordCursor { created_at, id }
241    }
242}
243
244#[derive(Serialize, Deserialize, Debug, Clone)]
245#[pyclass]
246pub struct DriftAlertPaginationResponse {
247    #[pyo3(get)]
248    pub items: Vec<Alert>,
249
250    #[pyo3(get)]
251    pub has_next: bool,
252
253    #[pyo3(get)]
254    pub next_cursor: Option<RecordCursor>,
255
256    #[pyo3(get)]
257    pub has_previous: bool,
258
259    #[pyo3(get)]
260    pub previous_cursor: Option<RecordCursor>,
261}
262
263#[pymethods]
264impl DriftAlertPaginationResponse {
265    pub fn __str__(&self) -> String {
266        PyHelperFuncs::__str__(self)
267    }
268}
269
270#[derive(Serialize, Deserialize, Debug, Clone, Default)]
271pub struct ServiceInfo {
272    pub space: String,
273    pub uid: String,
274}
275
276#[derive(Serialize, Deserialize, Debug, Clone)]
277pub struct LLMServiceInfo {
278    pub space: String,
279    pub name: String,
280    pub version: String,
281}
282
283#[derive(Serialize, Deserialize, Debug, Clone)]
284pub struct DriftTaskInfo {
285    pub space: String,
286    pub name: String,
287    pub version: String,
288    pub uid: String,
289    pub drift_type: DriftType,
290}
291
292#[derive(Serialize, Deserialize, Debug, Clone)]
293pub struct ObservabilityMetricRequest {
294    pub uid: String,
295    pub time_interval: String,
296    pub max_data_points: i32,
297}
298
299#[derive(Serialize, Deserialize, Debug, Clone)]
300pub struct UpdateAlertStatus {
301    pub id: i32, // this is the unique id for the alert record, not entity_id
302    pub active: bool,
303    pub space: String,
304}
305
306/// Common struct for returning errors from scouter server (axum response)
307#[derive(Serialize, Deserialize, Debug, Clone)]
308pub struct ScouterServerError {
309    pub error: String,
310}
311
312impl ScouterServerError {
313    pub fn permission_denied() -> Self {
314        ScouterServerError {
315            error: "Permission denied".to_string(),
316        }
317    }
318
319    pub fn need_admin_permission() -> Self {
320        error!("User does not have admin permissions");
321        ScouterServerError {
322            error: "Need admin permission".to_string(),
323        }
324    }
325
326    pub fn user_already_exists() -> Self {
327        ScouterServerError {
328            error: "User already exists".to_string(),
329        }
330    }
331
332    pub fn create_user_error<T: Display>(e: T) -> Self {
333        error!("Failed to create user: {}", e);
334        ScouterServerError {
335            error: "Failed to create user".to_string(),
336        }
337    }
338
339    pub fn user_not_found() -> Self {
340        ScouterServerError {
341            error: "User not found".to_string(),
342        }
343    }
344
345    pub fn get_user_error<T: Display>(e: T) -> Self {
346        error!("Failed to get user: {}", e);
347        ScouterServerError {
348            error: "Failed to get user".to_string(),
349        }
350    }
351
352    pub fn list_users_error<T: Display>(e: T) -> Self {
353        error!("Failed to list users: {}", e);
354        ScouterServerError {
355            error: "Failed to list users".to_string(),
356        }
357    }
358
359    pub fn update_user_error<T: Display>(e: T) -> Self {
360        error!("Failed to update user: {}", e);
361        ScouterServerError {
362            error: "Failed to update user".to_string(),
363        }
364    }
365    pub fn delete_user_error<T: Display>(e: T) -> Self {
366        error!("Failed to delete user: {}", e);
367        ScouterServerError {
368            error: "Failed to delete user".to_string(),
369        }
370    }
371
372    pub fn check_last_admin_error<T: Display>(e: T) -> Self {
373        error!("Failed to check admin status: {}", e);
374        ScouterServerError {
375            error: "Failed to check admin status".to_string(),
376        }
377    }
378
379    pub fn cannot_delete_last_admin() -> Self {
380        error!("Cannot delete the last admin user");
381        ScouterServerError {
382            error: "Cannot delete the last admin user".to_string(),
383        }
384    }
385    pub fn username_header_not_found() -> Self {
386        error!("Username header not found");
387        ScouterServerError {
388            error: "Username header not found".to_string(),
389        }
390    }
391
392    pub fn invalid_username_format() -> Self {
393        error!("Invalid username format");
394        ScouterServerError {
395            error: "Invalid username format".to_string(),
396        }
397    }
398
399    pub fn password_header_not_found() -> Self {
400        error!("Password header not found");
401        ScouterServerError {
402            error: "Password header not found".to_string(),
403        }
404    }
405    pub fn invalid_password_format() -> Self {
406        error!("Invalid password format");
407        ScouterServerError {
408            error: "Invalid password format".to_string(),
409        }
410    }
411
412    pub fn user_validation_error() -> Self {
413        error!("User validation failed");
414        ScouterServerError {
415            error: "User validation failed".to_string(),
416        }
417    }
418
419    pub fn failed_token_validation() -> Self {
420        error!("Failed to validate token");
421        ScouterServerError {
422            error: "Failed to validate token".to_string(),
423        }
424    }
425
426    pub fn bearer_token_not_found() -> Self {
427        error!("Bearer token not found");
428        ScouterServerError {
429            error: "Bearer token not found".to_string(),
430        }
431    }
432
433    pub fn refresh_token_error<T: Display>(e: T) -> Self {
434        error!("Failed to refresh token: {}", e);
435        ScouterServerError {
436            error: "Failed to refresh token".to_string(),
437        }
438    }
439
440    pub fn unauthorized<T: Display>(e: T) -> Self {
441        error!("Unauthorized: {}", e);
442        ScouterServerError {
443            error: "Unauthorized".to_string(),
444        }
445    }
446
447    pub fn jwt_decode_error(e: String) -> Self {
448        error!("Failed to decode JWT token: {}", e);
449        ScouterServerError {
450            error: "Failed to decode JWT token".to_string(),
451        }
452    }
453
454    pub fn no_refresh_token() -> Self {
455        error!("No refresh token provided");
456        ScouterServerError {
457            error: "No refresh token provided".to_string(),
458        }
459    }
460
461    pub fn new(error: String) -> Self {
462        ScouterServerError { error }
463    }
464
465    pub fn query_records_error<T: Display>(e: T) -> Self {
466        let msg = format!("Failed to query records: {e}");
467        ScouterServerError { error: msg }
468    }
469
470    pub fn query_alerts_error<T: Display>(e: T) -> Self {
471        let msg = format!("Failed to query alerts: {e}");
472        ScouterServerError { error: msg }
473    }
474
475    pub fn query_profile_error<T: Display>(e: T) -> Self {
476        let msg = format!("Failed to query profile: {e}");
477        ScouterServerError { error: msg }
478    }
479    pub fn query_tags_error<T: Display>(e: T) -> Self {
480        let msg = format!("Failed to query tags: {e}");
481        ScouterServerError { error: msg }
482    }
483
484    pub fn get_baggage_error<T: Display>(e: T) -> Self {
485        let msg = format!("Failed to get trace baggage records: {e}");
486        ScouterServerError { error: msg }
487    }
488
489    pub fn get_paginated_traces_error<T: Display>(e: T) -> Self {
490        let msg = format!("Failed to get paginated traces: {e}");
491        ScouterServerError { error: msg }
492    }
493
494    pub fn get_trace_spans_error<T: Display>(e: T) -> Self {
495        let msg = format!("Failed to get trace spans: {e}");
496        ScouterServerError { error: msg }
497    }
498
499    pub fn get_trace_metrics_error<T: Display>(e: T) -> Self {
500        let msg = format!("Failed to get trace metrics: {e}");
501        ScouterServerError { error: msg }
502    }
503
504    pub fn insert_tags_error<T: Display>(e: T) -> Self {
505        let msg = format!("Failed to insert tags: {e}");
506        ScouterServerError { error: msg }
507    }
508
509    pub fn get_entity_id_by_tags_error<T: Display>(e: T) -> Self {
510        let msg = format!("Failed to get entity IDs by tags: {e}");
511        ScouterServerError { error: msg }
512    }
513
514    pub fn refresh_trace_summary_error<T: Display>(e: T) -> Self {
515        let msg = format!("Failed to refresh trace summary: {e}");
516        ScouterServerError { error: msg }
517    }
518}
519
520#[derive(Serialize, Deserialize, Debug, Clone)]
521pub struct ScouterResponse {
522    pub status: String,
523    pub message: String,
524}
525
526impl ScouterResponse {
527    pub fn new(status: String, message: String) -> Self {
528        ScouterResponse { status, message }
529    }
530}
531
532#[derive(Serialize, Deserialize, Debug, Clone)]
533pub struct RegisteredProfileResponse {
534    pub space: String,
535    pub name: String,
536    pub version: String,
537    pub uid: String,
538    pub status: String,
539    pub active: bool,
540}
541
542#[derive(Serialize, Deserialize, Debug, Clone)]
543pub struct UpdateAlertResponse {
544    pub updated: bool,
545}
546
547#[derive(Debug, Serialize, Deserialize)]
548pub struct GenAIEvalTaskRequest {
549    pub record_uid: String,
550}
551
552#[derive(Debug, Serialize, Deserialize)]
553pub struct GenAIEvalTaskResponse {
554    pub tasks: Vec<GenAIEvalTaskResult>,
555}
556
557#[derive(Serialize, Deserialize, Debug, Clone, Default)]
558pub struct GenAIEvalRecordPaginationRequest {
559    pub service_info: ServiceInfo,
560    pub status: Option<Status>,
561    pub limit: Option<i32>,
562    pub cursor_created_at: Option<DateTime<Utc>>,
563    pub cursor_id: Option<i64>,
564    pub direction: Option<String>,
565    pub start_datetime: Option<DateTime<Utc>>,
566    pub end_datetime: Option<DateTime<Utc>>,
567}
568
569#[derive(Serialize, Deserialize, Debug, Clone, Default)]
570pub struct GenAIEvalRecordPaginationResponse {
571    pub items: Vec<GenAIEvalRecord>,
572    pub has_next: bool,
573    pub next_cursor: Option<RecordCursor>,
574    pub has_previous: bool,
575    pub previous_cursor: Option<RecordCursor>,
576}
577
578#[derive(Serialize, Deserialize, Debug, Clone, Default)]
579pub struct GenAIEvalWorkflowPaginationResponse {
580    pub items: Vec<GenAIEvalWorkflowResult>,
581    pub has_next: bool,
582    pub next_cursor: Option<RecordCursor>,
583    pub has_previous: bool,
584    pub previous_cursor: Option<RecordCursor>,
585}
586
587#[pyclass]
588#[derive(Debug, Clone, Serialize, Deserialize, Default)]
589pub struct BinnedMetricStats {
590    #[pyo3(get)]
591    pub avg: f64,
592
593    #[pyo3(get)]
594    pub lower_bound: f64,
595
596    #[pyo3(get)]
597    pub upper_bound: f64,
598}
599
600#[pymethods]
601impl BinnedMetricStats {
602    pub fn __str__(&self) -> String {
603        // serialize the struct to a string
604        PyHelperFuncs::__str__(self)
605    }
606}
607
608#[pyclass]
609#[derive(Debug, Clone, Serialize, Deserialize, Default)]
610pub struct BinnedMetric {
611    #[pyo3(get)]
612    pub metric: String,
613
614    #[pyo3(get)]
615    pub created_at: Vec<DateTime<Utc>>,
616
617    #[pyo3(get)]
618    pub stats: Vec<BinnedMetricStats>,
619}
620
621#[pymethods]
622impl BinnedMetric {
623    pub fn __str__(&self) -> String {
624        // serialize the struct to a string
625        PyHelperFuncs::__str__(self)
626    }
627}
628
629#[pyclass]
630#[derive(Debug, Clone, Serialize, Deserialize, Default)]
631pub struct BinnedMetrics {
632    #[pyo3(get)]
633    pub metrics: BTreeMap<String, BinnedMetric>,
634}
635
636#[pymethods]
637impl BinnedMetrics {
638    pub fn __str__(&self) -> String {
639        // serialize the struct to a string
640        PyHelperFuncs::__str__(self)
641    }
642
643    pub fn __getitem__<'py>(
644        &self,
645        py: Python<'py>,
646        key: &str,
647    ) -> Result<Option<Py<BinnedMetric>>, TypeError> {
648        match self.metrics.get(key) {
649            Some(metric) => {
650                let metric = Py::new(py, metric.clone())?;
651                Ok(Some(metric))
652            }
653            None => Ok(None),
654        }
655    }
656}
657
658impl BinnedMetrics {
659    pub fn from_vec(metrics: Vec<BinnedMetric>) -> Self {
660        let mapped: BTreeMap<String, BinnedMetric> = metrics
661            .into_iter()
662            .map(|metric| (metric.metric.clone(), metric))
663            .collect();
664        BinnedMetrics { metrics: mapped }
665    }
666}
667
668#[derive(Serialize, Deserialize, Debug, Clone)]
669pub struct TagsRequest {
670    pub entity_type: String,
671    pub entity_id: String,
672}
673
674#[derive(Serialize, Deserialize, Debug, Clone)]
675pub struct EntityIdTagsRequest {
676    pub entity_type: String,
677    pub tags: Vec<Tag>,
678    pub match_all: bool,
679}
680
681#[derive(Serialize, Deserialize, Debug, Clone)]
682pub struct EntityIdTagsResponse {
683    pub entity_id: Vec<String>,
684}
685
686#[derive(Serialize, Deserialize, Debug, Clone)]
687pub struct InsertTagsRequest {
688    pub tags: Vec<TagRecord>,
689}
690
691#[derive(Serialize, Deserialize, Debug, Clone)]
692#[pyclass]
693pub struct TraceRequest {
694    pub trace_id: String,
695    pub service_name: Option<String>,
696}
697
698#[derive(Serialize, Deserialize, Debug, Clone)]
699#[pyclass]
700pub struct TraceMetricsRequest {
701    pub service_name: Option<String>,
702    pub start_time: DateTime<Utc>,
703    pub end_time: DateTime<Utc>,
704    pub bucket_interval: String,
705    pub attribute_filters: Option<Vec<String>>,
706}
707
708#[pymethods]
709impl TraceMetricsRequest {
710    #[new]
711    #[pyo3(signature = (start_time, end_time, bucket_interval,service_name=None, attribute_filters=None))]
712    pub fn new(
713        start_time: DateTime<Utc>,
714        end_time: DateTime<Utc>,
715        bucket_interval: String,
716        service_name: Option<String>,
717        attribute_filters: Option<Vec<String>>,
718    ) -> Self {
719        TraceMetricsRequest {
720            service_name,
721            start_time,
722            end_time,
723            bucket_interval,
724            attribute_filters,
725        }
726    }
727}
728
729#[derive(Serialize, Deserialize, Debug, Clone)]
730#[pyclass]
731pub struct TracePaginationResponse {
732    #[pyo3(get)]
733    pub items: Vec<TraceListItem>,
734
735    #[pyo3(get)]
736    pub has_next: bool,
737
738    #[pyo3(get)]
739    pub next_cursor: Option<TraceCursor>,
740
741    #[pyo3(get)]
742    pub has_previous: bool,
743
744    #[pyo3(get)]
745    pub previous_cursor: Option<TraceCursor>,
746}
747
748#[derive(Serialize, Deserialize, Debug, Clone)]
749#[pyclass]
750pub struct TraceCursor {
751    #[pyo3(get)]
752    pub start_time: DateTime<Utc>,
753
754    #[pyo3(get)]
755    pub trace_id: String,
756}
757
758#[pymethods]
759impl TraceCursor {
760    #[new]
761    pub fn new(start_time: DateTime<Utc>, trace_id: String) -> Self {
762        TraceCursor {
763            start_time,
764            trace_id,
765        }
766    }
767}
768
769#[pymethods]
770impl TracePaginationResponse {
771    pub fn __str__(&self) -> String {
772        PyHelperFuncs::__str__(self)
773    }
774}
775
776#[derive(Serialize, Deserialize, Debug, Clone)]
777#[pyclass]
778pub struct TraceBaggageResponse {
779    #[pyo3(get)]
780    pub baggage: Vec<TraceBaggageRecord>,
781}
782
783#[pymethods]
784impl TraceBaggageResponse {
785    pub fn __str__(&self) -> String {
786        PyHelperFuncs::__str__(self)
787    }
788}
789
790#[derive(Serialize, Deserialize, Debug, Clone)]
791#[pyclass]
792pub struct TraceSpansResponse {
793    #[pyo3(get)]
794    pub spans: Vec<TraceSpan>,
795}
796
797#[pymethods]
798impl TraceSpansResponse {
799    pub fn __str__(&self) -> String {
800        PyHelperFuncs::__str__(self)
801    }
802
803    pub fn get_span_by_name(&self, span_name: &str) -> Result<Option<TraceSpan>, TypeError> {
804        let span = self
805            .spans
806            .iter()
807            .find(|s| s.span_name == span_name)
808            .cloned();
809        Ok(span)
810    }
811}
812
813#[derive(Serialize, Deserialize, Debug, Clone)]
814#[pyclass]
815pub struct TraceMetricsResponse {
816    #[pyo3(get)]
817    pub metrics: Vec<TraceMetricBucket>,
818}
819#[pymethods]
820impl TraceMetricsResponse {
821    pub fn __str__(&self) -> String {
822        PyHelperFuncs::__str__(self)
823    }
824}
825
826#[derive(Serialize, Deserialize, Debug, Clone)]
827#[pyclass]
828pub struct TagsResponse {
829    #[pyo3(get)]
830    pub tags: Vec<TagRecord>,
831}
832
833#[pymethods]
834impl TagsResponse {
835    pub fn __str__(&self) -> String {
836        PyHelperFuncs::__str__(self)
837    }
838}