2
2
3
3
import warnings
4
4
from pathlib import Path
5
- from typing import TYPE_CHECKING , Any , Optional
6
-
7
- import aiida
8
- from aiida import orm
9
- from aiida .common .exceptions import (
10
- CorruptStorage ,
11
- IncompatibleStorageSchema ,
12
- NotExistent ,
13
- ProfileConfigurationError ,
14
- UnreachableStorage ,
15
- )
16
- from aiida .storage .sqlite_zip .backend import SqliteZipBackend
5
+ from typing import TYPE_CHECKING , Any
6
+
17
7
from optimade .adapters import Structure
18
8
from optimade .models import DataType , EntryResource
19
- from pydantic import BaseModel , Field , model_validator
20
9
21
10
if TYPE_CHECKING :
22
- from .config import EntryConfig
23
-
24
-
25
- class AiidaEntryPath (BaseModel ):
26
- """Config to specify an AiiDA entry path."""
27
-
28
- aiida_file : Optional [str ] = Field (
29
- None , description = "AiiDA file that contains the structures."
30
- )
31
- aiida_profile : Optional [str ] = Field (
32
- None , description = "AiiDA profile that contains the structures."
33
- )
34
- aiida_group : Optional [str ] = Field (
35
- None ,
36
- description = "AiiDA group that contains the structures. 'None' assumes all StructureData nodes" ,
37
- )
11
+ from aiida import orm
38
12
39
- @model_validator (mode = "before" )
40
- @classmethod
41
- def check_file_or_profile (cls , values ):
42
- if isinstance (values , list ):
43
- # Skip validation for lists
44
- return values
45
- if not values .get ("aiida_file" ) and not values .get ("aiida_profile" ):
46
- raise ValueError ("Either 'aiida_file' or 'aiida_profile' must be defined." )
47
- if values .get ("aiida_file" ) and values .get ("aiida_profile" ):
48
- raise ValueError (
49
- "Both 'aiida_file' and 'aiida_profile' cannot be defined at the same time."
50
- )
51
- return values
13
+ from optimade_maker .config import EntryConfig
52
14
15
+ from optimade_maker .aiida_plugin .config import AiidaEntryPath
53
16
54
- class AiidaQueryItem (BaseModel ):
55
- """An item representing a step in an AiiDA query, which allows
56
- * to project properties of the current node, or
57
- * to move to a connected node in the AiiDA provenance graph.
58
- In the case of querying for connected nodes, the usual AiiDA
59
- QueryBuilder filters and edge_filters can be applied.
60
- """
61
17
62
- project : Optional [str ] = Field (
63
- None , description = "The AiiDA attribute to project in the query."
64
- )
65
- incoming_node : Optional [str ] = Field (
66
- None , description = "Query for an incoming node of the specified type."
67
- )
68
- outgoing_node : Optional [str ] = Field (
69
- None , description = "Query for an outgoing node of the specified type."
70
- )
71
- filters : Optional [dict [Any , Any ]] = Field (
72
- None , description = "filters passed to AiiDA QueryBuilder."
73
- )
74
- edge_filters : Optional [dict [Any , Any ]] = Field (
75
- None , description = "edge_filters passed to AiiDA QueryBuilder."
76
- )
18
+ def _check_aiida_import ():
19
+ """Check if AiiDA is installed and raise an ImportError if not."""
20
+ try :
21
+ import aiida
77
22
78
- @model_validator (mode = "before" )
79
- @classmethod
80
- def check_required_fields (cls , values ):
81
- if not any (
82
- values .get (field ) for field in ["project" , "incoming_node" , "outgoing_node" ]
83
- ):
84
- raise ValueError (
85
- "One of 'project', 'incoming_node', or 'outgoing_node' must be defined."
86
- )
87
- if values .get ("filters" ) or values .get ("edge_filters" ):
88
- if not any (
89
- values .get (field ) for field in ["incoming_node" , "outgoing_node" ]
90
- ):
91
- raise ValueError (
92
- "'filters' and 'edge_filters' can only be defined for 'incoming_node' or 'outgoing_node'."
93
- )
94
- return values
23
+ return aiida
24
+ except ImportError :
25
+ raise ImportError (
26
+ "The AiiDA plugin requires the `aiida-core` package to be installed. "
27
+ "Please install it with `pip install aiida-core`."
28
+ )
95
29
96
30
97
31
def query_for_aiida_structures (
98
32
structure_group : str | None = None ,
99
- ) -> dict [str , orm .StructureData ]:
33
+ ) -> dict [str , " orm.StructureData" ]:
100
34
"""
101
35
Query for all aiida structures in the specified group
102
36
(or all structures if no group specified)
103
37
"""
38
+ _check_aiida_import ()
39
+ from aiida import orm
40
+ from aiida .common .exceptions import (
41
+ NotExistent ,
42
+ )
43
+
104
44
qb = orm .QueryBuilder ()
45
+
105
46
if structure_group :
106
47
# check that the AiiDA group exists
107
48
try :
@@ -123,6 +64,8 @@ def query_for_aiida_properties(
123
64
Query for structure properties based on the custom aiida query format
124
65
specified in the yaml file.
125
66
"""
67
+ aiida = _check_aiida_import ()
68
+ from aiida import orm
126
69
127
70
# query for the structures
128
71
qb = orm .QueryBuilder ()
@@ -185,6 +128,14 @@ def query_for_aiida_properties(
185
128
186
129
187
130
def get_aiida_profile_from_file (path : Path ):
131
+ _check_aiida_import ()
132
+ from aiida .common .exceptions import (
133
+ CorruptStorage ,
134
+ IncompatibleStorageSchema ,
135
+ UnreachableStorage ,
136
+ )
137
+ from aiida .storage .sqlite_zip .backend import SqliteZipBackend
138
+
188
139
if not path .exists ():
189
140
raise FileNotFoundError (f"File not found: { path } " )
190
141
@@ -200,6 +151,8 @@ def get_aiida_profile_from_file(path: Path):
200
151
def convert_aiida_structure_to_optimade (
201
152
aiida_structure : orm .StructureData ,
202
153
) -> dict :
154
+ _check_aiida_import ()
155
+
203
156
try :
204
157
ase_structure = aiida_structure .get_ase ()
205
158
optimade_entry = Structure .ingest_from (ase_structure ).entry .model_dump ()
@@ -242,6 +195,9 @@ def construct_entries_from_aiida(
242
195
entry_config : EntryConfig ,
243
196
provider_prefix : str ,
244
197
) -> dict [str , dict ]:
198
+ aiida = _check_aiida_import ()
199
+ from aiida .common .exceptions import ProfileConfigurationError
200
+
245
201
if not isinstance (entry_config .entry_paths , AiidaEntryPath ):
246
202
raise RuntimeError ("entry_paths is not AiiDA-specific." )
247
203
if entry_config .entry_type != "structures" :
@@ -278,4 +234,5 @@ def construct_entries_from_aiida(
278
234
optimade_entries [uuid ]["attributes" ][prop_name ] = (
279
235
_convert_property_type (prop_def .type , prop )
280
236
)
237
+
281
238
return optimade_entries
0 commit comments