miso-client 0.2.0__py3-none-any.whl → 0.5.0__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 miso-client might be problematic. Click here for more details.

@@ -0,0 +1,116 @@
1
+ """
2
+ Sensitive fields configuration loader for ISO 27001 compliance.
3
+
4
+ This module provides utilities to load and merge sensitive fields configuration
5
+ from JSON files, supporting custom configuration paths and environment variables.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ # Default path to sensitive fields config relative to this file
14
+ _DEFAULT_CONFIG_PATH = Path(__file__).parent / "sensitive_fields_config.json"
15
+
16
+
17
+ def load_sensitive_fields_config(
18
+ config_path: Optional[str] = None,
19
+ ) -> Dict[str, Any]:
20
+ """
21
+ Load sensitive fields configuration from JSON file.
22
+
23
+ Supports custom path via:
24
+ 1. config_path parameter
25
+ 2. MISO_SENSITIVE_FIELDS_CONFIG environment variable
26
+ 3. Default path: miso_client/utils/sensitive_fields_config.json
27
+
28
+ Args:
29
+ config_path: Optional custom path to JSON config file
30
+
31
+ Returns:
32
+ Dictionary with 'fields' and 'fieldPatterns' keys
33
+ Returns empty dict if file cannot be loaded
34
+
35
+ Example:
36
+ >>> config = load_sensitive_fields_config()
37
+ >>> fields = config.get('fields', {})
38
+ """
39
+ # Priority: parameter > environment variable > default
40
+ if config_path:
41
+ file_path = Path(config_path)
42
+ elif os.environ.get("MISO_SENSITIVE_FIELDS_CONFIG"):
43
+ file_path = Path(os.environ["MISO_SENSITIVE_FIELDS_CONFIG"])
44
+ else:
45
+ file_path = _DEFAULT_CONFIG_PATH
46
+
47
+ try:
48
+ with open(file_path, "r", encoding="utf-8") as f:
49
+ config = json.load(f)
50
+ # Validate structure
51
+ if isinstance(config, dict):
52
+ return config
53
+ return {}
54
+ except (FileNotFoundError, json.JSONDecodeError, IOError, OSError):
55
+ # File not found, invalid JSON, or permission error
56
+ # Return empty dict - fallback to hardcoded defaults
57
+ return {}
58
+
59
+
60
+ def get_sensitive_fields_array(
61
+ config_path: Optional[str] = None,
62
+ ) -> List[str]:
63
+ """
64
+ Get flattened array of all sensitive field names from configuration.
65
+
66
+ Args:
67
+ config_path: Optional custom path to JSON config file
68
+
69
+ Returns:
70
+ Flattened list of all sensitive field names from all categories
71
+ Returns empty list if config cannot be loaded
72
+
73
+ Example:
74
+ >>> fields = get_sensitive_fields_array()
75
+ >>> assert 'password' in fields
76
+ >>> assert 'token' in fields
77
+ """
78
+ config = load_sensitive_fields_config(config_path)
79
+ fields_dict = config.get("fields", {})
80
+
81
+ # Flatten all categories into single list
82
+ all_fields: List[str] = []
83
+ if isinstance(fields_dict, dict):
84
+ for category_fields in fields_dict.values():
85
+ if isinstance(category_fields, list):
86
+ all_fields.extend(category_fields)
87
+
88
+ # Remove duplicates while preserving order
89
+ seen = set()
90
+ unique_fields = []
91
+ for field in all_fields:
92
+ if field.lower() not in seen:
93
+ seen.add(field.lower())
94
+ unique_fields.append(field)
95
+
96
+ return unique_fields
97
+
98
+
99
+ def get_field_patterns(config_path: Optional[str] = None) -> List[str]:
100
+ """
101
+ Get field pattern matching rules from configuration.
102
+
103
+ Args:
104
+ config_path: Optional custom path to JSON config file
105
+
106
+ Returns:
107
+ List of field pattern matching rules
108
+ Returns empty list if config cannot be loaded or no patterns defined
109
+
110
+ Example:
111
+ >>> patterns = get_field_patterns()
112
+ >>> # Patterns can be regex patterns or simple matching rules
113
+ """
114
+ config = load_sensitive_fields_config(config_path)
115
+ patterns = config.get("fieldPatterns", [])
116
+ return patterns if isinstance(patterns, list) else []
@@ -0,0 +1,116 @@
1
+ """
2
+ Sort utilities for MisoClient SDK.
3
+
4
+ This module provides reusable sort utilities for parsing sort parameters
5
+ and building sort query strings.
6
+ """
7
+
8
+ from typing import List
9
+ from urllib.parse import quote
10
+
11
+ from ..models.sort import SortOption
12
+
13
+
14
+ def parse_sort_params(params: dict) -> List[SortOption]:
15
+ """
16
+ Parse sort query parameters into SortOption list.
17
+
18
+ Parses `?sort=-field` format into SortOption objects.
19
+ Supports multiple sort parameters (array of sort strings).
20
+ Prefix with '-' for descending order, otherwise ascending.
21
+
22
+ Args:
23
+ params: Dictionary with query parameters (e.g., {'sort': '-updated_at'} or {'sort': ['-updated_at', 'created_at']})
24
+
25
+ Returns:
26
+ List of SortOption objects
27
+
28
+ Examples:
29
+ >>> parse_sort_params({'sort': '-updated_at'})
30
+ [SortOption(field='updated_at', order='desc')]
31
+ >>> parse_sort_params({'sort': ['-updated_at', 'created_at']})
32
+ [SortOption(field='updated_at', order='desc'), SortOption(field='created_at', order='asc')]
33
+ """
34
+ sort_options: List[SortOption] = []
35
+
36
+ # Get sort parameter (can be string or list)
37
+ sort_param = params.get("sort")
38
+ if not sort_param:
39
+ return sort_options
40
+
41
+ # Normalize to list
42
+ if isinstance(sort_param, str):
43
+ sort_strings = [sort_param]
44
+ elif isinstance(sort_param, list):
45
+ sort_strings = sort_param
46
+ else:
47
+ return sort_options
48
+
49
+ # Parse each sort string
50
+ for sort_str in sort_strings:
51
+ if not isinstance(sort_str, str):
52
+ continue
53
+
54
+ sort_str = sort_str.strip()
55
+ if not sort_str:
56
+ continue
57
+
58
+ # Check for descending order (prefix with '-')
59
+ if sort_str.startswith("-"):
60
+ field = sort_str[1:].strip()
61
+ order = "desc"
62
+ else:
63
+ field = sort_str.strip()
64
+ order = "asc"
65
+
66
+ if field:
67
+ sort_options.append(SortOption(field=field, order=order))
68
+
69
+ return sort_options
70
+
71
+
72
+ def build_sort_string(sort_options: List[SortOption]) -> str:
73
+ """
74
+ Convert SortOption list to query string format.
75
+
76
+ Converts SortOption objects to sort query string format.
77
+ Descending order fields are prefixed with '-'.
78
+
79
+ Args:
80
+ sort_options: List of SortOption objects
81
+
82
+ Returns:
83
+ Sort query string (e.g., '-updated_at,created_at' or single value '-updated_at')
84
+
85
+ Examples:
86
+ >>> from miso_client.models.sort import SortOption
87
+ >>> sort_options = [SortOption(field='updated_at', order='desc')]
88
+ >>> build_sort_string(sort_options)
89
+ '-updated_at'
90
+ >>> sort_options = [
91
+ ... SortOption(field='updated_at', order='desc'),
92
+ ... SortOption(field='created_at', order='asc')
93
+ ... ]
94
+ >>> build_sort_string(sort_options)
95
+ '-updated_at,created_at'
96
+ """
97
+ if not sort_options:
98
+ return ""
99
+
100
+ sort_strings: List[str] = []
101
+ for sort_option in sort_options:
102
+ field = sort_option.field
103
+ order = sort_option.order
104
+
105
+ # URL encode field name
106
+ field_encoded = quote(field)
107
+
108
+ # Add '-' prefix for descending order
109
+ if order == "desc":
110
+ sort_strings.append(f"-{field_encoded}")
111
+ else:
112
+ sort_strings.append(field_encoded)
113
+
114
+ # Join multiple sorts with comma (if needed for single sort param)
115
+ # Or return as comma-separated string
116
+ return ",".join(sort_strings)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: miso-client
3
- Version: 0.2.0
3
+ Version: 0.5.0
4
4
  Summary: Python client SDK for AI Fabrix authentication, authorization, and logging
5
5
  Home-page: https://github.com/aifabrix/miso-client-python
6
6
  Author: AI Fabrix Team
@@ -80,10 +80,13 @@ The **AI Fabrix Miso Client SDK** provides authentication, authorization, and lo
80
80
  ### 📊 Compliance & Audit
81
81
 
82
82
  **ISO 27001 Compliance**
83
- - Comprehensive audit trails for all user actions
83
+ - Comprehensive audit trails for all user actions and HTTP requests
84
+ - Automatic data masking for all sensitive information in logs
85
+ - HTTP request/response audit logging with masked sensitive data
84
86
  - Data access logging and monitoring
85
87
  - Security event tracking
86
88
  - Accountability and non-repudiation
89
+ - Configurable sensitive fields via JSON configuration
87
90
 
88
91
  **Regulatory Compliance**
89
92
  - GDPR-ready data protection
@@ -134,9 +137,13 @@ The **AI Fabrix Miso Client SDK** provides authentication, authorization, and lo
134
137
 
135
138
  **Observability**
136
139
  - Centralized logging with correlation IDs
140
+ - Automatic HTTP request/response audit logging (ISO 27001 compliant)
141
+ - Debug logging with detailed request/response information (when `log_level='debug'`)
137
142
  - Performance tracking and metrics
138
143
  - Error tracking and debugging
139
144
  - Health monitoring
145
+ - Automatic data masking for sensitive information in logs
146
+ - Configurable sensitive fields via JSON configuration
140
147
 
141
148
  ---
142
149
 
@@ -262,7 +269,7 @@ if is_admin:
262
269
 
263
270
  ### Step 5: Activate Logging
264
271
 
265
- **What happens:** Application logs are sent to the Miso Controller with client token authentication.
272
+ **What happens:** Application logs are sent to the Miso Controller with client token authentication. All HTTP requests are automatically audited with ISO 27001 compliant data masking.
266
273
 
267
274
  ```python
268
275
  from miso_client import MisoClient, load_config
@@ -278,10 +285,17 @@ user = await client.get_user(token)
278
285
  await client.log.info('User accessed dashboard', {'userId': user.id if user else None})
279
286
  await client.log.error('Operation failed', {'error': str(err)})
280
287
  await client.log.warn('Unusual activity', {'details': '...'})
288
+
289
+ # HTTP requests are automatically audited
290
+ # All sensitive data is automatically masked before logging
291
+ result = await client.http_client.get('/api/users')
292
+ # This automatically creates an audit log: http.request.GET with masked sensitive data
281
293
  ```
282
294
 
283
295
  **What happens to logs?** They're sent to the Miso Controller for centralized monitoring and analysis. Client token is automatically included.
284
296
 
297
+ **ISO 27001 Compliance:** All HTTP requests are automatically audited with sensitive data masked. Set `log_level='debug'` to enable detailed request/response logging (all sensitive data is still masked).
298
+
285
299
  → [Complete logging example](examples/step-5-logging.py)
286
300
  → [Logging Reference](docs/api-reference.md#logger-service)
287
301
 
@@ -414,6 +428,7 @@ config = MisoClientConfig(
414
428
  port=6379,
415
429
  ),
416
430
  log_level="info", # Optional: 'debug' | 'info' | 'warn' | 'error'
431
+ # Set to 'debug' for detailed HTTP request/response logging
417
432
  api_key="your-test-api-key", # Optional: API key for testing (bypasses OAuth2)
418
433
  cache={ # Optional: Cache TTL settings
419
434
  "role_ttl": 900, # Role cache TTL (default: 900s)
@@ -424,6 +439,18 @@ config = MisoClientConfig(
424
439
 
425
440
  **Recommended:** Use `load_config()` to load from `.env` file automatically.
426
441
 
442
+ **ISO 27001 Data Masking Configuration:**
443
+
444
+ Sensitive fields are configured via `miso_client/utils/sensitive_fields_config.json`. You can customize this by:
445
+
446
+ 1. Setting `MISO_SENSITIVE_FIELDS_CONFIG` environment variable to point to a custom JSON file
447
+ 2. Using `DataMasker.set_config_path()` to set a custom path programmatically
448
+
449
+ The default configuration includes ISO 27001 compliant sensitive fields:
450
+ - Authentication: password, token, secret, key, auth, authorization
451
+ - PII: ssn, creditcard, cc, cvv, pin, otp
452
+ - Security: apikey, accesstoken, refreshtoken, privatekey, secretkey, cookie, session
453
+
427
454
  → [Complete Configuration Reference](docs/configuration.md)
428
455
 
429
456
  ---
@@ -448,6 +475,28 @@ The SDK consists of five core services:
448
475
  - **LoggerService** - Centralized logging with API key authentication
449
476
  - **RedisService** - Caching and queue management (optional)
450
477
 
478
+ ### HTTP Client Architecture
479
+
480
+ The SDK uses a two-layer HTTP client architecture for ISO 27001 compliance:
481
+
482
+ - **InternalHttpClient** - Core HTTP functionality with automatic client token management (internal)
483
+ - **HttpClient** - Public wrapper that adds automatic ISO 27001 compliant audit and debug logging
484
+
485
+ **Features:**
486
+ - Automatic audit logging for all HTTP requests (`http.request.{METHOD}`)
487
+ - Debug logging when `log_level === 'debug'` with detailed request/response information
488
+ - Automatic data masking using `DataMasker` before logging (ISO 27001 compliant)
489
+ - Sensitive endpoints (`/api/logs`, `/api/auth/token`) are excluded from audit logging to prevent infinite loops
490
+ - All sensitive data (headers, bodies, query params) is automatically masked before logging
491
+
492
+ **ISO 27001 Compliance:**
493
+ - All request headers are masked (Authorization, x-client-token, Cookie, etc.)
494
+ - All request bodies are recursively masked for sensitive fields (password, token, secret, SSN, etc.)
495
+ - All response bodies are masked (limited to first 1000 characters)
496
+ - Query parameters are automatically masked
497
+ - Error messages are masked if they contain sensitive data
498
+ - Sensitive fields configuration can be customized via `sensitive_fields_config.json`
499
+
451
500
  → [Architecture Details](docs/api-reference.md#architecture)
452
501
 
453
502
  ---
@@ -485,6 +534,302 @@ The SDK consists of five core services:
485
534
  - [Flask Decorators](docs/examples.md#flask-decorators) - Decorator-based auth
486
535
  - [Error Handling](docs/examples.md#error-handling) - Best practices
487
536
 
537
+ ---
538
+
539
+ ### Structured Error Responses
540
+
541
+ **What happens:** The SDK automatically parses structured error responses from the API (RFC 7807-style format) and makes them available through the `MisoClientError` exception.
542
+
543
+ ```python
544
+ from miso_client import MisoClient, MisoClientError, ErrorResponse, load_config
545
+
546
+ client = MisoClient(load_config())
547
+ await client.initialize()
548
+
549
+ try:
550
+ result = await client.http_client.get("/api/some-endpoint")
551
+ except MisoClientError as e:
552
+ # Check if structured error response is available
553
+ if e.error_response:
554
+ print(f"Error Type: {e.error_response.type}")
555
+ print(f"Error Title: {e.error_response.title}")
556
+ print(f"Status Code: {e.error_response.statusCode}")
557
+ print(f"Errors: {e.error_response.errors}")
558
+ print(f"Instance: {e.error_response.instance}")
559
+ else:
560
+ # Fallback to traditional error handling
561
+ print(f"Error: {e.message}")
562
+ print(f"Status Code: {e.status_code}")
563
+ print(f"Error Body: {e.error_body}")
564
+ ```
565
+
566
+ **Error Response Structure:**
567
+
568
+ The `ErrorResponse` model follows RFC 7807-style format:
569
+
570
+ ```json
571
+ {
572
+ "errors": [
573
+ "The user has provided input that the browser is unable to convert.",
574
+ "There are multiple rows in the database for the same value"
575
+ ],
576
+ "type": "/Errors/Bad Input",
577
+ "title": "Bad Request",
578
+ "statusCode": 400,
579
+ "instance": "/OpenApi/rest/Xzy"
580
+ }
581
+ ```
582
+
583
+ **Features:**
584
+
585
+ - **Automatic Parsing**: Structured error responses are automatically parsed from HTTP responses
586
+ - **Backward Compatible**: Falls back to traditional error handling when structured format is not available
587
+ - **Type Safety**: Full type hints with Pydantic models for reliable error handling
588
+ - **Generic Interface**: `ErrorResponse` model can be reused across different applications
589
+ - **Instance URI**: Automatically extracted from request URL if not provided in response
590
+
591
+ **Using ErrorResponse directly:**
592
+
593
+ ```python
594
+ from miso_client import ErrorResponse
595
+
596
+ # Create ErrorResponse from dict
597
+ error_data = {
598
+ "errors": ["Validation failed"],
599
+ "type": "/Errors/Validation",
600
+ "title": "Validation Error",
601
+ "statusCode": 422,
602
+ "instance": "/api/endpoint"
603
+ }
604
+ error_response = ErrorResponse(**error_data)
605
+
606
+ # Access fields
607
+ print(error_response.errors) # ["Validation failed"]
608
+ print(error_response.type) # "/Errors/Validation"
609
+ print(error_response.title) # "Validation Error"
610
+ print(error_response.statusCode) # 422
611
+ print(error_response.instance) # "/api/endpoint"
612
+ ```
613
+
614
+ ---
615
+
616
+ ### Pagination, Filtering, and Sorting Utilities
617
+
618
+ **What happens:** The SDK provides reusable utilities for pagination, filtering, sorting, and error handling that work with any API endpoint.
619
+
620
+ #### Pagination
621
+
622
+ **Pagination Parameters:**
623
+ - `page`: Page number (1-based, defaults to 1)
624
+ - `page_size`: Number of items per page (defaults to 25)
625
+
626
+ ```python
627
+ from miso_client import (
628
+ parse_pagination_params,
629
+ create_paginated_list_response,
630
+ PaginatedListResponse,
631
+ )
632
+
633
+ # Parse pagination from query parameters
634
+ params = {"page": "1", "page_size": "25"}
635
+ current_page, page_size = parse_pagination_params(params)
636
+
637
+ # Create paginated response
638
+ items = [{"id": 1}, {"id": 2}]
639
+ response = create_paginated_list_response(
640
+ items,
641
+ total_items=120,
642
+ current_page=1,
643
+ page_size=25,
644
+ type="item"
645
+ )
646
+
647
+ # Response structure:
648
+ # {
649
+ # "meta": {
650
+ # "total_items": 120,
651
+ # "current_page": 1,
652
+ # "page_size": 25,
653
+ # "type": "item"
654
+ # },
655
+ # "data": [{"id": 1}, {"id": 2}]
656
+ # }
657
+ ```
658
+
659
+ #### Filtering
660
+
661
+ **Filter Operators:** `eq`, `neq`, `in`, `nin`, `gt`, `lt`, `gte`, `lte`, `contains`, `like`
662
+
663
+ **Filter Format:** `field:op:value` (e.g., `status:eq:active`)
664
+
665
+ ```python
666
+ from miso_client import FilterBuilder, parse_filter_params, build_query_string
667
+
668
+ # Dynamic filter building with FilterBuilder
669
+ filter_builder = FilterBuilder() \
670
+ .add('status', 'eq', 'active') \
671
+ .add('region', 'in', ['eu', 'us']) \
672
+ .add('created_at', 'gte', '2024-01-01')
673
+
674
+ # Get query string
675
+ query_string = filter_builder.to_query_string()
676
+ # Returns: "filter=status:eq:active&filter=region:in:eu,us&filter=created_at:gte:2024-01-01"
677
+
678
+ # Parse existing filter parameters
679
+ params = {'filter': ['status:eq:active', 'region:in:eu,us']}
680
+ filters = parse_filter_params(params)
681
+ # Returns: [FilterOption(field='status', op='eq', value='active'), ...]
682
+
683
+ # Use with HTTP client
684
+ response = await client.http_client.get_with_filters(
685
+ '/api/items',
686
+ filter_builder=filter_builder
687
+ )
688
+ ```
689
+
690
+ **Building Complete Filter Queries:**
691
+
692
+ ```python
693
+ from miso_client import FilterQuery, FilterOption, build_query_string
694
+
695
+ # Create filter query with filters, sort, pagination, and fields
696
+ filter_query = FilterQuery(
697
+ filters=[
698
+ FilterOption(field='status', op='eq', value='active'),
699
+ FilterOption(field='region', op='in', value=['eu', 'us'])
700
+ ],
701
+ sort=['-updated_at', 'created_at'],
702
+ page=1,
703
+ page_size=25,
704
+ fields=['id', 'name', 'status']
705
+ )
706
+
707
+ # Build query string
708
+ query_string = build_query_string(filter_query)
709
+ ```
710
+
711
+ #### Sorting
712
+
713
+ **Sort Format:** `-field` for descending, `field` for ascending (e.g., `-updated_at`, `created_at`)
714
+
715
+ ```python
716
+ from miso_client import parse_sort_params, build_sort_string, SortOption
717
+
718
+ # Parse sort parameters
719
+ params = {'sort': '-updated_at'}
720
+ sort_options = parse_sort_params(params)
721
+ # Returns: [SortOption(field='updated_at', order='desc')]
722
+
723
+ # Parse multiple sorts
724
+ params = {'sort': ['-updated_at', 'created_at']}
725
+ sort_options = parse_sort_params(params)
726
+ # Returns: [
727
+ # SortOption(field='updated_at', order='desc'),
728
+ # SortOption(field='created_at', order='asc')
729
+ # ]
730
+
731
+ # Build sort string
732
+ sort_options = [
733
+ SortOption(field='updated_at', order='desc'),
734
+ SortOption(field='created_at', order='asc')
735
+ ]
736
+ sort_string = build_sort_string(sort_options)
737
+ # Returns: "-updated_at,created_at"
738
+ ```
739
+
740
+ #### Combined Usage
741
+
742
+ **Pagination + Filter + Sort:**
743
+
744
+ ```python
745
+ from miso_client import (
746
+ FilterBuilder,
747
+ FilterQuery,
748
+ build_query_string,
749
+ parse_pagination_params,
750
+ )
751
+
752
+ # Build filters
753
+ filter_builder = FilterBuilder() \
754
+ .add('status', 'eq', 'active') \
755
+ .add('region', 'in', ['eu', 'us'])
756
+
757
+ # Parse pagination
758
+ params = {'page': '1', 'page_size': '25'}
759
+ current_page, page_size = parse_pagination_params(params)
760
+
761
+ # Create complete query
762
+ filter_query = FilterQuery(
763
+ filters=filter_builder.build(),
764
+ sort=['-updated_at'],
765
+ page=current_page,
766
+ page_size=page_size
767
+ )
768
+
769
+ # Build query string
770
+ query_string = build_query_string(filter_query)
771
+
772
+ # Use with HTTP client
773
+ response = await client.http_client.get_with_filters(
774
+ '/api/items',
775
+ filter_builder=filter_builder,
776
+ params={'page': current_page, 'page_size': page_size}
777
+ )
778
+ ```
779
+
780
+ **Or use pagination helper:**
781
+
782
+ ```python
783
+ # Get paginated response
784
+ response = await client.http_client.get_paginated(
785
+ '/api/items',
786
+ page=1,
787
+ page_size=25
788
+ )
789
+
790
+ # Response is automatically parsed as PaginatedListResponse
791
+ print(response.meta.total_items) # 120
792
+ print(response.meta.current_page) # 1
793
+ print(len(response.data)) # 25
794
+ ```
795
+
796
+ #### Metadata Filter Integration
797
+
798
+ **Working with `/metadata/filter` endpoint:**
799
+
800
+ ```python
801
+ # Get metadata filters from endpoint
802
+ metadata_response = await client.http_client.post(
803
+ "/api/v1/metadata/filter",
804
+ {"documentStorageKey": "my-doc-storage"}
805
+ )
806
+
807
+ # Convert AccessFieldFilter to FilterBuilder
808
+ filter_builder = FilterBuilder()
809
+ for access_filter in metadata_response.mandatoryFilters:
810
+ filter_builder.add(access_filter.field, 'in', access_filter.values)
811
+
812
+ # Use with query utilities
813
+ query_string = filter_builder.to_query_string()
814
+
815
+ # Apply to API requests
816
+ response = await client.http_client.get_with_filters(
817
+ '/api/items',
818
+ filter_builder=filter_builder
819
+ )
820
+ ```
821
+
822
+ **Features:**
823
+
824
+ - **Snake_case Convention**: All utilities use snake_case to match Miso/Dataplane API
825
+ - **Type Safety**: Full type hints with Pydantic models
826
+ - **Dynamic Filtering**: FilterBuilder supports method chaining for complex filters
827
+ - **Local Testing**: `apply_filters()` and `apply_pagination_to_array()` for local filtering/pagination in tests
828
+ - **URL Encoding**: Automatic URL encoding for field names and values
829
+ - **Backward Compatible**: Works alongside existing HTTP client methods
830
+
831
+ ---
832
+
488
833
  ### Common Tasks
489
834
 
490
835
  **Add authentication middleware (FastAPI):**
@@ -0,0 +1,33 @@
1
+ miso_client/__init__.py,sha256=iDpRsYBbBDrLQFNJdtDlv4yrg1pvwNZpD2C3TYAyOSo,15595
2
+ miso_client/errors.py,sha256=uyS5j-_bUCA5gbINPYQd0wMpGsaEH0tJRK0obQTq2oo,1976
3
+ miso_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ miso_client/models/__init__.py,sha256=lMnzU7j2Z5_UehvOeIrbJIo3MH4j5KINGfU1vJTzTyU,139
5
+ miso_client/models/config.py,sha256=TLckmwajrdnJpMTsjCtaFkz8wFAXcEQJfKjXQ8ww6vM,8024
6
+ miso_client/models/error_response.py,sha256=YBQkchdWOo6NjdNpLlOvGaIhOwA1MJzkTS2oeU0e2Mk,1694
7
+ miso_client/models/filter.py,sha256=3XWVxAjtUP78KPocd4-Ko9SijeRXm3jWVVBCXDPnXuI,4111
8
+ miso_client/models/pagination.py,sha256=f-YbspLrQH3ZhDYzbMCTloeKF2EyaSi7zjFLgh6dhBQ,2078
9
+ miso_client/models/sort.py,sha256=CutVQnYC4uRgesJ8zgd4OmPGZIop1KzvtgiTjNproSM,621
10
+ miso_client/services/__init__.py,sha256=2ok62Z9kaS0Zze-OxRkxEJ4JidzN9jL_pzGMOxpZppQ,489
11
+ miso_client/services/auth.py,sha256=hYnHEoTNgeH_g0ItoVDq99fBZCZX0o-0o-9gRxqZYmw,5465
12
+ miso_client/services/cache.py,sha256=lXcLcRm56snOX3AQiEKi-j7FIikCLYnEK2HXxBkxm2M,6510
13
+ miso_client/services/encryption.py,sha256=8512ACLt0P8t3W_CGzmG0JRSvzDinvjPRvkifdDxIRs,3022
14
+ miso_client/services/logger.py,sha256=dopKQeBZpz9S3iX9vw8xcPsS1oyMASOUQ4-OvCdElTw,16772
15
+ miso_client/services/permission.py,sha256=GOGRAaXSfPKTXqEqbR0XP8hrA-YQgVFbHPxD-gGBUyA,6853
16
+ miso_client/services/redis.py,sha256=BWfgXoSOyyGrB9cf_kTY0lZwfbjUWQhkiOyFFSp590M,5348
17
+ miso_client/services/role.py,sha256=qmxhk54QUGCjCuCWm_ruDlTq7iT9yOicfUvdRVkFKUI,5517
18
+ miso_client/utils/__init__.py,sha256=HArSxVKrmCqFkqFOPwe1i3B2IBHJ1vRqYu98c_KASV0,366
19
+ miso_client/utils/config_loader.py,sha256=yZk4pXNIBu3i61KqxM8QwsjraM0xhqUcH2THl8-DMu0,3027
20
+ miso_client/utils/data_masker.py,sha256=D7AEyViGxoShLa5UUZHYhRCPQMPKqX7qNilTK9h87OM,7035
21
+ miso_client/utils/error_utils.py,sha256=B-BXmDTTj6II1HwjC_DE6o9QKOMs6B3bzFXkOiPhf20,3274
22
+ miso_client/utils/filter.py,sha256=obkdQ3FFD69Qg0VviFMoM8y6zAP5JYBaIujmOT9diU8,8984
23
+ miso_client/utils/http_client.py,sha256=H386RsKjiw-JG7XhrTn8gCRDMWMvp8s7bC1jqa7qvGk,23807
24
+ miso_client/utils/internal_http_client.py,sha256=jgiaO94EiIUbMQWUKN4FhYqOQ9r0BZea0_grRcOepL4,16078
25
+ miso_client/utils/jwt_tools.py,sha256=-pvz5nk5BztEnhFnL-dtOv8Q5E0G2oh4RwFrVk2rVpg,1981
26
+ miso_client/utils/pagination.py,sha256=_sUzAGSCiUujVa9W3nedVRaCx18_9MuT_9bBlWmVc9o,4449
27
+ miso_client/utils/sensitive_fields_loader.py,sha256=EHODxyM1Gw7hgKXCvJ1B4Hf4LZqcEqWEXu4q5CPFaic,3667
28
+ miso_client/utils/sort.py,sha256=_bCLRCGevgNkXYqsgXasc3vaDQwkXV76kgHIyw-dato,3438
29
+ miso_client-0.5.0.dist-info/licenses/LICENSE,sha256=3hoU8LdT9_EIFIx6FjMk5sQnVCBMX3FRIOzqqy5im4c,1076
30
+ miso_client-0.5.0.dist-info/METADATA,sha256=ZzmZZNHxnv4n3P95ZJgMAPFKRE3gLTgYUhCG9sh20C4,28396
31
+ miso_client-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
+ miso_client-0.5.0.dist-info/top_level.txt,sha256=8i_FNeRn8PRy6scnXOpVr-IJYsArkqIvxRMTZPtik9E,12
33
+ miso_client-0.5.0.dist-info/RECORD,,