d365fo-client 0.1.0__py3-none-any.whl → 0.2.1__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.
@@ -0,0 +1,107 @@
1
+ """Label processing utilities for metadata v2.
2
+
3
+ This module provides utilities for processing D365 Finance & Operations labels
4
+ with fallback logic for when label_id doesn't start with '@'.
5
+
6
+ The main principle is:
7
+ - If label_text is already set, preserve it
8
+ - If label_id doesn't start with '@', it's already display text - use it as label_text
9
+ - If label_id starts with '@', it's a label reference that needs API resolution
10
+
11
+ This ensures that direct text labels (like "Customer Groups") are properly
12
+ converted to label_text, while label references (like "@SYS123") are preserved
13
+ for later resolution through the label API.
14
+
15
+ Functions:
16
+ process_label_fallback: Core label processing logic
17
+ process_data_entity_labels: Process DataEntityInfo labels
18
+ process_public_entity_labels: Process PublicEntityInfo and property labels
19
+ process_enumeration_labels: Process EnumerationInfo and member labels
20
+ apply_label_fallback: Alias for process_label_fallback (retrieval context)
21
+ """
22
+
23
+ from typing import Optional
24
+
25
+ from ..models import DataEntityInfo, EnumerationInfo, PublicEntityInfo
26
+
27
+
28
+ def process_label_fallback(label_id: Optional[str], label_text: Optional[str]) -> Optional[str]:
29
+ """Process label text with fallback to label_id when it doesn't start with '@'
30
+
31
+ This function implements the core logic for handling D365 F&O labels where:
32
+ - If label_text is already set, use it
33
+ - If label_id doesn't start with '@', it's already display text
34
+ - If label_id starts with '@', it needs resolution via label API
35
+
36
+ Args:
37
+ label_id: Label identifier (may be actual text or @-prefixed ID)
38
+ label_text: Existing label text (may be None)
39
+
40
+ Returns:
41
+ Processed label text with fallback applied
42
+ """
43
+ # If label_text is already set, use it
44
+ if label_text:
45
+ return label_text
46
+
47
+ # If label_id doesn't start with '@', it's already the text
48
+ if label_id and not label_id.startswith('@'):
49
+ return label_id
50
+
51
+ # Return None for @-prefixed labels without resolved text
52
+ return None
53
+
54
+
55
+ def process_data_entity_labels(entity: DataEntityInfo) -> DataEntityInfo:
56
+ """Process data entity labels with fallback logic
57
+
58
+ Args:
59
+ entity: Data entity info to process
60
+
61
+ Returns:
62
+ Processed data entity info with label fallback applied
63
+ """
64
+ entity.label_text = process_label_fallback(entity.label_id, entity.label_text)
65
+ return entity
66
+
67
+
68
+ def process_public_entity_labels(entity: PublicEntityInfo) -> PublicEntityInfo:
69
+ """Process public entity labels with fallback logic
70
+
71
+ Args:
72
+ entity: Public entity info to process
73
+
74
+ Returns:
75
+ Processed public entity info with label fallback applied
76
+ """
77
+ # Process entity label
78
+ entity.label_text = process_label_fallback(entity.label_id, entity.label_text)
79
+
80
+ # Process property labels
81
+ for prop in entity.properties:
82
+ prop.label_text = process_label_fallback(prop.label_id, prop.label_text)
83
+
84
+ return entity
85
+
86
+
87
+ def process_enumeration_labels(enumeration: EnumerationInfo) -> EnumerationInfo:
88
+ """Process enumeration labels with fallback logic
89
+
90
+ Args:
91
+ enumeration: Enumeration info to process
92
+
93
+ Returns:
94
+ Processed enumeration info with label fallback applied
95
+ """
96
+ # Process enumeration label
97
+ enumeration.label_text = process_label_fallback(enumeration.label_id, enumeration.label_text)
98
+
99
+ # Process member labels
100
+ for member in enumeration.members:
101
+ member.label_text = process_label_fallback(member.label_id, member.label_text)
102
+
103
+ return enumeration
104
+
105
+
106
+ # Alias for backward compatibility during data retrieval
107
+ apply_label_fallback = process_label_fallback
@@ -1,13 +1,11 @@
1
1
  """Smart sync manager with intelligent metadata synchronization strategies."""
2
2
 
3
- import asyncio
4
3
  import hashlib
5
4
  import json
6
5
  import logging
7
6
  import time
8
7
  from datetime import datetime, timezone
9
- from pathlib import Path
10
- from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set
8
+ from typing import TYPE_CHECKING, Callable, Dict, List, Optional
11
9
 
12
10
  # Use TYPE_CHECKING to avoid circular import
13
11
  if TYPE_CHECKING:
@@ -19,7 +17,6 @@ from ..models import (
19
17
  LabelInfo,
20
18
  MetadataVersionInfo,
21
19
  PublicEntityInfo,
22
- QueryOptions,
23
20
  SyncProgress,
24
21
  SyncResult,
25
22
  SyncStrategy,
@@ -509,14 +506,20 @@ class SmartSyncManagerV2:
509
506
  counts = {}
510
507
 
511
508
  async with aiosqlite.connect(self.cache.db_path) as db:
512
- # Copy data entities
509
+ # Copy data entities with label processing
513
510
  await db.execute(
514
511
  """INSERT INTO data_entities
515
512
  (global_version_id, name, public_entity_name, public_collection_name,
516
513
  label_id, label_text, entity_category, data_service_enabled,
517
514
  data_management_enabled, is_read_only)
518
515
  SELECT ?, name, public_entity_name, public_collection_name,
519
- label_id, label_text, entity_category, data_service_enabled,
516
+ label_id,
517
+ CASE
518
+ WHEN label_text IS NOT NULL AND label_text != '' THEN label_text
519
+ WHEN label_id IS NOT NULL AND label_id != '' AND NOT label_id LIKE '@%' THEN label_id
520
+ ELSE label_text
521
+ END as processed_label_text,
522
+ entity_category, data_service_enabled,
520
523
  data_management_enabled, is_read_only
521
524
  FROM data_entities
522
525
  WHERE global_version_id = ?""",
@@ -524,19 +527,90 @@ class SmartSyncManagerV2:
524
527
  )
525
528
  counts["entities"] = db.total_changes
526
529
 
527
- # Copy enumerations
530
+ # Copy enumerations with label processing
528
531
  await db.execute(
529
532
  """INSERT INTO enumerations
530
533
  (global_version_id, name, label_id, label_text)
531
- SELECT ?, name, label_id, label_text
534
+ SELECT ?, name, label_id,
535
+ CASE
536
+ WHEN label_text IS NOT NULL AND label_text != '' THEN label_text
537
+ WHEN label_id IS NOT NULL AND label_id != '' AND NOT label_id LIKE '@%' THEN label_id
538
+ ELSE label_text
539
+ END as processed_label_text
532
540
  FROM enumerations
533
541
  WHERE global_version_id = ?""",
534
542
  (target_version_id, source_version_id),
535
543
  )
536
544
  counts["enumerations"] = db.total_changes
537
545
 
546
+ # Copy public entities with label processing
547
+ await db.execute(
548
+ """INSERT INTO public_entities
549
+ (global_version_id, name, entity_set_name, label_id, label_text,
550
+ is_read_only, configuration_enabled)
551
+ SELECT ?, name, entity_set_name, label_id,
552
+ CASE
553
+ WHEN label_text IS NOT NULL AND label_text != '' THEN label_text
554
+ WHEN label_id IS NOT NULL AND label_id != '' AND NOT label_id LIKE '@%' THEN label_id
555
+ ELSE label_text
556
+ END as processed_label_text,
557
+ is_read_only, configuration_enabled
558
+ FROM public_entities
559
+ WHERE global_version_id = ?""",
560
+ (target_version_id, source_version_id),
561
+ )
562
+
563
+ # Copy entity properties with label processing
564
+ # Note: We need to get the new entity IDs for the relationships
565
+ await db.execute(
566
+ """INSERT INTO entity_properties
567
+ (entity_id, global_version_id, name, type_name, data_type,
568
+ odata_xpp_type, label_id, label_text, is_key, is_mandatory,
569
+ configuration_enabled, allow_edit, allow_edit_on_create,
570
+ is_dimension, dimension_relation, is_dynamic_dimension,
571
+ dimension_legal_entity_property, dimension_type_property,
572
+ property_order)
573
+ SELECT pe_new.id as entity_id, ?, ep.name, ep.type_name, ep.data_type,
574
+ ep.odata_xpp_type, ep.label_id,
575
+ CASE
576
+ WHEN ep.label_text IS NOT NULL AND ep.label_text != '' THEN ep.label_text
577
+ WHEN ep.label_id IS NOT NULL AND ep.label_id != '' AND NOT ep.label_id LIKE '@%' THEN ep.label_id
578
+ ELSE ep.label_text
579
+ END as processed_label_text,
580
+ ep.is_key, ep.is_mandatory,
581
+ ep.configuration_enabled, ep.allow_edit, ep.allow_edit_on_create,
582
+ ep.is_dimension, ep.dimension_relation, ep.is_dynamic_dimension,
583
+ ep.dimension_legal_entity_property, ep.dimension_type_property,
584
+ ep.property_order
585
+ FROM entity_properties ep
586
+ JOIN public_entities pe_old ON ep.entity_id = pe_old.id
587
+ JOIN public_entities pe_new ON pe_new.name = pe_old.name AND pe_new.global_version_id = ?
588
+ WHERE ep.global_version_id = ?""",
589
+ (target_version_id, target_version_id, source_version_id),
590
+ )
591
+
592
+ # Copy enumeration members with label processing
593
+ await db.execute(
594
+ """INSERT INTO enumeration_members
595
+ (enumeration_id, global_version_id, name, value,
596
+ label_id, label_text, configuration_enabled, member_order)
597
+ SELECT e_new.id as enumeration_id, ?, em.name, em.value,
598
+ em.label_id,
599
+ CASE
600
+ WHEN em.label_text IS NOT NULL AND em.label_text != '' THEN em.label_text
601
+ WHEN em.label_id IS NOT NULL AND em.label_id != '' AND NOT em.label_id LIKE '@%' THEN em.label_id
602
+ ELSE em.label_text
603
+ END as processed_label_text,
604
+ em.configuration_enabled, em.member_order
605
+ FROM enumeration_members em
606
+ JOIN enumerations e_old ON em.enumeration_id = e_old.id
607
+ JOIN enumerations e_new ON e_new.name = e_old.name AND e_new.global_version_id = ?
608
+ WHERE em.global_version_id = ?""",
609
+ (target_version_id, target_version_id, source_version_id),
610
+ )
611
+
538
612
  # Copy other metadata tables as needed...
539
- # This is a simplified implementation
613
+ # This is a more comprehensive implementation with label processing
540
614
 
541
615
  await db.commit()
542
616
 
d365fo_client/models.py CHANGED
@@ -221,7 +221,7 @@ class DataEntityInfo:
221
221
  "label_text": self.label_text,
222
222
  "data_service_enabled": self.data_service_enabled,
223
223
  "data_management_enabled": self.data_management_enabled,
224
- "entity_category": self.entity_category,
224
+ "entity_category": self.entity_category.value if self.entity_category else None,
225
225
  "is_read_only": self.is_read_only,
226
226
  }
227
227
 
@@ -503,7 +503,7 @@ class ActionInfo:
503
503
  def to_dict(self) -> Dict[str, Any]:
504
504
  return {
505
505
  "name": self.name,
506
- "binding_kind": self.binding_kind,
506
+ "binding_kind": self.binding_kind.value, # Serialize enum as string value
507
507
  "entity_name": self.entity_name,
508
508
  "entity_set_name": self.entity_set_name,
509
509
  "parameters": [param.to_dict() for param in self.parameters],
d365fo_client/output.py CHANGED
@@ -163,19 +163,19 @@ class OutputFormatter:
163
163
 
164
164
  def format_success_message(message: str) -> str:
165
165
  """Format a success message with checkmark."""
166
- return f" {message}"
166
+ return f"[OK] {message}"
167
167
 
168
168
 
169
169
  def format_error_message(message: str) -> str:
170
170
  """Format an error message with X mark."""
171
- return f" {message}"
171
+ return f"[ERROR] {message}"
172
172
 
173
173
 
174
174
  def format_info_message(message: str) -> str:
175
175
  """Format an info message with info icon."""
176
- return f"ℹ️ {message}"
176
+ return f"[INFO] {message}"
177
177
 
178
178
 
179
179
  def format_warning_message(message: str) -> str:
180
180
  """Format a warning message with warning icon."""
181
- return f"⚠️ {message}"
181
+ return f"[WARNING] {message}"
@@ -195,6 +195,18 @@ class ProfileManager:
195
195
  logger.error(f"Error getting default profile: {e}")
196
196
  return None
197
197
 
198
+ def reload_config(self) -> None:
199
+ """Reload configuration from file.
200
+
201
+ This is useful when profiles have been modified and we need
202
+ to refresh the in-memory configuration data.
203
+ """
204
+ try:
205
+ self.config_manager.reload_config()
206
+ logger.debug("Reloaded profile configuration")
207
+ except Exception as e:
208
+ logger.error(f"Error reloading configuration: {e}")
209
+
198
210
  def set_default_profile(self, profile_name: str) -> bool:
199
211
  """Set the default profile.
200
212
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: d365fo-client
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: Microsoft Dynamics 365 Finance & Operations client
5
5
  Author-email: Muhammad Afzaal <mo@thedataguy.pro>
6
6
  License-Expression: MIT
@@ -911,6 +911,8 @@ Add to your VS Code `mcp.json` for GitHub Copilot with MCP:
911
911
  "type": "stdio",
912
912
  "command": "uvx",
913
913
  "args": [
914
+ "--from",
915
+ "d365fo-client",
914
916
  "d365fo-mcp-server"
915
917
  ],
916
918
  "env": {
@@ -932,6 +934,8 @@ For environments requiring service principal authentication:
932
934
  "type": "stdio",
933
935
  "command": "uvx",
934
936
  "args": [
937
+ "--from",
938
+ "d365fo-client",
935
939
  "d365fo-mcp-server"
936
940
  ],
937
941
  "env": {
@@ -1,25 +1,25 @@
1
1
  d365fo_client/__init__.py,sha256=n9a6XtjxdOcwFIVWrAeQJpWRttu9V0dUYCQBQh8INZI,9692
2
2
  d365fo_client/auth.py,sha256=nLq1JGCTsuEuAt0a3f8rcjZkKANRqmMk7flR5MkDitI,2889
3
3
  d365fo_client/cli.py,sha256=fBF6OjbZgj0T--Sii1wazP2fIFZnZnEeznUSti3l194,26118
4
- d365fo_client/client.py,sha256=KG3je34XFc2rBEhn3WGROkovLFvD8LIQCRwqT-DBmQs,53169
5
- d365fo_client/config.py,sha256=BVvZ7qfLlOIMfIpvq4Z7Q2jyfSWFwE5pcTGaFzr6Jao,10193
4
+ d365fo_client/client.py,sha256=Ml5FThAkPNQYzdTEZ7BWg736_vZTu6tWHQfokeO7S9A,53898
5
+ d365fo_client/config.py,sha256=WQxXEHYhgXaM2Ocd98ILiAnJycevKArn1FNp-TGKdKA,10469
6
6
  d365fo_client/crud.py,sha256=YBjYIKqgyWYsLB8uRtI_sjRa2G7d9qtgm5mLGCB2CfA,6701
7
7
  d365fo_client/exceptions.py,sha256=k8tVb5K6C_F4sCY5NVoE5RYpKPjCgPLd3JtcLIuXzTw,733
8
- d365fo_client/labels.py,sha256=YEu3XIvU_TfyMWrXYPM9V9i3k_63la2S_Pc__OftNvE,18404
9
- d365fo_client/main.py,sha256=C-Cq-q9BOGRSeNIiO9xY3FqPmOb33AsBFY4jGsMjMsQ,19034
10
- d365fo_client/metadata_api.py,sha256=uX42xoh3A-n_DXY0InjdAxxGoTcy5Iqhpbp6nbqsbIg,28957
11
- d365fo_client/models.py,sha256=asBmmbi6xpchuIaZmeR7CT3oosULID__dRETw7XdhCk,26150
12
- d365fo_client/output.py,sha256=rm4XzCOFrWExIb-MSOSxsLdufw86aDjJWihkNzTnT7o,5980
13
- d365fo_client/profile_manager.py,sha256=VeBsfVgrlhD3O0l6ukg1b2rlFNhv1lRcjzunfiXZi5A,10906
8
+ d365fo_client/labels.py,sha256=a7b_YYWDWaXANirDAr-CoodEpBvfaZvRGy76kNg4Z44,18412
9
+ d365fo_client/main.py,sha256=6jBBcePWHOG-FMJ8HMz9Qz1Tmw486Ef6VGbfvK5oBHg,19045
10
+ d365fo_client/metadata_api.py,sha256=76WmWwViWYCBIlLRpT4nvbYo5_fY9pR52H7fceW4PSo,38577
11
+ d365fo_client/models.py,sha256=af6P-HFyG0PetVNREHoyW7FjQX-29ooZEncCH9Mto9U,26230
12
+ d365fo_client/output.py,sha256=U-q6_wRHARWUKEhK-OCu16VhgWZ89sofzZuE67hNg1Q,5986
13
+ d365fo_client/profile_manager.py,sha256=X8nMr0sTi_x8gEPtB--jl3xb9wjLpPj4JCpZpZltMOc,11344
14
14
  d365fo_client/profiles.py,sha256=cZg9WKO8wxRfIl6UjZ7k6pTqyw_r9EFWOUwf6QBt4aI,5759
15
15
  d365fo_client/query.py,sha256=wOZjXEtGzPcd5mRfdkpMZTHZdSId44gLSboJs4LeSaw,5028
16
16
  d365fo_client/session.py,sha256=4OIR61eYQHALPmbXZ446Ko4j5ttvozDxDRnMikogBys,1841
17
17
  d365fo_client/utils.py,sha256=9bHSWznuhuOmxbx9Vkd8k9RFctHYUCTiWZCmcUujPqw,6771
18
18
  d365fo_client/mcp/__init__.py,sha256=B6Pw342ejRUKrw0NN5zyMSb1lF2rTICxv8KFfBMlBsU,487
19
- d365fo_client/mcp/client_manager.py,sha256=_ldpH9mD8dPEjmrnhomhekA8mKC9roT4qfAhwsZnTNM,10525
20
- d365fo_client/mcp/main.py,sha256=f6zxIz0DoZPb0z_nIneTCbKy4CdN-KvQpI6J7uza2M8,2708
19
+ d365fo_client/mcp/client_manager.py,sha256=HpLjn85SnI6z-bQMWIxntMT7xUpQvMTR70Lis1ZzbEE,11705
20
+ d365fo_client/mcp/main.py,sha256=UtPRtUg4ZtNecl7m_d0leu9BPCXufSRNXinWmCLQcMs,3248
21
21
  d365fo_client/mcp/models.py,sha256=o32IlGsEqzJQa8t5K-7oxF2WDPV1QKxcTb7ihQKeWEI,7487
22
- d365fo_client/mcp/server.py,sha256=3FkS3uR2GAqG6TTeX2aQRuHkFCDtC7lEN1uSj-Wzu5k,18510
22
+ d365fo_client/mcp/server.py,sha256=yfNeNOIfFqhM3gYufMN-wjx6-Ntlt2QtMPodRSfIj4E,22249
23
23
  d365fo_client/mcp/prompts/__init__.py,sha256=haa0Cit3f2xWrcqlFkhfQTqff2DfAore_I0il2VIW_0,1104
24
24
  d365fo_client/mcp/prompts/action_execution.py,sha256=gdZcgtXHrn8fUBfk2758RGbxwAcu1qRODOxVqwLoeZA,15587
25
25
  d365fo_client/mcp/prompts/sequence_analysis.py,sha256=AMsb3boD0rdd4xUYrnEqvx_OTYcFIyo6acE9zMJzgOU,12662
@@ -35,17 +35,18 @@ d365fo_client/mcp/tools/crud_tools.py,sha256=YM6TDK41f7-xg9K4d4vs3t5vTwabtru9Z7g
35
35
  d365fo_client/mcp/tools/database_tools.py,sha256=Kh9g-e0RCTKPbmsuZ99MDK8-ZYaBB2czbElHDaJXPGU,33286
36
36
  d365fo_client/mcp/tools/label_tools.py,sha256=BpUI55MgDjhokN44JhZLh1J46JgiPL0Fh0pfhZrD5I0,6376
37
37
  d365fo_client/mcp/tools/metadata_tools.py,sha256=NHFrQbpL0403EA2mxLqqRE0q7MDPLroZ79JRb3gqxeQ,32260
38
- d365fo_client/mcp/tools/profile_tools.py,sha256=vGROwb3FLu8GjWQBq26Gjd3i4kkR9JQtnNVPneeSKqk,26337
38
+ d365fo_client/mcp/tools/profile_tools.py,sha256=N7-bLI9ZUg0T5GA9RhvBMAlUwrsBBQNVRNBKwJEtCCc,27039
39
39
  d365fo_client/metadata_v2/__init__.py,sha256=54VTuWSo5j-BEM-raLbRr3bfxc5lVXp2COajsy-7Oo0,1895
40
- d365fo_client/metadata_v2/cache_v2.py,sha256=L7iicUTcZpCt7YIHRZ1osUay_-VId0mNaFJgphocJbo,53721
41
- d365fo_client/metadata_v2/database_v2.py,sha256=_8rmFSTNw1vCvvStygJzdgRSbWvSMwYJr4MMcGElsmE,21986
40
+ d365fo_client/metadata_v2/cache_v2.py,sha256=VxXzCZAWISow4CPEOnrMFHj0LmtXPUWDv4u41kd358s,60044
41
+ d365fo_client/metadata_v2/database_v2.py,sha256=rUR45JgtpwUBofYIiFjxZstKLkXglav5czbUX1r-bGo,21859
42
42
  d365fo_client/metadata_v2/global_version_manager.py,sha256=5KYO3Go8cnyWWQAxdBTqO4aysKSBIOi0f9vUin4Ot18,20113
43
+ d365fo_client/metadata_v2/label_utils.py,sha256=-GRagiURJv-ZQCepeOkCRqfo62rhWo8A42271w0Cb6A,3752
43
44
  d365fo_client/metadata_v2/search_engine_v2.py,sha256=s_XVqP3LLog19IAv8DpxVUS7TFRUAjuBevyfe1Ldwps,16525
44
- d365fo_client/metadata_v2/sync_manager_v2.py,sha256=TgxRdype996mJw_ypsJOftbp8UCrX0QfgmbBHmQSZV8,29356
45
+ d365fo_client/metadata_v2/sync_manager_v2.py,sha256=deI5jOQYXtG0HAMGrMCZLSSEA2tVcWyKWaY_Mla2rhA,34067
45
46
  d365fo_client/metadata_v2/version_detector.py,sha256=t9mKaeT4SKb13LmIq5fB6PTLOZn5Jp7ZUqQobntNEUg,15791
46
- d365fo_client-0.1.0.dist-info/licenses/LICENSE,sha256=idD7NJAZD7ognzZVyKjDxVYDCmngEIt0WxA_uB1v0iI,1071
47
- d365fo_client-0.1.0.dist-info/METADATA,sha256=XT4Yxwp0JtZdtW8O5pk_diOeGW98QkRnO_nKpNfEVV0,35318
48
- d365fo_client-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
- d365fo_client-0.1.0.dist-info/entry_points.txt,sha256=ZZBjH4mQ0XO3ALeNswswa09dh_JGIvD-zCBkKi6qNqA,106
50
- d365fo_client-0.1.0.dist-info/top_level.txt,sha256=ZbvqO90RjhOW0cjFCAEeP8OFyITbhrij2vC3k4bWERQ,14
51
- d365fo_client-0.1.0.dist-info/RECORD,,
47
+ d365fo_client-0.2.1.dist-info/licenses/LICENSE,sha256=idD7NJAZD7ognzZVyKjDxVYDCmngEIt0WxA_uB1v0iI,1071
48
+ d365fo_client-0.2.1.dist-info/METADATA,sha256=a8jgsXCIuAIfaUDQG4SNg_5z7UxF72aqm2QTwt02XSc,35404
49
+ d365fo_client-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
50
+ d365fo_client-0.2.1.dist-info/entry_points.txt,sha256=ZZBjH4mQ0XO3ALeNswswa09dh_JGIvD-zCBkKi6qNqA,106
51
+ d365fo_client-0.2.1.dist-info/top_level.txt,sha256=ZbvqO90RjhOW0cjFCAEeP8OFyITbhrij2vC3k4bWERQ,14
52
+ d365fo_client-0.2.1.dist-info/RECORD,,