sfm-core 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- api/__init__.py +19 -0
- api/rest/__init__.py +1 -0
- api/rest/app.py +87 -0
- api/rest/config.py +39 -0
- api/rest/dependencies.py +24 -0
- api/rest/exceptions.py +126 -0
- api/rest/node_registry.py +120 -0
- api/rest/routers/__init__.py +1 -0
- api/rest/routers/evaluate.py +345 -0
- api/rest/routers/health.py +54 -0
- api/rest/routers/import_export.py +421 -0
- api/rest/routers/nodes.py +295 -0
- api/rest/routers/query.py +197 -0
- api/rest/routers/relationships.py +221 -0
- api/rest/schemas.py +244 -0
- api/sfm_service.py +1780 -0
- data/__init__.py +30 -0
- data/importers/__init__.py +96 -0
- data/importers/base_adapter.py +164 -0
- data/importers/csv_adapter.py +345 -0
- data/importers/mapping_config.py +348 -0
- data/importers/oecd_adapter.py +323 -0
- data/importers/validators.py +201 -0
- data/importers/worldbank_adapter.py +340 -0
- data/neo4j_repository.py +854 -0
- data/repositories.py +652 -0
- graph/__init__.py +34 -0
- graph/converters.py +302 -0
- graph/exporters/__init__.py +15 -0
- graph/exporters/system_dynamics_exporter.py +284 -0
- graph/exporters/xlsx_exporter.py +320 -0
- graph/sfm_graph.py +174 -0
- graph/sfm_persistence.py +682 -0
- graph/sfm_query.py +1108 -0
- models/__init__.py +183 -0
- models/base_nodes.py +86 -0
- models/complex_analysis.py +620 -0
- models/cultural_analysis.py +244 -0
- models/delivery_matrix.py +383 -0
- models/economic_analysis.py +90 -0
- models/exceptions.py +518 -0
- models/institutional_analysis.py +90 -0
- models/matrix_components.py +592 -0
- models/meta_entities.py +189 -0
- models/methodological_framework.py +557 -0
- models/network_analysis.py +264 -0
- models/policy_framework.py +92 -0
- models/sfm_enums.py +4371 -0
- models/social_assessment.py +225 -0
- models/specialized_components.py +318 -0
- models/specialized_nodes.py +146 -0
- models/system_analysis.py +160 -0
- models/technology_integration.py +103 -0
- models/temporal_clocks.py +388 -0
- sfm_core-0.1.0.dist-info/METADATA +777 -0
- sfm_core-0.1.0.dist-info/RECORD +59 -0
- sfm_core-0.1.0.dist-info/WHEEL +5 -0
- sfm_core-0.1.0.dist-info/licenses/LICENSE +674 -0
- sfm_core-0.1.0.dist-info/top_level.txt +4 -0
api/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API layer module for SFM Core.
|
|
3
|
+
|
|
4
|
+
Provides the service facade for Beta unified model operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from api.sfm_service import (
|
|
8
|
+
SFMService,
|
|
9
|
+
SFMServiceConfig,
|
|
10
|
+
ServiceHealth,
|
|
11
|
+
GraphStatistics,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"SFMService",
|
|
16
|
+
"SFMServiceConfig",
|
|
17
|
+
"ServiceHealth",
|
|
18
|
+
"GraphStatistics",
|
|
19
|
+
]
|
api/rest/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""REST API module for SFM Core."""
|
api/rest/app.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""FastAPI application factory for SFM Core REST API."""
|
|
2
|
+
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
5
|
+
from fastapi.exceptions import RequestValidationError
|
|
6
|
+
|
|
7
|
+
from api.rest.config import settings
|
|
8
|
+
from api.rest.exceptions import (
|
|
9
|
+
sfm_exception_handler,
|
|
10
|
+
validation_exception_handler,
|
|
11
|
+
generic_exception_handler,
|
|
12
|
+
)
|
|
13
|
+
from models.exceptions import SFMError
|
|
14
|
+
|
|
15
|
+
# Import routers
|
|
16
|
+
from api.rest.routers import health, nodes, relationships, query, evaluate, import_export
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_app() -> FastAPI:
|
|
20
|
+
"""
|
|
21
|
+
Create and configure FastAPI application.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Configured FastAPI application instance
|
|
25
|
+
"""
|
|
26
|
+
app = FastAPI(
|
|
27
|
+
title=settings.PROJECT_NAME,
|
|
28
|
+
version=settings.PROJECT_VERSION,
|
|
29
|
+
debug=settings.DEBUG,
|
|
30
|
+
docs_url=f"{settings.API_V1_PREFIX}/docs",
|
|
31
|
+
openapi_url=f"{settings.API_V1_PREFIX}/openapi.json",
|
|
32
|
+
redoc_url=f"{settings.API_V1_PREFIX}/redoc",
|
|
33
|
+
description="REST API for Social Fabric Matrix (SFM) Core framework - "
|
|
34
|
+
"modeling, analyzing, and querying complex socio-economic systems",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# CORS middleware
|
|
38
|
+
app.add_middleware(
|
|
39
|
+
CORSMiddleware,
|
|
40
|
+
allow_origins=settings.CORS_ORIGINS,
|
|
41
|
+
allow_credentials=settings.CORS_ALLOW_CREDENTIALS,
|
|
42
|
+
allow_methods=settings.CORS_ALLOW_METHODS,
|
|
43
|
+
allow_headers=settings.CORS_ALLOW_HEADERS,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Exception handlers
|
|
47
|
+
app.add_exception_handler(SFMError, sfm_exception_handler) # type: ignore[arg-type]
|
|
48
|
+
app.add_exception_handler(RequestValidationError, validation_exception_handler) # type: ignore[arg-type]
|
|
49
|
+
app.add_exception_handler(Exception, generic_exception_handler)
|
|
50
|
+
|
|
51
|
+
# Include routers
|
|
52
|
+
app.include_router(
|
|
53
|
+
health.router,
|
|
54
|
+
prefix=settings.API_V1_PREFIX,
|
|
55
|
+
tags=["Health & Diagnostics"]
|
|
56
|
+
)
|
|
57
|
+
app.include_router(
|
|
58
|
+
nodes.router,
|
|
59
|
+
prefix=f"{settings.API_V1_PREFIX}/nodes",
|
|
60
|
+
tags=["Nodes"]
|
|
61
|
+
)
|
|
62
|
+
app.include_router(
|
|
63
|
+
relationships.router,
|
|
64
|
+
prefix=f"{settings.API_V1_PREFIX}/relationships",
|
|
65
|
+
tags=["Relationships"]
|
|
66
|
+
)
|
|
67
|
+
app.include_router(
|
|
68
|
+
query.router,
|
|
69
|
+
prefix=f"{settings.API_V1_PREFIX}/query",
|
|
70
|
+
tags=["Query Analysis"]
|
|
71
|
+
)
|
|
72
|
+
app.include_router(
|
|
73
|
+
evaluate.router,
|
|
74
|
+
prefix=f"{settings.API_V1_PREFIX}/evaluate",
|
|
75
|
+
tags=["Evaluation"]
|
|
76
|
+
)
|
|
77
|
+
app.include_router(
|
|
78
|
+
import_export.router,
|
|
79
|
+
prefix=f"{settings.API_V1_PREFIX}/import",
|
|
80
|
+
tags=["Import/Export"]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return app
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Create application instance
|
|
87
|
+
app = create_app()
|
api/rest/config.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Configuration settings for SFM REST API."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class APISettings(BaseSettings):
|
|
8
|
+
"""API configuration settings loaded from environment variables."""
|
|
9
|
+
|
|
10
|
+
# API Settings
|
|
11
|
+
API_V1_PREFIX: str = "/api/v1"
|
|
12
|
+
PROJECT_NAME: str = "SFM Core REST API"
|
|
13
|
+
PROJECT_VERSION: str = "1.0.0"
|
|
14
|
+
DEBUG: bool = False
|
|
15
|
+
|
|
16
|
+
# CORS
|
|
17
|
+
CORS_ORIGINS: List[str] = ["*"]
|
|
18
|
+
CORS_ALLOW_CREDENTIALS: bool = True
|
|
19
|
+
CORS_ALLOW_METHODS: List[str] = ["*"]
|
|
20
|
+
CORS_ALLOW_HEADERS: List[str] = ["*"]
|
|
21
|
+
|
|
22
|
+
# Service Configuration
|
|
23
|
+
STORAGE_TYPE: str = "networkx"
|
|
24
|
+
GRAPH_SIZE_LIMIT: int = 10000
|
|
25
|
+
|
|
26
|
+
# Neo4j (optional, for production)
|
|
27
|
+
NEO4J_URI: str = "bolt://localhost:7687"
|
|
28
|
+
NEO4J_USERNAME: str = "neo4j"
|
|
29
|
+
NEO4J_PASSWORD: str = "password"
|
|
30
|
+
|
|
31
|
+
model_config = SettingsConfigDict(
|
|
32
|
+
env_file=".env",
|
|
33
|
+
env_file_encoding="utf-8",
|
|
34
|
+
case_sensitive=True
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Global settings instance
|
|
39
|
+
settings = APISettings()
|
api/rest/dependencies.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Dependency injection for REST API."""
|
|
2
|
+
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from api.sfm_service import SFMService, SFMServiceConfig
|
|
5
|
+
from api.rest.config import settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@lru_cache()
|
|
9
|
+
def get_sfm_service() -> SFMService:
|
|
10
|
+
"""
|
|
11
|
+
Get or create singleton SFMService instance.
|
|
12
|
+
|
|
13
|
+
Uses lru_cache to ensure one instance per worker process.
|
|
14
|
+
Safe for NetworkX backend (thread-safe reads).
|
|
15
|
+
For Neo4j backend, connection pooling handles concurrency.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
SFMService instance configured from environment settings.
|
|
19
|
+
"""
|
|
20
|
+
config = SFMServiceConfig(
|
|
21
|
+
storage_type=settings.STORAGE_TYPE,
|
|
22
|
+
graph_size_limit=settings.GRAPH_SIZE_LIMIT,
|
|
23
|
+
)
|
|
24
|
+
return SFMService(config=config)
|
api/rest/exceptions.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""HTTP exception handlers for REST API.
|
|
2
|
+
|
|
3
|
+
Note: Some status codes use raw integers (422, 413) instead of named constants
|
|
4
|
+
to avoid deprecation warnings. The new constant names (HTTP_422_UNPROCESSABLE_CONTENT,
|
|
5
|
+
HTTP_413_CONTENT_TOO_LARGE) are not yet available in Starlette 1.1.0.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from fastapi import Request, status
|
|
10
|
+
from fastapi.responses import JSONResponse
|
|
11
|
+
from fastapi.exceptions import RequestValidationError
|
|
12
|
+
|
|
13
|
+
from models.exceptions import (
|
|
14
|
+
SFMError,
|
|
15
|
+
ErrorCode,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# HTTP status code mapping for SFM error codes
|
|
20
|
+
ERROR_CODE_HTTP_STATUS = {
|
|
21
|
+
ErrorCode.NOT_FOUND_ERROR: status.HTTP_404_NOT_FOUND,
|
|
22
|
+
ErrorCode.VALIDATION_ERROR: status.HTTP_400_BAD_REQUEST,
|
|
23
|
+
ErrorCode.NODE_CREATION_ERROR: status.HTTP_400_BAD_REQUEST,
|
|
24
|
+
ErrorCode.NODE_UPDATE_ERROR: status.HTTP_400_BAD_REQUEST,
|
|
25
|
+
ErrorCode.NODE_DELETE_ERROR: status.HTTP_400_BAD_REQUEST,
|
|
26
|
+
ErrorCode.RELATIONSHIP_ERROR: status.HTTP_400_BAD_REQUEST,
|
|
27
|
+
ErrorCode.INTEGRITY_ERROR: status.HTTP_409_CONFLICT,
|
|
28
|
+
ErrorCode.QUERY_EXECUTION_ERROR: status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
29
|
+
ErrorCode.QUERY_TIMEOUT_ERROR: status.HTTP_504_GATEWAY_TIMEOUT,
|
|
30
|
+
ErrorCode.QUERY_SYNTAX_ERROR: status.HTTP_400_BAD_REQUEST,
|
|
31
|
+
ErrorCode.DATABASE_CONNECTION_ERROR: status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
32
|
+
ErrorCode.DATABASE_TRANSACTION_ERROR: status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
33
|
+
ErrorCode.DATABASE_PERSISTENCE_ERROR: status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
34
|
+
ErrorCode.PERMISSION_DENIED_ERROR: status.HTTP_403_FORBIDDEN,
|
|
35
|
+
ErrorCode.SECURITY_VALIDATION_ERROR: status.HTTP_403_FORBIDDEN,
|
|
36
|
+
ErrorCode.GRAPH_SIZE_EXCEEDED: 413, # Raw int avoids deprecation (HTTP_413_CONTENT_TOO_LARGE not yet available)
|
|
37
|
+
ErrorCode.GRAPH_OPERATION_ERROR: status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def sfm_exception_handler(request: Request, exc: SFMError) -> JSONResponse:
|
|
42
|
+
"""
|
|
43
|
+
Handle SFMError exceptions and convert to HTTP responses.
|
|
44
|
+
|
|
45
|
+
Maps SFM error codes to appropriate HTTP status codes and
|
|
46
|
+
returns structured error response.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
request: FastAPI request object
|
|
50
|
+
exc: SFMError exception instance
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
JSONResponse with error details and appropriate status code
|
|
54
|
+
"""
|
|
55
|
+
# Get HTTP status code from error code, default to 500
|
|
56
|
+
http_status = ERROR_CODE_HTTP_STATUS.get(
|
|
57
|
+
exc.error_code,
|
|
58
|
+
status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Build error response
|
|
62
|
+
error_response = {
|
|
63
|
+
"error": exc.error_code.value if hasattr(exc.error_code, "value") else str(exc.error_code),
|
|
64
|
+
"message": exc.message,
|
|
65
|
+
"context": exc.context.to_dict() if hasattr(exc.context, "to_dict") else {},
|
|
66
|
+
"remediation": exc.remediation,
|
|
67
|
+
"details": exc.details if hasattr(exc, "details") else None,
|
|
68
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return JSONResponse(
|
|
72
|
+
status_code=http_status,
|
|
73
|
+
content=error_response
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def validation_exception_handler(
|
|
78
|
+
request: Request,
|
|
79
|
+
exc: RequestValidationError
|
|
80
|
+
) -> JSONResponse:
|
|
81
|
+
"""
|
|
82
|
+
Handle Pydantic validation errors.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
request: FastAPI request object
|
|
86
|
+
exc: RequestValidationError exception
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
JSONResponse with validation error details
|
|
90
|
+
"""
|
|
91
|
+
return JSONResponse(
|
|
92
|
+
status_code=422, # Raw int avoids deprecation (HTTP_422_UNPROCESSABLE_CONTENT not yet available)
|
|
93
|
+
content={
|
|
94
|
+
"error": "VALIDATION_ERROR",
|
|
95
|
+
"message": "Request validation failed",
|
|
96
|
+
"context": {
|
|
97
|
+
"errors": exc.errors(),
|
|
98
|
+
"body": exc.body if hasattr(exc, "body") else None,
|
|
99
|
+
},
|
|
100
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
106
|
+
"""
|
|
107
|
+
Handle unexpected exceptions.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
request: FastAPI request object
|
|
111
|
+
exc: Generic exception
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
JSONResponse with generic error message
|
|
115
|
+
"""
|
|
116
|
+
return JSONResponse(
|
|
117
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
118
|
+
content={
|
|
119
|
+
"error": "INTERNAL_SERVER_ERROR",
|
|
120
|
+
"message": "An unexpected error occurred",
|
|
121
|
+
"context": {
|
|
122
|
+
"exception_type": type(exc).__name__,
|
|
123
|
+
},
|
|
124
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
125
|
+
}
|
|
126
|
+
)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Node type registry for API validation.
|
|
2
|
+
|
|
3
|
+
This module provides a centralized registry of all available SFM node types,
|
|
4
|
+
enabling validation and documentation of node_type parameters in API endpoints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Set, Dict
|
|
8
|
+
|
|
9
|
+
# Complete registry of all Node subclasses from models/
|
|
10
|
+
# Organized by domain module for maintainability
|
|
11
|
+
NODE_TYPES: Dict[str, Set[str]] = {
|
|
12
|
+
"base": {
|
|
13
|
+
"Node", # Base class from models/base_nodes.py
|
|
14
|
+
},
|
|
15
|
+
"complex_analysis": {
|
|
16
|
+
"DigraphAnalysis",
|
|
17
|
+
"CircularCausationProcess",
|
|
18
|
+
"ConflictDetection",
|
|
19
|
+
},
|
|
20
|
+
"cultural_analysis": {
|
|
21
|
+
"CeremonialInstrumentalClassification",
|
|
22
|
+
"ValueSystem",
|
|
23
|
+
"SocialBelief",
|
|
24
|
+
"CulturalAttitude",
|
|
25
|
+
},
|
|
26
|
+
"economic_analysis": {
|
|
27
|
+
"TransactionCost",
|
|
28
|
+
"CoordinationMechanism",
|
|
29
|
+
"CommonsGovernance",
|
|
30
|
+
},
|
|
31
|
+
"institutional_analysis": {
|
|
32
|
+
"InstitutionalStructure",
|
|
33
|
+
"PathDependencyAnalysis",
|
|
34
|
+
},
|
|
35
|
+
"matrix_components": {
|
|
36
|
+
"MatrixCell",
|
|
37
|
+
"SFMCriteria",
|
|
38
|
+
"SFMMatrix",
|
|
39
|
+
},
|
|
40
|
+
"meta_entities": {
|
|
41
|
+
"Scenario",
|
|
42
|
+
"ScenarioSet",
|
|
43
|
+
"ScenarioPath",
|
|
44
|
+
},
|
|
45
|
+
"methodological_framework": {
|
|
46
|
+
"InstrumentalistInquiryFramework",
|
|
47
|
+
"NormativeSystemsAnalysis",
|
|
48
|
+
"PolicyRelevanceIntegration",
|
|
49
|
+
"DatabaseIntegrationCapability",
|
|
50
|
+
},
|
|
51
|
+
"network_analysis": {
|
|
52
|
+
"CrossImpactAnalysis",
|
|
53
|
+
"DeliveryRelationship",
|
|
54
|
+
"MatrixDeliveryNetwork",
|
|
55
|
+
},
|
|
56
|
+
"policy_framework": {
|
|
57
|
+
"PolicyInstrument",
|
|
58
|
+
"ValueJudgment",
|
|
59
|
+
"ProblemSolvingSequence",
|
|
60
|
+
},
|
|
61
|
+
"social_assessment": {
|
|
62
|
+
"SocialValueAssessment",
|
|
63
|
+
"SocialFabricIndicator",
|
|
64
|
+
"SocialCost",
|
|
65
|
+
},
|
|
66
|
+
"specialized_components": {
|
|
67
|
+
"SocialIndicatorSystem",
|
|
68
|
+
"EvolutionaryPathway",
|
|
69
|
+
"SocialProvisioningMatrix",
|
|
70
|
+
},
|
|
71
|
+
"system_analysis": {
|
|
72
|
+
"SystemProperty",
|
|
73
|
+
"SystemLevelAnalysis",
|
|
74
|
+
"InstitutionalHolarchy",
|
|
75
|
+
},
|
|
76
|
+
"technology_integration": {
|
|
77
|
+
"ToolSkillTechnologyComplex",
|
|
78
|
+
"EcologicalSystem",
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_all_node_types() -> Set[str]:
|
|
84
|
+
"""
|
|
85
|
+
Get set of all valid node type names.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Set of all node type class names
|
|
89
|
+
"""
|
|
90
|
+
all_types = set()
|
|
91
|
+
for types in NODE_TYPES.values():
|
|
92
|
+
all_types.update(types)
|
|
93
|
+
return all_types
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def is_valid_node_type(node_type: str) -> bool:
|
|
97
|
+
"""
|
|
98
|
+
Check if a node type is valid.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
node_type: Node type name to validate
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
True if node_type is in the registry
|
|
105
|
+
"""
|
|
106
|
+
return node_type in get_all_node_types()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_node_types_by_domain() -> Dict[str, Set[str]]:
|
|
110
|
+
"""
|
|
111
|
+
Get node types organized by domain module.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Dictionary mapping domain module names to sets of node types
|
|
115
|
+
"""
|
|
116
|
+
return NODE_TYPES.copy()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Pre-computed set for fast validation
|
|
120
|
+
ALL_NODE_TYPES = get_all_node_types()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""REST API routers."""
|