d365fo-client 0.2.3__py3-none-any.whl → 0.3.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.
- d365fo_client/__init__.py +7 -1
- d365fo_client/auth.py +9 -21
- d365fo_client/cli.py +25 -13
- d365fo_client/client.py +8 -4
- d365fo_client/config.py +52 -30
- d365fo_client/credential_sources.py +5 -0
- d365fo_client/main.py +1 -1
- d365fo_client/mcp/__init__.py +3 -1
- d365fo_client/mcp/auth_server/__init__.py +5 -0
- d365fo_client/mcp/auth_server/auth/__init__.py +30 -0
- d365fo_client/mcp/auth_server/auth/auth.py +372 -0
- d365fo_client/mcp/auth_server/auth/oauth_proxy.py +989 -0
- d365fo_client/mcp/auth_server/auth/providers/__init__.py +0 -0
- d365fo_client/mcp/auth_server/auth/providers/azure.py +325 -0
- d365fo_client/mcp/auth_server/auth/providers/bearer.py +25 -0
- d365fo_client/mcp/auth_server/auth/providers/jwt.py +547 -0
- d365fo_client/mcp/auth_server/auth/redirect_validation.py +65 -0
- d365fo_client/mcp/auth_server/dependencies.py +136 -0
- d365fo_client/mcp/client_manager.py +16 -67
- d365fo_client/mcp/fastmcp_main.py +358 -0
- d365fo_client/mcp/fastmcp_server.py +598 -0
- d365fo_client/mcp/fastmcp_utils.py +431 -0
- d365fo_client/mcp/main.py +40 -13
- d365fo_client/mcp/mixins/__init__.py +24 -0
- d365fo_client/mcp/mixins/base_tools_mixin.py +55 -0
- d365fo_client/mcp/mixins/connection_tools_mixin.py +50 -0
- d365fo_client/mcp/mixins/crud_tools_mixin.py +311 -0
- d365fo_client/mcp/mixins/database_tools_mixin.py +685 -0
- d365fo_client/mcp/mixins/label_tools_mixin.py +87 -0
- d365fo_client/mcp/mixins/metadata_tools_mixin.py +565 -0
- d365fo_client/mcp/mixins/performance_tools_mixin.py +109 -0
- d365fo_client/mcp/mixins/profile_tools_mixin.py +713 -0
- d365fo_client/mcp/mixins/sync_tools_mixin.py +321 -0
- d365fo_client/mcp/prompts/action_execution.py +1 -1
- d365fo_client/mcp/prompts/sequence_analysis.py +1 -1
- d365fo_client/mcp/tools/crud_tools.py +3 -3
- d365fo_client/mcp/tools/sync_tools.py +1 -1
- d365fo_client/mcp/utilities/__init__.py +1 -0
- d365fo_client/mcp/utilities/auth.py +34 -0
- d365fo_client/mcp/utilities/logging.py +58 -0
- d365fo_client/mcp/utilities/types.py +426 -0
- d365fo_client/metadata_v2/sync_manager_v2.py +2 -0
- d365fo_client/metadata_v2/sync_session_manager.py +7 -7
- d365fo_client/models.py +139 -139
- d365fo_client/output.py +2 -2
- d365fo_client/profile_manager.py +62 -27
- d365fo_client/profiles.py +118 -113
- d365fo_client/settings.py +355 -0
- d365fo_client/sync_models.py +85 -2
- d365fo_client/utils.py +2 -1
- {d365fo_client-0.2.3.dist-info → d365fo_client-0.3.0.dist-info}/METADATA +1261 -810
- d365fo_client-0.3.0.dist-info/RECORD +84 -0
- d365fo_client-0.3.0.dist-info/entry_points.txt +4 -0
- d365fo_client-0.2.3.dist-info/RECORD +0 -56
- d365fo_client-0.2.3.dist-info/entry_points.txt +0 -3
- {d365fo_client-0.2.3.dist-info → d365fo_client-0.3.0.dist-info}/WHEEL +0 -0
- {d365fo_client-0.2.3.dist-info → d365fo_client-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {d365fo_client-0.2.3.dist-info → d365fo_client-0.3.0.dist-info}/top_level.txt +0 -0
d365fo_client/sync_models.py
CHANGED
@@ -4,9 +4,20 @@ import uuid
|
|
4
4
|
from datetime import datetime, timezone
|
5
5
|
from dataclasses import dataclass, field
|
6
6
|
from enum import StrEnum
|
7
|
-
from typing import Dict, List, Optional,
|
7
|
+
from typing import Dict, List, Optional, Set
|
8
8
|
|
9
|
-
|
9
|
+
# Removed import to avoid circular dependency - models will be defined here
|
10
|
+
|
11
|
+
|
12
|
+
class SyncStrategy(StrEnum):
|
13
|
+
"""Metadata synchronization strategies"""
|
14
|
+
|
15
|
+
FULL = "full"
|
16
|
+
INCREMENTAL = "incremental"
|
17
|
+
ENTITIES_ONLY = "entities_only"
|
18
|
+
LABELS_ONLY = "labels_only"
|
19
|
+
SHARING_MODE = "sharing_mode"
|
20
|
+
FULL_WITHOUT_LABELS = "full_without_labels"
|
10
21
|
|
11
22
|
|
12
23
|
class SyncStatus(StrEnum):
|
@@ -33,6 +44,78 @@ class SyncPhase(StrEnum):
|
|
33
44
|
FAILED = "failed"
|
34
45
|
|
35
46
|
|
47
|
+
@dataclass
|
48
|
+
class SyncResult:
|
49
|
+
"""Enhanced synchronization result with sharing metrics"""
|
50
|
+
|
51
|
+
sync_type: str # full|incremental|linked|skipped|failed
|
52
|
+
entities_synced: int = 0
|
53
|
+
actions_synced: int = 0
|
54
|
+
enumerations_synced: int = 0
|
55
|
+
labels_synced: int = 0
|
56
|
+
duration_ms: float = 0.0
|
57
|
+
success: bool = True
|
58
|
+
errors: List[str] = field(default_factory=list)
|
59
|
+
|
60
|
+
# Enhanced metrics for V2
|
61
|
+
entities_shared: int = 0
|
62
|
+
actions_shared: int = 0
|
63
|
+
enumerations_shared: int = 0
|
64
|
+
labels_shared: int = 0
|
65
|
+
cache_hit_rate: float = 0.0
|
66
|
+
sharing_efficiency: float = 0.0 # Percentage of items shared vs downloaded
|
67
|
+
source_version_id: Optional[int] = None # Version we shared from
|
68
|
+
|
69
|
+
def to_dict(self) -> dict:
|
70
|
+
"""Convert to dictionary for JSON serialization"""
|
71
|
+
return {
|
72
|
+
"sync_type": self.sync_type,
|
73
|
+
"entities_synced": self.entities_synced,
|
74
|
+
"actions_synced": self.actions_synced,
|
75
|
+
"enumerations_synced": self.enumerations_synced,
|
76
|
+
"labels_synced": self.labels_synced,
|
77
|
+
"duration_ms": self.duration_ms,
|
78
|
+
"success": self.success,
|
79
|
+
"errors": self.errors,
|
80
|
+
"entities_shared": self.entities_shared,
|
81
|
+
"actions_shared": self.actions_shared,
|
82
|
+
"enumerations_shared": self.enumerations_shared,
|
83
|
+
"labels_shared": self.labels_shared,
|
84
|
+
"cache_hit_rate": self.cache_hit_rate,
|
85
|
+
"sharing_efficiency": self.sharing_efficiency,
|
86
|
+
"source_version_id": self.source_version_id
|
87
|
+
}
|
88
|
+
|
89
|
+
|
90
|
+
@dataclass
|
91
|
+
class SyncProgress:
|
92
|
+
"""Sync progress tracking"""
|
93
|
+
|
94
|
+
global_version_id: int
|
95
|
+
strategy: SyncStrategy
|
96
|
+
phase: str
|
97
|
+
total_steps: int
|
98
|
+
completed_steps: int
|
99
|
+
current_operation: str
|
100
|
+
start_time: datetime
|
101
|
+
estimated_completion: Optional[datetime] = None
|
102
|
+
error: Optional[str] = None
|
103
|
+
|
104
|
+
def to_dict(self) -> dict:
|
105
|
+
"""Convert to dictionary for JSON serialization"""
|
106
|
+
return {
|
107
|
+
"global_version_id": self.global_version_id,
|
108
|
+
"strategy": self.strategy,
|
109
|
+
"phase": self.phase,
|
110
|
+
"total_steps": self.total_steps,
|
111
|
+
"completed_steps": self.completed_steps,
|
112
|
+
"current_operation": self.current_operation,
|
113
|
+
"start_time": self.start_time.isoformat() if self.start_time else None,
|
114
|
+
"estimated_completion": self.estimated_completion.isoformat() if self.estimated_completion else None,
|
115
|
+
"error": self.error
|
116
|
+
}
|
117
|
+
|
118
|
+
|
36
119
|
@dataclass
|
37
120
|
class SyncActivity:
|
38
121
|
"""Individual sync activity within a phase"""
|
d365fo_client/utils.py
CHANGED
@@ -38,7 +38,8 @@ def get_user_cache_dir(app_name: str = "d365fo-client") -> Path:
|
|
38
38
|
# Falls back to APPDATA if LOCALAPPDATA is not available
|
39
39
|
cache_root = os.environ.get("LOCALAPPDATA") or os.environ.get("APPDATA")
|
40
40
|
if cache_root:
|
41
|
-
|
41
|
+
# Normalize path separators for consistency
|
42
|
+
return Path(cache_root.replace("\\", "/")) / app_name
|
42
43
|
else:
|
43
44
|
# Fallback: use user home directory
|
44
45
|
return Path.home() / "AppData" / "Local" / app_name
|