component-mapper 0.1.0__tar.gz

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.
Files changed (93) hide show
  1. component_mapper-0.1.0/.cache/custom_registry.json +1 -0
  2. component_mapper-0.1.0/.cache/demo_mapping_cache.json +72 -0
  3. component_mapper-0.1.0/.cache/getaipage_cache.json +41 -0
  4. component_mapper-0.1.0/.cache/mapping_cache.json +1 -0
  5. component_mapper-0.1.0/.cache/registry_http/ext_bundui_pagination.json +1 -0
  6. component_mapper-0.1.0/.cache/registry_http/ext_hextaui_pagination.json +1 -0
  7. component_mapper-0.1.0/.cache/registry_http/shadcn_accordion.json +1 -0
  8. component_mapper-0.1.0/.cache/registry_http/shadcn_alert-dialog.json +1 -0
  9. component_mapper-0.1.0/.cache/registry_http/shadcn_alert.json +1 -0
  10. component_mapper-0.1.0/.cache/registry_http/shadcn_aspect-ratio.json +1 -0
  11. component_mapper-0.1.0/.cache/registry_http/shadcn_avatar.json +1 -0
  12. component_mapper-0.1.0/.cache/registry_http/shadcn_badge.json +1 -0
  13. component_mapper-0.1.0/.cache/registry_http/shadcn_breadcrumb.json +1 -0
  14. component_mapper-0.1.0/.cache/registry_http/shadcn_button.json +1 -0
  15. component_mapper-0.1.0/.cache/registry_http/shadcn_calendar.json +1 -0
  16. component_mapper-0.1.0/.cache/registry_http/shadcn_card.json +1 -0
  17. component_mapper-0.1.0/.cache/registry_http/shadcn_carousel.json +1 -0
  18. component_mapper-0.1.0/.cache/registry_http/shadcn_checkbox.json +1 -0
  19. component_mapper-0.1.0/.cache/registry_http/shadcn_collapsible.json +1 -0
  20. component_mapper-0.1.0/.cache/registry_http/shadcn_command.json +1 -0
  21. component_mapper-0.1.0/.cache/registry_http/shadcn_context-menu.json +1 -0
  22. component_mapper-0.1.0/.cache/registry_http/shadcn_data-table.json +1 -0
  23. component_mapper-0.1.0/.cache/registry_http/shadcn_date-picker.json +1 -0
  24. component_mapper-0.1.0/.cache/registry_http/shadcn_dialog.json +1 -0
  25. component_mapper-0.1.0/.cache/registry_http/shadcn_drawer.json +1 -0
  26. component_mapper-0.1.0/.cache/registry_http/shadcn_dropdown-menu.json +1 -0
  27. component_mapper-0.1.0/.cache/registry_http/shadcn_form.json +1 -0
  28. component_mapper-0.1.0/.cache/registry_http/shadcn_hover-card.json +1 -0
  29. component_mapper-0.1.0/.cache/registry_http/shadcn_input-otp.json +1 -0
  30. component_mapper-0.1.0/.cache/registry_http/shadcn_input.json +1 -0
  31. component_mapper-0.1.0/.cache/registry_http/shadcn_label.json +1 -0
  32. component_mapper-0.1.0/.cache/registry_http/shadcn_menubar.json +1 -0
  33. component_mapper-0.1.0/.cache/registry_http/shadcn_navigation-menu.json +1 -0
  34. component_mapper-0.1.0/.cache/registry_http/shadcn_pagination.json +1 -0
  35. component_mapper-0.1.0/.cache/registry_http/shadcn_popover.json +1 -0
  36. component_mapper-0.1.0/.cache/registry_http/shadcn_progress.json +1 -0
  37. component_mapper-0.1.0/.cache/registry_http/shadcn_radio-group.json +1 -0
  38. component_mapper-0.1.0/.cache/registry_http/shadcn_resizable.json +1 -0
  39. component_mapper-0.1.0/.cache/registry_http/shadcn_scroll-area.json +1 -0
  40. component_mapper-0.1.0/.cache/registry_http/shadcn_select.json +1 -0
  41. component_mapper-0.1.0/.cache/registry_http/shadcn_separator.json +1 -0
  42. component_mapper-0.1.0/.cache/registry_http/shadcn_sheet.json +1 -0
  43. component_mapper-0.1.0/.cache/registry_http/shadcn_sidebar.json +1 -0
  44. component_mapper-0.1.0/.cache/registry_http/shadcn_skeleton.json +1 -0
  45. component_mapper-0.1.0/.cache/registry_http/shadcn_slider.json +1 -0
  46. component_mapper-0.1.0/.cache/registry_http/shadcn_sonner.json +1 -0
  47. component_mapper-0.1.0/.cache/registry_http/shadcn_switch.json +1 -0
  48. component_mapper-0.1.0/.cache/registry_http/shadcn_table.json +1 -0
  49. component_mapper-0.1.0/.cache/registry_http/shadcn_tabs.json +1 -0
  50. component_mapper-0.1.0/.cache/registry_http/shadcn_textarea.json +1 -0
  51. component_mapper-0.1.0/.cache/registry_http/shadcn_toast.json +1 -0
  52. component_mapper-0.1.0/.cache/registry_http/shadcn_toggle-group.json +1 -0
  53. component_mapper-0.1.0/.cache/registry_http/shadcn_toggle.json +1 -0
  54. component_mapper-0.1.0/.cache/registry_http/shadcn_tooltip.json +1 -0
  55. component_mapper-0.1.0/.claude/settings.local.json +7 -0
  56. component_mapper-0.1.0/Makefile +22 -0
  57. component_mapper-0.1.0/PKG-INFO +16 -0
  58. component_mapper-0.1.0/README.md +17 -0
  59. component_mapper-0.1.0/component_mapper/__init__.py +4 -0
  60. component_mapper-0.1.0/component_mapper/cache/__init__.py +0 -0
  61. component_mapper-0.1.0/component_mapper/cache/mapping_cache.py +72 -0
  62. component_mapper-0.1.0/component_mapper/config.py +247 -0
  63. component_mapper-0.1.0/component_mapper/mcp/__init__.py +0 -0
  64. component_mapper-0.1.0/component_mapper/mcp/official_client.py +182 -0
  65. component_mapper-0.1.0/component_mapper/mcp/registry_fetcher.py +214 -0
  66. component_mapper-0.1.0/component_mapper/models.py +159 -0
  67. component_mapper-0.1.0/component_mapper/pipeline.py +182 -0
  68. component_mapper-0.1.0/component_mapper/registry/__init__.py +0 -0
  69. component_mapper-0.1.0/component_mapper/registry/astro_generator.py +390 -0
  70. component_mapper-0.1.0/component_mapper/registry/custom_registry.py +127 -0
  71. component_mapper-0.1.0/component_mapper/registry/prop_mapper.py +370 -0
  72. component_mapper-0.1.0/component_mapper/registry/signature_index.py +694 -0
  73. component_mapper-0.1.0/component_mapper/stages/__init__.py +0 -0
  74. component_mapper-0.1.0/component_mapper/stages/astro_stage.py +122 -0
  75. component_mapper-0.1.0/component_mapper/stages/cache_lookup.py +93 -0
  76. component_mapper-0.1.0/component_mapper/stages/llm_mapper.py +509 -0
  77. component_mapper-0.1.0/component_mapper/stages/structural_match.py +145 -0
  78. component_mapper-0.1.0/component_mapper/utils/__init__.py +0 -0
  79. component_mapper-0.1.0/component_mapper/utils/similarity.py +69 -0
  80. component_mapper-0.1.0/component_mapper/utils/source_parser.py +292 -0
  81. component_mapper-0.1.0/examples/01_page_segmentation.py +195 -0
  82. component_mapper-0.1.0/examples/02_mapping_pipeline.py +206 -0
  83. component_mapper-0.1.0/examples/03_custom_components.py +172 -0
  84. component_mapper-0.1.0/examples/04_astro_generation.py +232 -0
  85. component_mapper-0.1.0/examples/05_cache_and_persistence.py +189 -0
  86. component_mapper-0.1.0/examples/06_full_pipeline.py +256 -0
  87. component_mapper-0.1.0/litellm_config.json +6 -0
  88. component_mapper-0.1.0/output/astro_example/src/components/collection/Card.astro +24 -0
  89. component_mapper-0.1.0/output/getaipage/src/components/Alert.astro +7 -0
  90. component_mapper-0.1.0/output/getaipage/src/components/Button.astro +7 -0
  91. component_mapper-0.1.0/output/getaipage/src/components/Calendar.astro +7 -0
  92. component_mapper-0.1.0/pyproject.toml +32 -0
  93. component_mapper-0.1.0/uv.lock +2020 -0
@@ -0,0 +1,72 @@
1
+ {
2
+ "e4dc628e8415fad3e0da0d278454eab4e9a695dd53c1643357c474b14bbc2ded": {
3
+ "fingerprint_hash": "e4dc628e8415fad3e0da0d278454eab4e9a695dd53c1643357c474b14bbc2ded",
4
+ "component_name": "card",
5
+ "registry_source": "shadcn",
6
+ "prop_mapping": {
7
+ "mappings": [
8
+ {
9
+ "segment_field": "h3:0",
10
+ "component_prop": "title",
11
+ "type": "text",
12
+ "confidence": 0.95,
13
+ "ambiguous": false
14
+ },
15
+ {
16
+ "segment_field": "img[src]",
17
+ "component_prop": "image",
18
+ "type": "image_url",
19
+ "confidence": 0.99,
20
+ "ambiguous": false
21
+ },
22
+ {
23
+ "segment_field": "p:0",
24
+ "component_prop": "description",
25
+ "type": "text",
26
+ "confidence": 0.88,
27
+ "ambiguous": false
28
+ }
29
+ ],
30
+ "has_ambiguous": false,
31
+ "unmapped_props": []
32
+ },
33
+ "mapping_stage": "structural_match",
34
+ "confidence": 0.82,
35
+ "hit_count": 2
36
+ },
37
+ "7033415c55fc76ff9a568348065b9f6dd04f7632b63ef1ee25002d94ca440ea7": {
38
+ "fingerprint_hash": "7033415c55fc76ff9a568348065b9f6dd04f7632b63ef1ee25002d94ca440ea7",
39
+ "component_name": "accordion",
40
+ "registry_source": "shadcn",
41
+ "prop_mapping": {
42
+ "mappings": [
43
+ {
44
+ "segment_field": "h3:0",
45
+ "component_prop": "title",
46
+ "type": "text",
47
+ "confidence": 0.95,
48
+ "ambiguous": false
49
+ },
50
+ {
51
+ "segment_field": "img[src]",
52
+ "component_prop": "image",
53
+ "type": "image_url",
54
+ "confidence": 0.99,
55
+ "ambiguous": false
56
+ },
57
+ {
58
+ "segment_field": "p:0",
59
+ "component_prop": "description",
60
+ "type": "text",
61
+ "confidence": 0.88,
62
+ "ambiguous": false
63
+ }
64
+ ],
65
+ "has_ambiguous": false,
66
+ "unmapped_props": []
67
+ },
68
+ "mapping_stage": "llm_mapped",
69
+ "confidence": 0.91,
70
+ "hit_count": 2
71
+ }
72
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "d691ae8a52a332a7069eaec2201dad6de1f1be46356e8c0fa052a6428f1f205c": {
3
+ "fingerprint_hash": "d691ae8a52a332a7069eaec2201dad6de1f1be46356e8c0fa052a6428f1f205c",
4
+ "component_name": "alert",
5
+ "registry_source": "shadcn",
6
+ "prop_mapping": {
7
+ "mappings": [],
8
+ "has_ambiguous": false,
9
+ "unmapped_props": []
10
+ },
11
+ "mapping_stage": "llm_mapped",
12
+ "confidence": 0.5,
13
+ "hit_count": 1
14
+ },
15
+ "1eead0483aca1676d40bb56c8c9d205fa43911abcfab25db37173c3c6ccc1575": {
16
+ "fingerprint_hash": "1eead0483aca1676d40bb56c8c9d205fa43911abcfab25db37173c3c6ccc1575",
17
+ "component_name": "button",
18
+ "registry_source": "shadcn",
19
+ "prop_mapping": {
20
+ "mappings": [],
21
+ "has_ambiguous": false,
22
+ "unmapped_props": []
23
+ },
24
+ "mapping_stage": "llm_mapped",
25
+ "confidence": 0.5,
26
+ "hit_count": 1
27
+ },
28
+ "25bf34fbd9687a8ccf046a9bb7b7425bdae468921dcbbd86c1d58efd8499cea4": {
29
+ "fingerprint_hash": "25bf34fbd9687a8ccf046a9bb7b7425bdae468921dcbbd86c1d58efd8499cea4",
30
+ "component_name": "calendar",
31
+ "registry_source": "shadcn",
32
+ "prop_mapping": {
33
+ "mappings": [],
34
+ "has_ambiguous": false,
35
+ "unmapped_props": []
36
+ },
37
+ "mapping_stage": "llm_mapped",
38
+ "confidence": 0.5,
39
+ "hit_count": 1
40
+ }
41
+ }
@@ -0,0 +1 @@
1
+ {"$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "pagination", "type": "registry:component", "title": "Pagination", "author": "Toby Belhome", "description": "Pagination with page navigation, next and previous links.", "dependencies": [], "registryDependencies": [], "files": [{"path": "examples/components/pagination/01/page.tsx", "content": "import {\n Pagination,\n PaginationContent,\n PaginationEllipsis,\n PaginationItem,\n PaginationLink,\n PaginationNext,\n PaginationPrevious\n} from \"@/components/ui/pagination\";\n\nexport default function PaginationDemo() {\n return (\n <Pagination>\n <PaginationContent>\n <PaginationItem>\n <PaginationPrevious href=\"#\" />\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\">1</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\" isActive>\n 2\n </PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\">3</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationEllipsis />\n </PaginationItem>\n <PaginationItem>\n <PaginationNext href=\"#\" />\n </PaginationItem>\n </PaginationContent>\n </Pagination>\n );\n}\n", "type": "registry:component", "target": "components/pagination.tsx"}], "meta": {"isPro": false}}
@@ -0,0 +1 @@
1
+ {"$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "pagination", "title": "Pagination", "description": "Controls for paging through content.", "registryDependencies": ["button"], "files": [{"path": "registry/new-york/ui/pagination.tsx", "content": "import {\n ChevronLeftIcon,\n ChevronRightIcon,\n MoreHorizontalIcon,\n} from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { type Button, buttonVariants } from \"@/registry/new-york/ui/button\";\n\nfunction Pagination({ className, ...props }: React.ComponentProps<\"nav\">) {\n return (\n <nav\n aria-label=\"pagination\"\n className={cn(\"mx-auto flex w-full justify-center\", className)}\n data-slot=\"pagination\"\n role=\"navigation\"\n {...props}\n />\n );\n}\n\nfunction PaginationContent({\n className,\n ...props\n}: React.ComponentProps<\"ul\">) {\n return (\n <ul\n className={cn(\"flex flex-row items-center gap-1\", className)}\n data-slot=\"pagination-content\"\n {...props}\n />\n );\n}\n\nfunction PaginationItem({ ...props }: React.ComponentProps<\"li\">) {\n return <li data-slot=\"pagination-item\" {...props} />;\n}\n\ntype PaginationLinkProps = {\n isActive?: boolean;\n} & Pick<React.ComponentProps<typeof Button>, \"size\"> &\n React.ComponentProps<\"a\">;\n\nconst PaginationLink = React.forwardRef<HTMLAnchorElement, PaginationLinkProps>(\n function PaginationLink(\n { className, isActive, size = \"icon\", ...props },\n ref\n ) {\n return (\n <a\n aria-current={isActive ? \"page\" : undefined}\n aria-disabled={(props as any).disabled ? true : undefined}\n className={cn(\n buttonVariants({ variant: isActive ? \"outline\" : \"ghost\", size }),\n \"touch-manipulation tabular-nums\",\n className\n )}\n data-active={isActive}\n data-slot=\"pagination-link\"\n ref={ref}\n {...props}\n />\n );\n }\n);\n\nfunction PaginationPrevious({\n className,\n ...props\n}: React.ComponentProps<typeof PaginationLink>) {\n return (\n <PaginationLink\n aria-label=\"Go to previous page\"\n className={cn(\"gap-1 px-2.5 sm:pl-2.5\", className)}\n rel={(props as any).rel ?? \"prev\"}\n {...props}\n size={props.size ?? \"default\"}\n >\n <ChevronLeftIcon aria-hidden=\"true\" />\n <span className=\"hidden sm:block\">Previous</span>\n </PaginationLink>\n );\n}\n\nfunction PaginationNext({\n className,\n ...props\n}: React.ComponentProps<typeof PaginationLink>) {\n return (\n <PaginationLink\n aria-label=\"Go to next page\"\n className={cn(\"gap-1 px-2.5 sm:pr-2.5\", className)}\n rel={(props as any).rel ?? \"next\"}\n {...props}\n size={props.size ?? \"default\"}\n >\n <span className=\"hidden sm:block\">Next</span>\n <ChevronRightIcon aria-hidden=\"true\" />\n </PaginationLink>\n );\n}\n\nfunction PaginationEllipsis({\n className,\n ...props\n}: React.ComponentProps<\"span\">) {\n return (\n <span\n aria-hidden\n className={cn(\"flex size-9 items-center justify-center\", className)}\n data-slot=\"pagination-ellipsis\"\n {...props}\n >\n <MoreHorizontalIcon className=\"size-4\" />\n <span className=\"sr-only\">More pages</span>\n </span>\n );\n}\n\nexport {\n Pagination,\n PaginationContent,\n PaginationLink,\n PaginationItem,\n PaginationPrevious,\n PaginationNext,\n PaginationEllipsis,\n};\n", "type": "registry:ui"}], "type": "registry:ui"}
@@ -0,0 +1 @@
1
+ {"name": "accordion", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "alert-dialog", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "alert", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "aspect-ratio", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "avatar", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "badge", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "breadcrumb", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "button", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "calendar", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "card", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "carousel", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "checkbox", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "collapsible", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "command", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "context-menu", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "data-table", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "date-picker", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "dialog", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "drawer", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "dropdown-menu", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "form", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "hover-card", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "input-otp", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "input", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "label", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "menubar", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "navigation-menu", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "pagination", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "popover", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "progress", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "radio-group", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "resizable", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "scroll-area", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "select", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "separator", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "sheet", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "sidebar", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "skeleton", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "slider", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "sonner", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "switch", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "table", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "tabs", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "textarea", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "toast", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "toggle-group", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "toggle", "files": []}
@@ -0,0 +1 @@
1
+ {"name": "tooltip", "files": []}
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(ls -la /Users/baneet/Desktop/claude/ && ls -la /Users/baneet/Desktop/claude/component_mapper/ 2>/dev/null || echo \"component_mapper dir is empty\")"
5
+ ]
6
+ }
7
+ }
@@ -0,0 +1,22 @@
1
+ .PHONY: install build publish lint format clean test
2
+
3
+ install:
4
+ uv sync
5
+
6
+ build:
7
+ uv build
8
+
9
+ publish: build
10
+ uv publish
11
+
12
+ lint:
13
+ uv run ruff check .
14
+
15
+ format:
16
+ uv run ruff format .
17
+
18
+ test:
19
+ uv run pytest
20
+
21
+ clean:
22
+ rm -rf dist/ .ruff_cache/ .pytest_cache/ .venv/
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: component-mapper
3
+ Version: 0.1.0
4
+ Summary: Maps ClassifiedSegment objects to Shadcn UI components and Astro wrappers
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: aiofiles>=23.0
7
+ Requires-Dist: aiohttp>=3.9
8
+ Requires-Dist: beautifulsoup4>=4.12
9
+ Requires-Dist: litellm>=1.40
10
+ Requires-Dist: numpy>=1.26
11
+ Requires-Dist: page-segmenter>=0.1.2
12
+ Requires-Dist: pydantic-settings>=2.2
13
+ Requires-Dist: pydantic>=2.7
14
+ Requires-Dist: scikit-learn>=1.5
15
+ Requires-Dist: scipy>=1.13
16
+ Requires-Dist: segment-classifier>=0.1.1
@@ -0,0 +1,17 @@
1
+ # Component Mapper
2
+
3
+ Maps `ClassifiedSegment` objects to Shadcn UI components and Astro wrappers.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ make install
9
+ ```
10
+
11
+ ## Development
12
+
13
+ - `make lint`: Check for linting issues
14
+ - `make format`: Format code
15
+ - `make test`: Run tests
16
+ - `make build`: Build package
17
+ - `make publish`: Publish to PyPI
@@ -0,0 +1,4 @@
1
+ from component_mapper.pipeline import MapperPipeline
2
+ from component_mapper.config import MapperSettings
3
+
4
+ __all__ = ["MapperPipeline", "MapperSettings"]
@@ -0,0 +1,72 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ from pathlib import Path
5
+ from component_mapper.models import MappingCacheRecord
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class MappingCache:
11
+ def __init__(self, cache_path: str, auto_persist_every: int = 50):
12
+ self._path = Path(cache_path)
13
+ self._store: dict[str, MappingCacheRecord] = {}
14
+ self._lock = asyncio.Lock()
15
+ self._write_count = 0
16
+ self._auto_persist_every = auto_persist_every
17
+
18
+ async def load(self) -> None:
19
+ """Load cache from disk. Silent if file missing."""
20
+ if not self._path.exists():
21
+ logger.debug("No mapping cache at %s — starting fresh", self._path)
22
+ return
23
+ try:
24
+ async with asyncio.Lock():
25
+ data = self._path.read_text()
26
+ records = json.loads(data)
27
+ async with self._lock:
28
+ for key, raw in records.items():
29
+ try:
30
+ self._store[key] = MappingCacheRecord.model_validate(raw)
31
+ except Exception:
32
+ pass
33
+ logger.info(
34
+ "Loaded %d mapping cache records from %s", len(self._store), self._path
35
+ )
36
+ except Exception as exc:
37
+ logger.warning("Failed to load mapping cache: %s", exc)
38
+
39
+ async def get(self, fingerprint_hash: str) -> MappingCacheRecord | None:
40
+ async with self._lock:
41
+ return self._store.get(fingerprint_hash)
42
+
43
+ async def set(self, fingerprint_hash: str, record: MappingCacheRecord) -> None:
44
+ async with self._lock:
45
+ self._store[fingerprint_hash] = record
46
+ self._write_count += 1
47
+ should_persist = self._write_count % self._auto_persist_every == 0
48
+ if should_persist:
49
+ await self.persist()
50
+
51
+ async def persist(self) -> None:
52
+ """Write cache to disk atomically."""
53
+ self._path.parent.mkdir(parents=True, exist_ok=True)
54
+ tmp_path = self._path.with_suffix(".tmp")
55
+ try:
56
+ async with self._lock:
57
+ data = {k: v.model_dump(mode="json") for k, v in self._store.items()}
58
+ tmp_path.write_text(json.dumps(data, indent=2))
59
+ tmp_path.replace(self._path)
60
+ logger.debug("Persisted %d mapping cache records", len(data))
61
+ except Exception as exc:
62
+ logger.warning("Failed to persist mapping cache: %s", exc)
63
+
64
+ async def increment_hit(self, fingerprint_hash: str) -> None:
65
+ async with self._lock:
66
+ record = self._store.get(fingerprint_hash)
67
+ if record:
68
+ record.hit_count += 1
69
+
70
+ @property
71
+ def size(self) -> int:
72
+ return len(self._store)