d365fo-client 0.2.1__py3-none-any.whl → 0.2.3__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.
@@ -213,17 +213,19 @@ class MetadataCacheV2:
213
213
  entities: List of data entity information
214
214
  """
215
215
  async with aiosqlite.connect(self.db_path) as db:
216
- # Clear existing entities for this version
217
- await db.execute(
218
- "DELETE FROM data_entities WHERE global_version_id = ?",
219
- (global_version_id,),
220
- )
216
+
221
217
 
222
218
  # Insert new entities with label processing
223
219
  for entity in entities:
224
220
  # Process label fallback for this entity
225
221
  processed_label_text = process_label_fallback(entity.label_id, entity.label_text)
226
-
222
+
223
+ # Clear existing entity for this version
224
+ await db.execute(
225
+ "DELETE FROM data_entities WHERE global_version_id = ? and name = ?",
226
+ (global_version_id, entity.name,),
227
+ )
228
+
227
229
  await db.execute(
228
230
  """INSERT INTO data_entities
229
231
  (global_version_id, name, public_entity_name, public_collection_name,
@@ -237,7 +239,7 @@ class MetadataCacheV2:
237
239
  entity.public_collection_name,
238
240
  entity.label_id,
239
241
  processed_label_text, # Use processed label text
240
- entity.entity_category.value if entity.entity_category else None,
242
+ entity.entity_category if entity.entity_category else None,
241
243
  entity.data_service_enabled,
242
244
  entity.data_management_enabled,
243
245
  entity.is_read_only,
@@ -245,7 +247,7 @@ class MetadataCacheV2:
245
247
  )
246
248
 
247
249
  await db.commit()
248
- logger.info(
250
+ logger.debug(
249
251
  f"Stored {len(entities)} data entities for version {global_version_id}"
250
252
  )
251
253
 
@@ -481,7 +483,7 @@ class MetadataCacheV2:
481
483
  nav_prop.name,
482
484
  nav_prop.related_entity,
483
485
  nav_prop.related_relation_name,
484
- nav_prop.cardinality.value, # Convert enum to string value
486
+ nav_prop.cardinality, # StrEnum automatically converts to string
485
487
  ),
486
488
  )
487
489
 
@@ -519,7 +521,7 @@ class MetadataCacheV2:
519
521
  entity_id,
520
522
  global_version_id,
521
523
  action.name,
522
- action.binding_kind.value, # Convert enum to string value
524
+ action.binding_kind, # StrEnum automatically converts to string
523
525
  entity_schema.name,
524
526
  entity_schema.entity_set_name,
525
527
  action.return_type.type_name if action.return_type else None,
@@ -1357,7 +1359,7 @@ class MetadataCacheV2:
1357
1359
  )
1358
1360
  await db.commit()
1359
1361
 
1360
- logger.info(
1362
+ logger.debug(
1361
1363
  f"Batch cached {len(labels)} labels for version {global_version_id}"
1362
1364
  )
1363
1365
 
@@ -1495,19 +1497,24 @@ class MetadataCacheV2:
1495
1497
  """Get cache statistics
1496
1498
 
1497
1499
  Returns:
1498
- Dictionary with cache statistics
1500
+ Dictionary with cache statistics scoped to the current environment
1499
1501
  """
1502
+ await self.initialize()
1503
+
1504
+ if self._environment_id is None:
1505
+ raise ValueError("Environment not initialized")
1506
+
1500
1507
  stats = {}
1501
1508
 
1502
- # Database statistics
1503
- db_stats = await self.database.get_database_statistics()
1509
+ # Environment-scoped database statistics
1510
+ db_stats = await self.database.get_environment_database_statistics(self._environment_id)
1504
1511
  stats.update(db_stats)
1505
1512
 
1506
- # Version statistics
1507
- version_stats = await self.version_manager.get_version_statistics()
1513
+ # Environment-scoped version statistics
1514
+ version_stats = await self.version_manager.get_environment_version_statistics(self._environment_id)
1508
1515
  stats["version_manager"] = version_stats
1509
1516
 
1510
- # Current version info
1517
+ # Current version info (already environment-scoped)
1511
1518
  current_version = await self._get_current_global_version_id()
1512
1519
  if current_version:
1513
1520
  version_info = await self.version_manager.get_global_version_info(
@@ -1517,11 +1524,11 @@ class MetadataCacheV2:
1517
1524
  stats["current_version"] = {
1518
1525
  "global_version_id": version_info.id,
1519
1526
  "version_hash": version_info.version_hash,
1520
- "modules_count": len(version_info.sample_modules),
1527
+ "modules_count": len(version_info.modules),
1521
1528
  "reference_count": version_info.reference_count,
1522
1529
  }
1523
1530
 
1524
- # Label cache statistics
1531
+ # Label cache statistics (already environment-scoped via current_version)
1525
1532
  label_stats = await self.get_label_cache_statistics(current_version)
1526
1533
  stats["label_cache"] = label_stats
1527
1534
 
@@ -542,6 +542,99 @@ class MetadataDatabaseV2:
542
542
 
543
543
  return stats
544
544
 
545
+ async def get_statistics(self) -> Dict[str, Any]:
546
+ """Alias for get_database_statistics for backward compatibility
547
+
548
+ Returns:
549
+ Dictionary with database statistics
550
+ """
551
+ return await self.get_database_statistics()
552
+
553
+ async def get_environment_database_statistics(self, environment_id: int) -> Dict[str, Any]:
554
+ """Get database statistics scoped to a specific environment
555
+
556
+ Args:
557
+ environment_id: Environment ID to get statistics for
558
+
559
+ Returns:
560
+ Dictionary with environment-scoped database statistics
561
+ """
562
+ async with aiosqlite.connect(self.db_path) as db:
563
+ stats = {}
564
+
565
+ # Get active global versions for this environment
566
+ cursor = await db.execute(
567
+ """SELECT DISTINCT global_version_id
568
+ FROM environment_versions
569
+ WHERE environment_id = ? AND is_active = 1""",
570
+ (environment_id,)
571
+ )
572
+ active_versions = [row[0] for row in await cursor.fetchall()]
573
+
574
+ if not active_versions:
575
+ # No active versions, return zero counts
576
+ return {
577
+ "data_entities_count": 0,
578
+ "public_entities_count": 0,
579
+ "entity_properties_count": 0,
580
+ "navigation_properties_count": 0,
581
+ "entity_actions_count": 0,
582
+ "enumerations_count": 0,
583
+ "labels_cache_count": 0,
584
+ "environment_statistics": {
585
+ "total_environments": 1,
586
+ "linked_versions": 0,
587
+ },
588
+ "database_size_bytes": None,
589
+ "database_size_mb": None,
590
+ }
591
+
592
+ # Create placeholders for SQL IN clause
593
+ version_placeholders = ",".join("?" for _ in active_versions)
594
+
595
+ # Environment-scoped metadata counts
596
+ tables = [
597
+ ("data_entities", "entities"),
598
+ ("public_entities", "public_entities"),
599
+ ("entity_properties", "properties"),
600
+ ("navigation_properties", "navigation_properties"),
601
+ ("entity_actions", "actions"),
602
+ ("enumerations", "enumerations"),
603
+ ("labels_cache", "labels"),
604
+ ]
605
+
606
+ for table, key in tables:
607
+ cursor = await db.execute(
608
+ f"SELECT COUNT(*) FROM {table} WHERE global_version_id IN ({version_placeholders})",
609
+ active_versions
610
+ )
611
+ stats[f"{table}_count"] = (await cursor.fetchone())[0]
612
+
613
+ # Environment-specific statistics
614
+ cursor = await db.execute(
615
+ """SELECT
616
+ COUNT(DISTINCT ev.global_version_id) as linked_versions
617
+ FROM environment_versions ev
618
+ WHERE ev.environment_id = ? AND ev.is_active = 1""",
619
+ (environment_id,)
620
+ )
621
+ env_stats = await cursor.fetchone()
622
+ stats["environment_statistics"] = {
623
+ "total_environments": 1, # Current environment only
624
+ "linked_versions": env_stats[0] or 0,
625
+ }
626
+
627
+ # Database file size (shared across all environments)
628
+ try:
629
+ db_size = self.db_path.stat().st_size
630
+ stats["database_size_bytes"] = db_size
631
+ stats["database_size_mb"] = round(db_size / (1024 * 1024), 2)
632
+ except Exception:
633
+ stats["database_size_bytes"] = None
634
+ stats["database_size_mb"] = None
635
+
636
+ return stats
637
+
545
638
  async def vacuum_database(self) -> bool:
546
639
  """Vacuum database to reclaim space
547
640
 
@@ -325,13 +325,11 @@ class GlobalVersionManager:
325
325
  first_seen_at=datetime.fromisoformat(row[3]),
326
326
  last_used_at=datetime.fromisoformat(row[4]),
327
327
  reference_count=row[5],
328
- sample_modules=(
329
- modules[:10] if modules else []
330
- ), # Use first 10 modules as sample
328
+ modules=modules
331
329
  )
332
330
 
333
331
  async def find_compatible_versions(
334
- self, modules: List[ModuleVersionInfo], exact_match: bool = False
332
+ self, modules: List[ModuleVersionInfo], exact_match: bool = True
335
333
  ) -> List[GlobalVersionInfo]:
336
334
  """Find compatible global versions
337
335
 
@@ -571,3 +569,63 @@ class GlobalVersionManager:
571
569
  }
572
570
 
573
571
  return stats
572
+
573
+ async def get_environment_version_statistics(self, environment_id: int) -> Dict[str, Any]:
574
+ """Get version statistics scoped to a specific environment
575
+
576
+ Args:
577
+ environment_id: Environment ID to get statistics for
578
+
579
+ Returns:
580
+ Dictionary with environment-scoped version statistics
581
+ """
582
+ async with aiosqlite.connect(self.db_path) as db:
583
+ stats = {}
584
+
585
+ # Get versions for this specific environment
586
+ cursor = await db.execute(
587
+ """SELECT COUNT(DISTINCT global_version_id)
588
+ FROM environment_versions
589
+ WHERE environment_id = ? AND is_active = 1""",
590
+ (environment_id,)
591
+ )
592
+ stats["total_versions"] = (await cursor.fetchone())[0]
593
+
594
+ # This environment only
595
+ stats["total_environments"] = 1
596
+
597
+ # Reference statistics for this environment's versions
598
+ cursor = await db.execute(
599
+ """SELECT
600
+ SUM(gv.reference_count) as total_references,
601
+ AVG(gv.reference_count) as avg_references,
602
+ MAX(gv.reference_count) as max_references,
603
+ COUNT(*) as versions_with_refs
604
+ FROM global_versions gv
605
+ INNER JOIN environment_versions ev ON gv.id = ev.global_version_id
606
+ WHERE ev.environment_id = ? AND ev.is_active = 1 AND gv.reference_count > 0""",
607
+ (environment_id,)
608
+ )
609
+ ref_stats = await cursor.fetchone()
610
+ stats["reference_statistics"] = {
611
+ "total_references": ref_stats[0] or 0,
612
+ "avg_references": round(ref_stats[1] or 0, 2),
613
+ "max_references": ref_stats[2] or 0,
614
+ "versions_with_references": ref_stats[3] or 0,
615
+ }
616
+
617
+ # Version age statistics for this environment
618
+ cursor = await db.execute(
619
+ """SELECT
620
+ COUNT(*) as recent_versions
621
+ FROM global_versions gv
622
+ INNER JOIN environment_versions ev ON gv.id = ev.global_version_id
623
+ WHERE ev.environment_id = ? AND ev.is_active = 1
624
+ AND gv.last_used_at >= datetime('now', '-7 days')""",
625
+ (environment_id,)
626
+ )
627
+ stats["recent_activity"] = {
628
+ "versions_used_last_7_days": (await cursor.fetchone())[0]
629
+ }
630
+
631
+ return stats
@@ -656,7 +656,7 @@ class SmartSyncManagerV2:
656
656
 
657
657
  # Check for compatible versions (sharing opportunity)
658
658
  compatible_versions = await self.version_manager.find_compatible_versions(
659
- version_info.sample_modules, exact_match=True
659
+ version_info.modules, exact_match=True
660
660
  )
661
661
 
662
662
  for version in compatible_versions: