pyarchinit-mini 1.9.28__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.
- pyarchinit_mini/__init__.py +40 -0
- pyarchinit_mini/api/__init__.py +92 -0
- pyarchinit_mini/api/auth.py +312 -0
- pyarchinit_mini/api/dependencies.py +63 -0
- pyarchinit_mini/api/graphml.py +160 -0
- pyarchinit_mini/api/inventario.py +107 -0
- pyarchinit_mini/api/schemas.py +186 -0
- pyarchinit_mini/api/site.py +220 -0
- pyarchinit_mini/api/us.py +107 -0
- pyarchinit_mini/cli/__init__.py +7 -0
- pyarchinit_mini/cli/export_import_cli.py +289 -0
- pyarchinit_mini/cli/extended_matrix_import.py +101 -0
- pyarchinit_mini/cli/graphml_cli.py +219 -0
- pyarchinit_mini/cli/harris_import.py +574 -0
- pyarchinit_mini/cli/harris_template.py +323 -0
- pyarchinit_mini/cli/migrate.py +202 -0
- pyarchinit_mini/cli/pyarchinit_import_export_cli.py +428 -0
- pyarchinit_mini/cli_interface/__init__.py +1 -0
- pyarchinit_mini/cli_interface/cli_app.py +529 -0
- pyarchinit_mini/config/__init__.py +0 -0
- pyarchinit_mini/config/connection_manager.py +286 -0
- pyarchinit_mini/config/em_node_config_manager.py +452 -0
- pyarchinit_mini/config/em_node_types.yaml +349 -0
- pyarchinit_mini/database/__init__.py +13 -0
- pyarchinit_mini/database/connection.py +168 -0
- pyarchinit_mini/database/database_creator.py +290 -0
- pyarchinit_mini/database/manager.py +231 -0
- pyarchinit_mini/database/migration_scripts/__init__.py +7 -0
- pyarchinit_mini/database/migration_scripts/add_file_path.py +152 -0
- pyarchinit_mini/database/migration_scripts/add_i18n_columns.py +312 -0
- pyarchinit_mini/database/migration_scripts/add_tipo_documento.py +154 -0
- pyarchinit_mini/database/migration_scripts/change_us_area_to_text.py +321 -0
- pyarchinit_mini/database/migration_scripts/change_us_area_to_text_simple.py +230 -0
- pyarchinit_mini/database/migrations.py +232 -0
- pyarchinit_mini/database/postgres_installer.py +375 -0
- pyarchinit_mini/database/schemas.py +177 -0
- pyarchinit_mini/desktop_gui/__init__.py +51 -0
- pyarchinit_mini/desktop_gui/analytics_dialog.py +228 -0
- pyarchinit_mini/desktop_gui/assets/logo.png +0 -0
- pyarchinit_mini/desktop_gui/dialogs.py +1984 -0
- pyarchinit_mini/desktop_gui/excel_import_dialog.py +380 -0
- pyarchinit_mini/desktop_gui/export_import_dialog.py +313 -0
- pyarchinit_mini/desktop_gui/graphml_export_dialog.py +221 -0
- pyarchinit_mini/desktop_gui/gui_app.py +106 -0
- pyarchinit_mini/desktop_gui/harris_matrix_editor.py +970 -0
- pyarchinit_mini/desktop_gui/i18n.py +191 -0
- pyarchinit_mini/desktop_gui/icons.py +70 -0
- pyarchinit_mini/desktop_gui/inventario_dialog_extended.py +966 -0
- pyarchinit_mini/desktop_gui/main_window.py +1826 -0
- pyarchinit_mini/desktop_gui/media_manager_advanced.py +141 -0
- pyarchinit_mini/desktop_gui/postgres_installer_dialog.py +357 -0
- pyarchinit_mini/desktop_gui/pyarchinit_import_export_dialog.py +585 -0
- pyarchinit_mini/desktop_gui/thesaurus_dialog.py +463 -0
- pyarchinit_mini/desktop_gui/us_dialog_extended.py +2122 -0
- pyarchinit_mini/dto/__init__.py +16 -0
- pyarchinit_mini/dto/inventario_dto.py +57 -0
- pyarchinit_mini/dto/site_dto.py +62 -0
- pyarchinit_mini/dto/us_dto.py +237 -0
- pyarchinit_mini/exceptions.py +23 -0
- pyarchinit_mini/graphml_converter/__init__.py +47 -0
- pyarchinit_mini/graphml_converter/converter.py +343 -0
- pyarchinit_mini/graphml_converter/dot_parser.py +1331 -0
- pyarchinit_mini/graphml_converter/em_palette.py +355 -0
- pyarchinit_mini/graphml_converter/graphml_builder.py +657 -0
- pyarchinit_mini/graphml_converter/graphml_exporter.py +1048 -0
- pyarchinit_mini/graphml_converter/pure_networkx_exporter.py +719 -0
- pyarchinit_mini/graphml_converter/svg_resources.py +244 -0
- pyarchinit_mini/graphml_converter/templates/EM_palette.graphml +658 -0
- pyarchinit_mini/graphml_converter/x11_colors.py +678 -0
- pyarchinit_mini/graphml_converter/yed_template.py +365 -0
- pyarchinit_mini/harris_matrix/__init__.py +11 -0
- pyarchinit_mini/harris_matrix/enhanced_visualizer.py +465 -0
- pyarchinit_mini/harris_matrix/matrix_generator.py +1244 -0
- pyarchinit_mini/harris_matrix/matrix_visualizer.py +368 -0
- pyarchinit_mini/harris_matrix/pyarchinit_visualizer.py +581 -0
- pyarchinit_mini/i18n/__init__.py +15 -0
- pyarchinit_mini/i18n/flask_babel_config.py +94 -0
- pyarchinit_mini/i18n/locale_manager.py +142 -0
- pyarchinit_mini/mcp_server/__init__.py +22 -0
- pyarchinit_mini/mcp_server/__main__.py +45 -0
- pyarchinit_mini/mcp_server/blender_client.py +525 -0
- pyarchinit_mini/mcp_server/config.py +181 -0
- pyarchinit_mini/mcp_server/event_stream.py +282 -0
- pyarchinit_mini/mcp_server/graphml_parser.py +612 -0
- pyarchinit_mini/mcp_server/http_server.py +501 -0
- pyarchinit_mini/mcp_server/period_filter.py +362 -0
- pyarchinit_mini/mcp_server/prompts/__init__.py +18 -0
- pyarchinit_mini/mcp_server/prompts/base_prompt.py +23 -0
- pyarchinit_mini/mcp_server/prompts/period_visualization_prompt.py +25 -0
- pyarchinit_mini/mcp_server/prompts/stratigraphic_model_prompt.py +25 -0
- pyarchinit_mini/mcp_server/prompts/us_description_prompt.py +25 -0
- pyarchinit_mini/mcp_server/proxy_generator.py +470 -0
- pyarchinit_mini/mcp_server/resources/__init__.py +24 -0
- pyarchinit_mini/mcp_server/resources/base_resource.py +79 -0
- pyarchinit_mini/mcp_server/resources/graphml_resource.py +382 -0
- pyarchinit_mini/mcp_server/resources/periods_resource.py +52 -0
- pyarchinit_mini/mcp_server/resources/relationships_resource.py +52 -0
- pyarchinit_mini/mcp_server/resources/sites_resource.py +52 -0
- pyarchinit_mini/mcp_server/resources/us_resource.py +52 -0
- pyarchinit_mini/mcp_server/server.py +564 -0
- pyarchinit_mini/mcp_server/tools/__init__.py +24 -0
- pyarchinit_mini/mcp_server/tools/base_tool.py +119 -0
- pyarchinit_mini/mcp_server/tools/batch_insert_tool.py +393 -0
- pyarchinit_mini/mcp_server/tools/build_3d_tool.py +524 -0
- pyarchinit_mini/mcp_server/tools/chatgpt_fetch_tool.py +183 -0
- pyarchinit_mini/mcp_server/tools/chatgpt_search_tool.py +130 -0
- pyarchinit_mini/mcp_server/tools/configure_em_nodes_tool.py +427 -0
- pyarchinit_mini/mcp_server/tools/create_database_tool.py +118 -0
- pyarchinit_mini/mcp_server/tools/create_harris_matrix_tool.py +421 -0
- pyarchinit_mini/mcp_server/tools/data_import_parser_tool.py +695 -0
- pyarchinit_mini/mcp_server/tools/data_management_tool.py +262 -0
- pyarchinit_mini/mcp_server/tools/database_manager_tool.py +200 -0
- pyarchinit_mini/mcp_server/tools/delete_data_tool.py +288 -0
- pyarchinit_mini/mcp_server/tools/export_harris_matrix_graphml_tool.py +184 -0
- pyarchinit_mini/mcp_server/tools/export_tool.py +32 -0
- pyarchinit_mini/mcp_server/tools/filter_tool.py +32 -0
- pyarchinit_mini/mcp_server/tools/generate_report_tool.py +679 -0
- pyarchinit_mini/mcp_server/tools/get_schema_tool.py +224 -0
- pyarchinit_mini/mcp_server/tools/import_excel_tool.py +270 -0
- pyarchinit_mini/mcp_server/tools/insert_data_tool.py +390 -0
- pyarchinit_mini/mcp_server/tools/manage_services_tool.py +507 -0
- pyarchinit_mini/mcp_server/tools/material_tool.py +32 -0
- pyarchinit_mini/mcp_server/tools/media_management_tool.py +494 -0
- pyarchinit_mini/mcp_server/tools/position_tool.py +32 -0
- pyarchinit_mini/mcp_server/tools/pyarchinit_sync_tool.py +172 -0
- pyarchinit_mini/mcp_server/tools/resolve_conflicts_tool.py +377 -0
- pyarchinit_mini/mcp_server/tools/service_management_tool.py +104 -0
- pyarchinit_mini/mcp_server/tools/thesaurus_management_tool.py +420 -0
- pyarchinit_mini/mcp_server/tools/update_data_tool.py +360 -0
- pyarchinit_mini/mcp_server/tools/upload_file_tool.py +112 -0
- pyarchinit_mini/mcp_server/tools/validate_relationship_format_tool.py +327 -0
- pyarchinit_mini/mcp_server/tools/validate_relationship_integrity_tool.py +532 -0
- pyarchinit_mini/mcp_server/tools/validate_stratigraphic_relationships_tool.py +261 -0
- pyarchinit_mini/mcp_server/tools/validate_stratigraphy_tool.py +363 -0
- pyarchinit_mini/media_manager/__init__.py +9 -0
- pyarchinit_mini/media_manager/media_handler.py +322 -0
- pyarchinit_mini/models/__init__.py +35 -0
- pyarchinit_mini/models/base.py +32 -0
- pyarchinit_mini/models/datazione.py +68 -0
- pyarchinit_mini/models/extended_matrix.py +44 -0
- pyarchinit_mini/models/harris_matrix.py +117 -0
- pyarchinit_mini/models/inventario_materiali.py +238 -0
- pyarchinit_mini/models/media.py +109 -0
- pyarchinit_mini/models/site.py +96 -0
- pyarchinit_mini/models/thesaurus.py +179 -0
- pyarchinit_mini/models/us.py +291 -0
- pyarchinit_mini/models/user.py +78 -0
- pyarchinit_mini/pdf_export/__init__.py +9 -0
- pyarchinit_mini/pdf_export/pdf_generator.py +1048 -0
- pyarchinit_mini/pdf_export/pdf_generator_backup.py +1013 -0
- pyarchinit_mini/pdf_export/pyarchinit_finds_template.py +404 -0
- pyarchinit_mini/pdf_export/pyarchinit_inventory_template.py +353 -0
- pyarchinit_mini/pdf_export/usm_implementation.py +465 -0
- pyarchinit_mini/s3d_integration/__init__.py +9 -0
- pyarchinit_mini/s3d_integration/model_manager.py +234 -0
- pyarchinit_mini/s3d_integration/s3d_converter.py +848 -0
- pyarchinit_mini/scripts/__init__.py +3 -0
- pyarchinit_mini/scripts/auto_translate.py +679 -0
- pyarchinit_mini/scripts/configure_claude_desktop.py +277 -0
- pyarchinit_mini/scripts/create_admin_user.py +152 -0
- pyarchinit_mini/scripts/create_correct_stratigraphy_sample.py +373 -0
- pyarchinit_mini/scripts/create_italian_sample_database.py +302 -0
- pyarchinit_mini/scripts/create_sample_for_webapp.py +463 -0
- pyarchinit_mini/scripts/create_sample_with_50_us.py +353 -0
- pyarchinit_mini/scripts/generate_test_3d_model.py +323 -0
- pyarchinit_mini/scripts/load_italian_as_main.py +39 -0
- pyarchinit_mini/scripts/load_sample_as_main.py +73 -0
- pyarchinit_mini/scripts/migrate_relationships.py +165 -0
- pyarchinit_mini/scripts/populate_sample_data.py +498 -0
- pyarchinit_mini/scripts/populate_simple_data.py +213 -0
- pyarchinit_mini/scripts/setup_auth.py +112 -0
- pyarchinit_mini/scripts/setup_user_env.py +316 -0
- pyarchinit_mini/scripts/test_cascade_delete.py +91 -0
- pyarchinit_mini/scripts/test_relationship_fix.py +128 -0
- pyarchinit_mini/scripts/update_sample_relationships.py +136 -0
- pyarchinit_mini/scripts/update_urls_and_versions.py +312 -0
- pyarchinit_mini/scripts/verify_docs.py +312 -0
- pyarchinit_mini/services/__init__.py +19 -0
- pyarchinit_mini/services/analytics_service.py +242 -0
- pyarchinit_mini/services/command_parser.py +159 -0
- pyarchinit_mini/services/datazione_service.py +244 -0
- pyarchinit_mini/services/export_import_service.py +430 -0
- pyarchinit_mini/services/extended_matrix_excel_parser.py +708 -0
- pyarchinit_mini/services/import_export_service.py +2381 -0
- pyarchinit_mini/services/inventario_service.py +347 -0
- pyarchinit_mini/services/mcp_executor.py +206 -0
- pyarchinit_mini/services/media_service.py +458 -0
- pyarchinit_mini/services/periodizzazione_service.py +417 -0
- pyarchinit_mini/services/relationship_sync_service.py +400 -0
- pyarchinit_mini/services/site_service.py +328 -0
- pyarchinit_mini/services/thesaurus_service.py +358 -0
- pyarchinit_mini/services/us_service.py +446 -0
- pyarchinit_mini/services/user_service.py +293 -0
- pyarchinit_mini/translations/en/LC_MESSAGES/messages.mo +0 -0
- pyarchinit_mini/translations/en/LC_MESSAGES/messages.po +5110 -0
- pyarchinit_mini/translations/it/LC_MESSAGES/messages.mo +0 -0
- pyarchinit_mini/translations/it/LC_MESSAGES/messages.po +4798 -0
- pyarchinit_mini/translations/messages.pot +0 -0
- pyarchinit_mini/utils/__init__.py +27 -0
- pyarchinit_mini/utils/auth.py +143 -0
- pyarchinit_mini/utils/exceptions.py +38 -0
- pyarchinit_mini/utils/relationship_validator.py +388 -0
- pyarchinit_mini/utils/stratigraphic_validator.py +613 -0
- pyarchinit_mini/utils/validators.py +230 -0
- pyarchinit_mini/web_interface/app.py +4152 -0
- pyarchinit_mini/web_interface/auth_routes.py +263 -0
- pyarchinit_mini/web_interface/em_node_config_routes.py +280 -0
- pyarchinit_mini/web_interface/excel_import_routes.py +336 -0
- pyarchinit_mini/web_interface/harris_creator_routes.py +568 -0
- pyarchinit_mini/web_interface/pyarchinit_import_export_routes.py +888 -0
- pyarchinit_mini/web_interface/s3d_routes.py +361 -0
- pyarchinit_mini/web_interface/socketio_events.py +537 -0
- pyarchinit_mini/web_interface/static/css/style.css +4 -0
- pyarchinit_mini/web_interface/static/images/favicon.ico +0 -0
- pyarchinit_mini/web_interface/static/images/logo.png +0 -0
- pyarchinit_mini/web_interface/static/images/pyarchinit-mini-logo.png +0 -0
- pyarchinit_mini/web_interface/static/js/OrbitControls.js +1045 -0
- pyarchinit_mini/web_interface/static/js/harris_creator_editor.js +989 -0
- pyarchinit_mini/web_interface/static/js/stratigraphic_layout.js +782 -0
- pyarchinit_mini/web_interface/static/js/three-d-viewer.js +670 -0
- pyarchinit_mini/web_interface/static/js/three.min.js +6 -0
- pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.bin +0 -0
- pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.gltf +92 -0
- pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.mtl +71 -0
- pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.obj +244 -0
- pyarchinit_mini/web_interface/templates/3d_builder/index.html +2092 -0
- pyarchinit_mini/web_interface/templates/3d_viewer/viewer.html +259 -0
- pyarchinit_mini/web_interface/templates/admin/database.html +1140 -0
- pyarchinit_mini/web_interface/templates/admin/database_connect.html +188 -0
- pyarchinit_mini/web_interface/templates/admin/database_info.html +171 -0
- pyarchinit_mini/web_interface/templates/admin/database_upload.html +112 -0
- pyarchinit_mini/web_interface/templates/analytics/dashboard.html +414 -0
- pyarchinit_mini/web_interface/templates/auth/login.html +52 -0
- pyarchinit_mini/web_interface/templates/auth/users.html +217 -0
- pyarchinit_mini/web_interface/templates/base.html +765 -0
- pyarchinit_mini/web_interface/templates/blender_viewer.html +1285 -0
- pyarchinit_mini/web_interface/templates/components/language_switcher.html +47 -0
- pyarchinit_mini/web_interface/templates/dashboard.html +226 -0
- pyarchinit_mini/web_interface/templates/datazioni/list.html +62 -0
- pyarchinit_mini/web_interface/templates/em_node_config/index.html +430 -0
- pyarchinit_mini/web_interface/templates/excel_import/index.html +391 -0
- pyarchinit_mini/web_interface/templates/export/export_import.html +240 -0
- pyarchinit_mini/web_interface/templates/harris_creator/editor.html +373 -0
- pyarchinit_mini/web_interface/templates/harris_creator/index.html +196 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/graphml_export.html +300 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/large_graph_message.html +108 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/stratigraph_viewer.html +474 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/view.html +97 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/view_graphviz.html +202 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/viewer_3d_integrated.html +1288 -0
- pyarchinit_mini/web_interface/templates/inventario/form.html +605 -0
- pyarchinit_mini/web_interface/templates/inventario/list.html +109 -0
- pyarchinit_mini/web_interface/templates/media/list.html +293 -0
- pyarchinit_mini/web_interface/templates/media/upload.html +125 -0
- pyarchinit_mini/web_interface/templates/periodizzazione/form.html +81 -0
- pyarchinit_mini/web_interface/templates/periodizzazione/list.html +102 -0
- pyarchinit_mini/web_interface/templates/periodizzazione/periods.html +107 -0
- pyarchinit_mini/web_interface/templates/pyarchinit_import_export/index.html +928 -0
- pyarchinit_mini/web_interface/templates/sites/detail.html +128 -0
- pyarchinit_mini/web_interface/templates/sites/form.html +315 -0
- pyarchinit_mini/web_interface/templates/sites/list.html +273 -0
- pyarchinit_mini/web_interface/templates/thesaurus/list.html +290 -0
- pyarchinit_mini/web_interface/templates/us/form.html +788 -0
- pyarchinit_mini/web_interface/templates/us/list.html +165 -0
- pyarchinit_mini/web_interface/templates/validation/report.html +253 -0
- pyarchinit_mini/web_interface/test_prova_extended_matrix.graphml +177 -0
- pyarchinit_mini/web_interface/three_d_builder_routes.py +1554 -0
- pyarchinit_mini/web_interface/uploads/graphml/Sito Archeologico di Esempio_stratigraphy.graphml +1792 -0
- pyarchinit_mini/web_interface/uploads/graphml/Sito Archeologico di Esempio_stratigraphy.json +2115 -0
- pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Scavo archeologico_heriverse.json +2132 -0
- pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Scavo archeologico_stratigraphy.graphml +2001 -0
- pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Scavo archeologico_stratigraphy.json +1782 -0
- pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Sito Archeologico di Esempio_stratigraphy.json +1457 -0
- pyarchinit_mini-1.9.28.dist-info/METADATA +2141 -0
- pyarchinit_mini-1.9.28.dist-info/RECORD +281 -0
- pyarchinit_mini-1.9.28.dist-info/WHEEL +5 -0
- pyarchinit_mini-1.9.28.dist-info/entry_points.txt +18 -0
- pyarchinit_mini-1.9.28.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/conftest.py +88 -0
- tests/unit/test_site_service.py +106 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyArchInit-Mini: Standalone Archaeological Data Management System
|
|
3
|
+
|
|
4
|
+
A lightweight, modular version of PyArchInit focused on core archaeological
|
|
5
|
+
data management functionality without GIS dependencies.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Site management with complete CRUD operations
|
|
9
|
+
- Stratigraphic Unit (US) management (49 fields, 6 tabs)
|
|
10
|
+
- Material inventory management (37 fields, 8 tabs, ICCD thesaurus)
|
|
11
|
+
- Harris Matrix generation (Matplotlib + Graphviz with 4 grouping modes)
|
|
12
|
+
- Stratigraphic validation (paradoxes, cycles, auto-fix)
|
|
13
|
+
- Multi-database support (PostgreSQL/SQLite upload/connect)
|
|
14
|
+
- PDF export (Sites, US, Inventario, Harris Matrix)
|
|
15
|
+
- Excel/CSV export and batch import
|
|
16
|
+
- Multi-user authentication with role-based permissions (Admin, Operator, Viewer)
|
|
17
|
+
- Real-time collaboration with WebSocket notifications (Flask-SocketIO)
|
|
18
|
+
- Analytics dashboard with 8 chart types (Chart.js for web, matplotlib for desktop)
|
|
19
|
+
- REST API interface (FastAPI with JWT authentication)
|
|
20
|
+
- Web UI interface (Flask with session-based auth) - 100% Desktop GUI parity
|
|
21
|
+
- Desktop GUI interface (Tkinter)
|
|
22
|
+
- CLI interface
|
|
23
|
+
- Scalable and modular architecture
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__version__ = "1.9.23"
|
|
27
|
+
__author__ = "PyArchInit Team"
|
|
28
|
+
__email__ = "enzo.ccc@gmail.com"
|
|
29
|
+
|
|
30
|
+
from .database.manager import DatabaseManager
|
|
31
|
+
from .services.site_service import SiteService
|
|
32
|
+
from .services.us_service import USService
|
|
33
|
+
from .services.inventario_service import InventarioService
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"DatabaseManager",
|
|
37
|
+
"SiteService",
|
|
38
|
+
"USService",
|
|
39
|
+
"InventarioService"
|
|
40
|
+
]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI REST API for PyArchInit-Mini
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
7
|
+
from pyarchinit_mini import __version__
|
|
8
|
+
from .site import router as site_router
|
|
9
|
+
from .us import router as us_router
|
|
10
|
+
from .inventario import router as inventario_router
|
|
11
|
+
from .auth import router as auth_router
|
|
12
|
+
from .graphml import router as graphml_router
|
|
13
|
+
|
|
14
|
+
def create_app(database_url: str = None) -> FastAPI:
|
|
15
|
+
"""
|
|
16
|
+
Create and configure FastAPI application
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
database_url: Database connection URL
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Configured FastAPI app
|
|
23
|
+
"""
|
|
24
|
+
app = FastAPI(
|
|
25
|
+
title="PyArchInit-Mini API",
|
|
26
|
+
description="REST API for archaeological data management",
|
|
27
|
+
version=__version__,
|
|
28
|
+
docs_url="/docs",
|
|
29
|
+
redoc_url="/redoc"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Add CORS middleware
|
|
33
|
+
app.add_middleware(
|
|
34
|
+
CORSMiddleware,
|
|
35
|
+
allow_origins=["*"], # Configure appropriately for production
|
|
36
|
+
allow_credentials=True,
|
|
37
|
+
allow_methods=["*"],
|
|
38
|
+
allow_headers=["*"],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Include routers
|
|
42
|
+
app.include_router(auth_router) # Auth routes already have /api/auth prefix
|
|
43
|
+
app.include_router(site_router, prefix="/api/v1/sites", tags=["sites"])
|
|
44
|
+
app.include_router(us_router, prefix="/api/v1/us", tags=["stratigraphic-units"])
|
|
45
|
+
app.include_router(inventario_router, prefix="/api/v1/inventario", tags=["inventory"])
|
|
46
|
+
app.include_router(graphml_router, prefix="/api/graphml", tags=["graphml-converter"])
|
|
47
|
+
|
|
48
|
+
# Store database URL in app state
|
|
49
|
+
if database_url:
|
|
50
|
+
app.state.database_url = database_url
|
|
51
|
+
|
|
52
|
+
@app.get("/")
|
|
53
|
+
async def root():
|
|
54
|
+
return {
|
|
55
|
+
"message": "PyArchInit-Mini API",
|
|
56
|
+
"version": __version__,
|
|
57
|
+
"docs": "/docs"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@app.get("/health")
|
|
61
|
+
async def health_check():
|
|
62
|
+
return {"status": "healthy"}
|
|
63
|
+
|
|
64
|
+
return app
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def run_server():
|
|
68
|
+
"""
|
|
69
|
+
Entry point for running the API server via console script.
|
|
70
|
+
"""
|
|
71
|
+
import uvicorn
|
|
72
|
+
import os
|
|
73
|
+
|
|
74
|
+
# Get configuration from environment or use defaults
|
|
75
|
+
host = os.getenv("PYARCHINIT_API_HOST", "0.0.0.0")
|
|
76
|
+
port = int(os.getenv("PYARCHINIT_API_PORT", "8000"))
|
|
77
|
+
reload = os.getenv("PYARCHINIT_API_RELOAD", "false").lower() == "true"
|
|
78
|
+
|
|
79
|
+
print(f"Starting PyArchInit-Mini API server on {host}:{port}")
|
|
80
|
+
print(f"API Documentation: http://{host if host != '0.0.0.0' else 'localhost'}:{port}/docs")
|
|
81
|
+
|
|
82
|
+
# Run the server
|
|
83
|
+
uvicorn.run(
|
|
84
|
+
"pyarchinit_mini.api:create_app",
|
|
85
|
+
host=host,
|
|
86
|
+
port=port,
|
|
87
|
+
reload=reload,
|
|
88
|
+
factory=True
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
__all__ = ["create_app", "run_server"]
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication API endpoints
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from datetime import timedelta
|
|
7
|
+
|
|
8
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
9
|
+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
|
10
|
+
from pydantic import BaseModel, EmailStr
|
|
11
|
+
|
|
12
|
+
from pyarchinit_mini.database.manager import DatabaseManager
|
|
13
|
+
from pyarchinit_mini.services.user_service import UserService
|
|
14
|
+
from pyarchinit_mini.models.user import UserRole
|
|
15
|
+
from pyarchinit_mini.utils.auth import create_access_token, decode_access_token
|
|
16
|
+
from pyarchinit_mini.api.dependencies import get_database_manager
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# OAuth2 scheme
|
|
20
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Pydantic schemas
|
|
24
|
+
class Token(BaseModel):
|
|
25
|
+
"""Token response"""
|
|
26
|
+
access_token: str
|
|
27
|
+
token_type: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TokenData(BaseModel):
|
|
31
|
+
"""Token payload data"""
|
|
32
|
+
username: Optional[str] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class UserResponse(BaseModel):
|
|
36
|
+
"""User response schema"""
|
|
37
|
+
id: int
|
|
38
|
+
username: str
|
|
39
|
+
email: str
|
|
40
|
+
full_name: Optional[str] = None
|
|
41
|
+
role: str
|
|
42
|
+
is_active: bool
|
|
43
|
+
is_superuser: bool
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class UserCreate(BaseModel):
|
|
47
|
+
"""User creation schema"""
|
|
48
|
+
username: str
|
|
49
|
+
email: EmailStr
|
|
50
|
+
password: str
|
|
51
|
+
full_name: Optional[str] = None
|
|
52
|
+
role: UserRole = UserRole.VIEWER
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class UserUpdate(BaseModel):
|
|
56
|
+
"""User update schema"""
|
|
57
|
+
email: Optional[EmailStr] = None
|
|
58
|
+
full_name: Optional[str] = None
|
|
59
|
+
role: Optional[UserRole] = None
|
|
60
|
+
is_active: Optional[bool] = None
|
|
61
|
+
password: Optional[str] = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Router
|
|
65
|
+
router = APIRouter(prefix="/api/auth", tags=["authentication"])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def get_current_user(
|
|
69
|
+
token: str = Depends(oauth2_scheme),
|
|
70
|
+
db_manager: DatabaseManager = Depends(get_database_manager)
|
|
71
|
+
) -> dict:
|
|
72
|
+
"""
|
|
73
|
+
Get current authenticated user from token
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
token: JWT token
|
|
77
|
+
db_manager: Database manager
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
dict: Current user data
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
HTTPException: If authentication fails
|
|
84
|
+
"""
|
|
85
|
+
credentials_exception = HTTPException(
|
|
86
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
87
|
+
detail="Could not validate credentials",
|
|
88
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Decode token
|
|
92
|
+
payload = decode_access_token(token)
|
|
93
|
+
if payload is None:
|
|
94
|
+
raise credentials_exception
|
|
95
|
+
|
|
96
|
+
username: str = payload.get("sub")
|
|
97
|
+
if username is None:
|
|
98
|
+
raise credentials_exception
|
|
99
|
+
|
|
100
|
+
# Get user
|
|
101
|
+
user_service = UserService(db_manager)
|
|
102
|
+
user = user_service.get_user_by_username(username)
|
|
103
|
+
|
|
104
|
+
if user is None:
|
|
105
|
+
raise credentials_exception
|
|
106
|
+
|
|
107
|
+
if not user["is_active"]:
|
|
108
|
+
raise HTTPException(
|
|
109
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
110
|
+
detail="User is inactive"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return user
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
async def get_current_active_admin(
|
|
117
|
+
current_user: dict = Depends(get_current_user)
|
|
118
|
+
) -> dict:
|
|
119
|
+
"""
|
|
120
|
+
Verify current user is admin
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
current_user: Current user from token
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
dict: Current user data
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
HTTPException: If user is not admin
|
|
130
|
+
"""
|
|
131
|
+
if current_user["role"] != UserRole.ADMIN.value and not current_user["is_superuser"]:
|
|
132
|
+
raise HTTPException(
|
|
133
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
134
|
+
detail="Not enough permissions"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return current_user
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@router.post("/login", response_model=Token)
|
|
141
|
+
async def login(
|
|
142
|
+
form_data: OAuth2PasswordRequestForm = Depends(),
|
|
143
|
+
db_manager: DatabaseManager = Depends(get_database_manager)
|
|
144
|
+
):
|
|
145
|
+
"""
|
|
146
|
+
Login endpoint - Returns JWT token
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
form_data: Username and password
|
|
150
|
+
db_manager: Database manager
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Token: Access token
|
|
154
|
+
"""
|
|
155
|
+
user_service = UserService(db_manager)
|
|
156
|
+
user = user_service.authenticate_user(form_data.username, form_data.password)
|
|
157
|
+
|
|
158
|
+
if not user:
|
|
159
|
+
raise HTTPException(
|
|
160
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
161
|
+
detail="Incorrect username or password",
|
|
162
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Create access token
|
|
166
|
+
access_token_expires = timedelta(minutes=30)
|
|
167
|
+
access_token = create_access_token(
|
|
168
|
+
data={"sub": user["username"]},
|
|
169
|
+
expires_delta=access_token_expires
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return {"access_token": access_token, "token_type": "bearer"}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@router.get("/me", response_model=UserResponse)
|
|
176
|
+
async def get_me(current_user: dict = Depends(get_current_user)):
|
|
177
|
+
"""
|
|
178
|
+
Get current user information
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
current_user: Current authenticated user
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
UserResponse: Current user data
|
|
185
|
+
"""
|
|
186
|
+
return current_user
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@router.post("/register", response_model=UserResponse)
|
|
190
|
+
async def register(
|
|
191
|
+
user_data: UserCreate,
|
|
192
|
+
db_manager: DatabaseManager = Depends(get_database_manager),
|
|
193
|
+
current_user: dict = Depends(get_current_active_admin)
|
|
194
|
+
):
|
|
195
|
+
"""
|
|
196
|
+
Register a new user (admin only)
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
user_data: User creation data
|
|
200
|
+
db_manager: Database manager
|
|
201
|
+
current_user: Current admin user
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
UserResponse: Created user
|
|
205
|
+
"""
|
|
206
|
+
user_service = UserService(db_manager)
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
user = user_service.create_user(
|
|
210
|
+
username=user_data.username,
|
|
211
|
+
email=user_data.email,
|
|
212
|
+
password=user_data.password,
|
|
213
|
+
full_name=user_data.full_name,
|
|
214
|
+
role=user_data.role
|
|
215
|
+
)
|
|
216
|
+
return user
|
|
217
|
+
except ValueError as e:
|
|
218
|
+
raise HTTPException(
|
|
219
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
220
|
+
detail=str(e)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@router.get("/users", response_model=list[UserResponse])
|
|
225
|
+
async def list_users(
|
|
226
|
+
db_manager: DatabaseManager = Depends(get_database_manager),
|
|
227
|
+
current_user: dict = Depends(get_current_active_admin)
|
|
228
|
+
):
|
|
229
|
+
"""
|
|
230
|
+
List all users (admin only)
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
db_manager: Database manager
|
|
234
|
+
current_user: Current admin user
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
List[UserResponse]: List of all users
|
|
238
|
+
"""
|
|
239
|
+
user_service = UserService(db_manager)
|
|
240
|
+
users = user_service.get_all_users()
|
|
241
|
+
return users
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@router.put("/users/{user_id}", response_model=UserResponse)
|
|
245
|
+
async def update_user(
|
|
246
|
+
user_id: int,
|
|
247
|
+
user_data: UserUpdate,
|
|
248
|
+
db_manager: DatabaseManager = Depends(get_database_manager),
|
|
249
|
+
current_user: dict = Depends(get_current_active_admin)
|
|
250
|
+
):
|
|
251
|
+
"""
|
|
252
|
+
Update user (admin only)
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
user_id: User ID to update
|
|
256
|
+
user_data: Update data
|
|
257
|
+
db_manager: Database manager
|
|
258
|
+
current_user: Current admin user
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
UserResponse: Updated user
|
|
262
|
+
"""
|
|
263
|
+
user_service = UserService(db_manager)
|
|
264
|
+
|
|
265
|
+
# Prepare update dict (exclude None values)
|
|
266
|
+
updates = {k: v for k, v in user_data.dict().items() if v is not None}
|
|
267
|
+
|
|
268
|
+
user = user_service.update_user(user_id, **updates)
|
|
269
|
+
|
|
270
|
+
if not user:
|
|
271
|
+
raise HTTPException(
|
|
272
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
273
|
+
detail="User not found"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return user
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@router.delete("/users/{user_id}")
|
|
280
|
+
async def delete_user(
|
|
281
|
+
user_id: int,
|
|
282
|
+
db_manager: DatabaseManager = Depends(get_database_manager),
|
|
283
|
+
current_user: dict = Depends(get_current_active_admin)
|
|
284
|
+
):
|
|
285
|
+
"""
|
|
286
|
+
Delete user (admin only)
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
user_id: User ID to delete
|
|
290
|
+
db_manager: Database manager
|
|
291
|
+
current_user: Current admin user
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
dict: Success message
|
|
295
|
+
"""
|
|
296
|
+
# Prevent deleting yourself
|
|
297
|
+
if user_id == current_user["id"]:
|
|
298
|
+
raise HTTPException(
|
|
299
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
300
|
+
detail="Cannot delete your own account"
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
user_service = UserService(db_manager)
|
|
304
|
+
deleted = user_service.delete_user(user_id)
|
|
305
|
+
|
|
306
|
+
if not deleted:
|
|
307
|
+
raise HTTPException(
|
|
308
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
309
|
+
detail="User not found"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
return {"message": "User deleted successfully"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI dependencies for PyArchInit-Mini
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from fastapi import Depends, HTTPException, Request
|
|
6
|
+
from ..database.connection import DatabaseConnection
|
|
7
|
+
from ..database.manager import DatabaseManager
|
|
8
|
+
from ..services.site_service import SiteService
|
|
9
|
+
from ..services.us_service import USService
|
|
10
|
+
from ..services.inventario_service import InventarioService
|
|
11
|
+
|
|
12
|
+
# Global database connection (will be initialized by app)
|
|
13
|
+
_db_connection = None
|
|
14
|
+
|
|
15
|
+
def get_database_connection() -> DatabaseConnection:
|
|
16
|
+
"""Get database connection dependency"""
|
|
17
|
+
global _db_connection
|
|
18
|
+
if _db_connection is None:
|
|
19
|
+
raise HTTPException(status_code=500, detail="Database not initialized")
|
|
20
|
+
return _db_connection
|
|
21
|
+
|
|
22
|
+
def get_database_manager(
|
|
23
|
+
db_conn: DatabaseConnection = Depends(get_database_connection)
|
|
24
|
+
) -> DatabaseManager:
|
|
25
|
+
"""Get database manager dependency"""
|
|
26
|
+
return DatabaseManager(db_conn)
|
|
27
|
+
|
|
28
|
+
def get_site_service(
|
|
29
|
+
db_manager: DatabaseManager = Depends(get_database_manager)
|
|
30
|
+
) -> SiteService:
|
|
31
|
+
"""Get site service dependency"""
|
|
32
|
+
return SiteService(db_manager)
|
|
33
|
+
|
|
34
|
+
def get_us_service(
|
|
35
|
+
db_manager: DatabaseManager = Depends(get_database_manager)
|
|
36
|
+
) -> USService:
|
|
37
|
+
"""Get US service dependency"""
|
|
38
|
+
return USService(db_manager)
|
|
39
|
+
|
|
40
|
+
def get_inventario_service(
|
|
41
|
+
db_manager: DatabaseManager = Depends(get_database_manager)
|
|
42
|
+
) -> InventarioService:
|
|
43
|
+
"""Get inventario service dependency"""
|
|
44
|
+
return InventarioService(db_manager)
|
|
45
|
+
|
|
46
|
+
def init_database(database_url: str):
|
|
47
|
+
"""Initialize global database connection"""
|
|
48
|
+
global _db_connection
|
|
49
|
+
_db_connection = DatabaseConnection.from_url(database_url)
|
|
50
|
+
|
|
51
|
+
# Initialize database with migrations
|
|
52
|
+
_db_connection.initialize_database()
|
|
53
|
+
|
|
54
|
+
# Test connection
|
|
55
|
+
if not _db_connection.test_connection():
|
|
56
|
+
raise Exception("Failed to establish database connection")
|
|
57
|
+
|
|
58
|
+
def close_database():
|
|
59
|
+
"""Close global database connection"""
|
|
60
|
+
global _db_connection
|
|
61
|
+
if _db_connection:
|
|
62
|
+
_db_connection.close()
|
|
63
|
+
_db_connection = None
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GraphML Converter API endpoints
|
|
3
|
+
"""
|
|
4
|
+
from fastapi import APIRouter, UploadFile, File, Form, HTTPException
|
|
5
|
+
from fastapi.responses import Response, FileResponse
|
|
6
|
+
from typing import Optional
|
|
7
|
+
import tempfile
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from pyarchinit_mini.graphml_converter import (
|
|
11
|
+
convert_dot_to_graphml,
|
|
12
|
+
convert_dot_content_to_graphml,
|
|
13
|
+
get_template_path
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
router = APIRouter()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@router.post("/convert",
|
|
20
|
+
summary="Convert DOT file to GraphML",
|
|
21
|
+
description="Upload a Graphviz DOT file and convert it to yEd-compatible GraphML format")
|
|
22
|
+
async def convert_dot_file(
|
|
23
|
+
file: UploadFile = File(..., description="DOT file to convert"),
|
|
24
|
+
title: str = Form("", description="Diagram title/header"),
|
|
25
|
+
reverse_epochs: bool = Form(False, description="Whether to reverse epoch ordering")
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Convert uploaded DOT file to GraphML format.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
file: DOT file upload
|
|
32
|
+
title: Optional diagram title
|
|
33
|
+
reverse_epochs: Whether to reverse epoch ordering (default: False)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
GraphML file content
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
curl -X POST "http://localhost:8000/api/graphml/convert" \
|
|
40
|
+
-F "file=@harris_matrix.dot" \
|
|
41
|
+
-F "title=Pompei - Regio VI" \
|
|
42
|
+
-F "reverse_epochs=false"
|
|
43
|
+
"""
|
|
44
|
+
if not file.filename.endswith('.dot'):
|
|
45
|
+
raise HTTPException(status_code=400, detail="File must have .dot extension")
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
# Read uploaded file content
|
|
49
|
+
content = await file.read()
|
|
50
|
+
dot_content = content.decode('utf-8')
|
|
51
|
+
|
|
52
|
+
# Convert to GraphML
|
|
53
|
+
graphml_content = convert_dot_content_to_graphml(
|
|
54
|
+
dot_content,
|
|
55
|
+
title=title,
|
|
56
|
+
reverse_epochs=reverse_epochs
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if graphml_content is None:
|
|
60
|
+
raise HTTPException(status_code=500, detail="Conversion failed")
|
|
61
|
+
|
|
62
|
+
# Return GraphML content
|
|
63
|
+
filename = file.filename.replace('.dot', '.graphml')
|
|
64
|
+
return Response(
|
|
65
|
+
content=graphml_content,
|
|
66
|
+
media_type="application/xml",
|
|
67
|
+
headers={
|
|
68
|
+
"Content-Disposition": f"attachment; filename={filename}"
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
except UnicodeDecodeError:
|
|
73
|
+
raise HTTPException(status_code=400, detail="File must be UTF-8 encoded text")
|
|
74
|
+
except Exception as e:
|
|
75
|
+
raise HTTPException(status_code=500, detail=f"Conversion error: {str(e)}")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@router.post("/convert-content",
|
|
79
|
+
summary="Convert DOT content to GraphML",
|
|
80
|
+
description="Convert DOT content string to GraphML format")
|
|
81
|
+
async def convert_dot_content(
|
|
82
|
+
dot_content: str = Form(..., description="DOT file content as string"),
|
|
83
|
+
title: str = Form("", description="Diagram title/header"),
|
|
84
|
+
reverse_epochs: bool = Form(False, description="Whether to reverse epoch ordering")
|
|
85
|
+
):
|
|
86
|
+
"""
|
|
87
|
+
Convert DOT content string to GraphML format.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
dot_content: DOT file content as string
|
|
91
|
+
title: Optional diagram title
|
|
92
|
+
reverse_epochs: Whether to reverse epoch ordering (default: False)
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
GraphML content as string
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
curl -X POST "http://localhost:8000/api/graphml/convert-content" \
|
|
99
|
+
-F "dot_content=digraph { \"US 100\" -> \"US 101\"; }" \
|
|
100
|
+
-F "title=Test Site"
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
graphml_content = convert_dot_content_to_graphml(
|
|
104
|
+
dot_content,
|
|
105
|
+
title=title,
|
|
106
|
+
reverse_epochs=reverse_epochs
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if graphml_content is None:
|
|
110
|
+
raise HTTPException(status_code=500, detail="Conversion failed")
|
|
111
|
+
|
|
112
|
+
return Response(
|
|
113
|
+
content=graphml_content,
|
|
114
|
+
media_type="application/xml"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
raise HTTPException(status_code=500, detail=f"Conversion error: {str(e)}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@router.get("/template",
|
|
122
|
+
summary="Download yEd GraphML template",
|
|
123
|
+
description="Download the EM_palette.graphml template used for Harris Matrix diagrams")
|
|
124
|
+
async def download_template():
|
|
125
|
+
"""
|
|
126
|
+
Download the yEd GraphML template file.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
EM_palette.graphml template file
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
curl -X GET "http://localhost:8000/api/graphml/template" -o EM_palette.graphml
|
|
133
|
+
"""
|
|
134
|
+
template_path = get_template_path()
|
|
135
|
+
|
|
136
|
+
if not os.path.exists(template_path):
|
|
137
|
+
raise HTTPException(status_code=404, detail="Template file not found")
|
|
138
|
+
|
|
139
|
+
return FileResponse(
|
|
140
|
+
path=template_path,
|
|
141
|
+
media_type="application/xml",
|
|
142
|
+
filename="EM_palette.graphml"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@router.get("/health",
|
|
147
|
+
summary="Health check",
|
|
148
|
+
description="Check if the GraphML converter service is running")
|
|
149
|
+
async def health_check():
|
|
150
|
+
"""
|
|
151
|
+
Health check endpoint.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Service status
|
|
155
|
+
"""
|
|
156
|
+
return {
|
|
157
|
+
"status": "ok",
|
|
158
|
+
"service": "graphml-converter",
|
|
159
|
+
"version": "1.0.0"
|
|
160
|
+
}
|