linkml 1.9.5rc1__py3-none-any.whl → 1.9.5rc2__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.
- linkml/cli/main.py +1 -1
- linkml/converter/__init__.py +0 -0
- linkml/generators/owlgen.py +9 -1
- linkml/generators/rustgen/build.py +3 -0
- linkml/generators/rustgen/cli.py +19 -1
- linkml/generators/rustgen/rustgen.py +169 -21
- linkml/generators/rustgen/template.py +51 -6
- linkml/generators/rustgen/templates/Cargo.toml.jinja +2 -2
- linkml/generators/rustgen/templates/anything.rs.jinja +8 -1
- linkml/generators/rustgen/templates/as_key_value.rs.jinja +33 -3
- linkml/generators/rustgen/templates/enum.rs.jinja +16 -0
- linkml/generators/rustgen/templates/file.rs.jinja +13 -0
- linkml/generators/rustgen/templates/lib_shim.rs.jinja +52 -0
- linkml/generators/rustgen/templates/poly_trait_property_impl.rs.jinja +3 -1
- linkml/generators/rustgen/templates/property.rs.jinja +12 -3
- linkml/generators/rustgen/templates/serde_utils.rs.jinja +186 -6
- linkml/generators/rustgen/templates/slot_range_as_union.rs.jinja +3 -0
- linkml/generators/rustgen/templates/struct.rs.jinja +6 -0
- linkml/generators/rustgen/templates/struct_or_subtype_enum.rs.jinja +4 -1
- linkml/generators/rustgen/templates/stub_gen.rs.jinja +71 -0
- linkml/generators/rustgen/templates/stub_utils.rs.jinja +76 -0
- linkml/generators/yarrrmlgen.py +20 -4
- linkml/utils/schema_builder.py +2 -0
- {linkml-1.9.5rc1.dist-info → linkml-1.9.5rc2.dist-info}/METADATA +1 -1
- {linkml-1.9.5rc1.dist-info → linkml-1.9.5rc2.dist-info}/RECORD +29 -25
- {linkml-1.9.5rc1.dist-info → linkml-1.9.5rc2.dist-info}/entry_points.txt +1 -1
- /linkml/{utils/converter.py → converter/cli.py} +0 -0
- {linkml-1.9.5rc1.dist-info → linkml-1.9.5rc2.dist-info}/WHEEL +0 -0
- {linkml-1.9.5rc1.dist-info → linkml-1.9.5rc2.dist-info}/licenses/LICENSE +0 -0
|
@@ -17,7 +17,9 @@ impl serde_utils::InlinedPair for {{ name }} {
|
|
|
17
17
|
Value::Map(m) => m,
|
|
18
18
|
_ => return Err("ClassDefinition must be a mapping".into()),
|
|
19
19
|
};
|
|
20
|
-
|
|
20
|
+
let key_value = serde_value::to_value(k.clone())
|
|
21
|
+
.map_err(|e| format!("unable to serialize key: {}", e))?;
|
|
22
|
+
map.insert(Value::String("{{key_property_name}}".into()), key_value);
|
|
21
23
|
let de = Value::Map(map).into_deserializer();
|
|
22
24
|
match serde_path_to_error::deserialize(de) {
|
|
23
25
|
Ok(ok) => Ok(ok),
|
|
@@ -29,7 +31,9 @@ impl serde_utils::InlinedPair for {{ name }} {
|
|
|
29
31
|
{% if can_convert_from_primitive %}
|
|
30
32
|
fn from_pair_simple(k: Self::Key, v: Value) -> Result<Self,Self::Error> {
|
|
31
33
|
let mut map: BTreeMap<Value, Value> = BTreeMap::new();
|
|
32
|
-
|
|
34
|
+
let key_value = serde_value::to_value(k.clone())
|
|
35
|
+
.map_err(|e| format!("unable to serialize key: {}", e))?;
|
|
36
|
+
map.insert(Value::String("{{ key_property_name }}".into()), key_value);
|
|
33
37
|
map.insert(Value::String("{{ value_property_name }}".into()), v);
|
|
34
38
|
let de = Value::Map(map).into_deserializer();
|
|
35
39
|
match serde_path_to_error::deserialize(de) {
|
|
@@ -40,7 +44,9 @@ impl serde_utils::InlinedPair for {{ name }} {
|
|
|
40
44
|
{% elif can_convert_from_empty %}
|
|
41
45
|
fn from_pair_simple(k: Self::Key, _v: Value) -> Result<Self,Self::Error> {
|
|
42
46
|
let mut map: BTreeMap<Value, Value> = BTreeMap::new();
|
|
43
|
-
|
|
47
|
+
let key_value = serde_value::to_value(k.clone())
|
|
48
|
+
.map_err(|e| format!("unable to serialize key: {}", e))?;
|
|
49
|
+
map.insert(Value::String("{{ key_property_name }}".into()), key_value);
|
|
44
50
|
let de = Value::Map(map).into_deserializer();
|
|
45
51
|
match serde_path_to_error::deserialize(de) {
|
|
46
52
|
Ok(ok) => Ok(ok),
|
|
@@ -53,4 +59,28 @@ impl serde_utils::InlinedPair for {{ name }} {
|
|
|
53
59
|
Err("Cannot create a {{name}} from a primitive value!".into())
|
|
54
60
|
{% endif %}
|
|
55
61
|
}
|
|
62
|
+
|
|
63
|
+
{% if can_convert_from_primitive %}
|
|
64
|
+
fn simple_value(&self) -> Option<&Self::Value> {
|
|
65
|
+
{% if value_property_optional %}
|
|
66
|
+
self.{{ value_property_name }}.as_ref()
|
|
67
|
+
{% else %}
|
|
68
|
+
Some(&self.{{ value_property_name }})
|
|
69
|
+
{% endif %}
|
|
70
|
+
}
|
|
71
|
+
{% endif %}
|
|
72
|
+
|
|
73
|
+
fn compact_value(&self) -> Option<Value> {
|
|
74
|
+
let value = match serde_value::to_value(self) {
|
|
75
|
+
Ok(v) => v,
|
|
76
|
+
Err(_) => return None,
|
|
77
|
+
};
|
|
78
|
+
match value {
|
|
79
|
+
Value::Map(mut map) => {
|
|
80
|
+
map.remove(&Value::String("{{ key_property_name }}".into()));
|
|
81
|
+
Some(Value::Map(map))
|
|
82
|
+
}
|
|
83
|
+
_ => None,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
56
86
|
}
|
|
@@ -52,3 +52,19 @@ impl<'py> FromPyObject<'py> for {{ name }} {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
#[cfg(feature = "stubgen")]
|
|
57
|
+
impl ::pyo3_stub_gen::PyStubType for {{ name }} {
|
|
58
|
+
fn type_output() -> ::pyo3_stub_gen::TypeInfo {
|
|
59
|
+
{% set literal_values = items | map(attribute='text_literal') | list %}
|
|
60
|
+
{% set escaped_values = literal_values | map('replace', "'", "\\'" ) | list %}
|
|
61
|
+
{% if escaped_values %}
|
|
62
|
+
::pyo3_stub_gen::TypeInfo::with_module(
|
|
63
|
+
"typing.Literal['{{ escaped_values | join("', '") }}']",
|
|
64
|
+
"typing".into(),
|
|
65
|
+
)
|
|
66
|
+
{% else %}
|
|
67
|
+
::pyo3_stub_gen::TypeInfo::ident("typing.Any")
|
|
68
|
+
{% endif %}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -11,6 +11,10 @@ mod serde_utils;
|
|
|
11
11
|
pub mod poly;
|
|
12
12
|
pub mod poly_containers;
|
|
13
13
|
{% endif %}
|
|
14
|
+
{% if stubgen %}
|
|
15
|
+
#[cfg(feature = "stubgen")]
|
|
16
|
+
pub mod stub_utils;
|
|
17
|
+
{% endif %}
|
|
14
18
|
|
|
15
19
|
{{ imports }}
|
|
16
20
|
|
|
@@ -48,12 +52,21 @@ fn overwrite_except_none<T>(left: &mut Option<T>, right: Option<T>) {
|
|
|
48
52
|
}
|
|
49
53
|
{% endif %}
|
|
50
54
|
|
|
55
|
+
{% if stubgen %}
|
|
56
|
+
#[cfg(feature = "stubgen")]
|
|
57
|
+
define_stub_info_gatherer!(stub_info);
|
|
58
|
+
{% endif %}
|
|
59
|
+
|
|
51
60
|
|
|
52
61
|
{% if pyo3 %}
|
|
53
62
|
#[cfg(feature = "pyo3")]
|
|
63
|
+
{% if handwritten_lib %}
|
|
64
|
+
pub fn register_pymodule(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
65
|
+
{% else %}
|
|
54
66
|
#[pymodule]
|
|
55
67
|
#[pyo3(name="{{ name }}")]
|
|
56
68
|
fn {{ name }}(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
69
|
+
{% endif %}
|
|
57
70
|
{% for s in pyclass_struct_names %}
|
|
58
71
|
m.add_class::<{{ s }}>()?;
|
|
59
72
|
{% endfor %}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//! This file will only be autogenerated once, so it is safe to manually edit (unlike the other generated files)
|
|
2
|
+
//! If you want to re-generate it, just delete it and rerun rust generator.
|
|
3
|
+
|
|
4
|
+
pub mod generated;
|
|
5
|
+
|
|
6
|
+
pub use generated::*;
|
|
7
|
+
pub use chrono::NaiveDate;
|
|
8
|
+
pub use chrono::NaiveDateTime;
|
|
9
|
+
|
|
10
|
+
{% if root_struct_name %}
|
|
11
|
+
#[cfg(feature = "serde")]
|
|
12
|
+
/// Example helper that loads a YAML document into the root class. Edit or extend as needed.
|
|
13
|
+
pub fn load_yaml_{{ root_struct_fn_snake }}<P>(
|
|
14
|
+
path: P,
|
|
15
|
+
) -> Result<generated::{{ root_struct_name }}, Box<dyn std::error::Error + Send + Sync>>
|
|
16
|
+
where
|
|
17
|
+
P: AsRef<std::path::Path>,
|
|
18
|
+
{
|
|
19
|
+
let file = std::fs::File::open(path)?;
|
|
20
|
+
let reader = std::io::BufReader::new(file);
|
|
21
|
+
let parsed = serde_yml::from_reader(reader)?;
|
|
22
|
+
Ok(parsed)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[cfg(all(feature = "pyo3", feature = "serde"))]
|
|
26
|
+
#[pyfunction(name = "load_yaml_{{ root_struct_fn_snake }}")]
|
|
27
|
+
fn load_yaml_{{ root_struct_fn_snake }}_py(path: &str) -> PyResult<generated::{{ root_struct_name }}> {
|
|
28
|
+
load_yaml_{{ root_struct_fn_snake }}(path)
|
|
29
|
+
.map_err(|err| PyErr::new::<pyo3::exceptions::PyIOError, _>(err.to_string()))
|
|
30
|
+
}
|
|
31
|
+
{% endif %}
|
|
32
|
+
|
|
33
|
+
{% if pyo3 %}
|
|
34
|
+
#[cfg(feature = "pyo3")]
|
|
35
|
+
use pyo3::prelude::*;
|
|
36
|
+
#[cfg(all(feature = "pyo3", feature = "serde"))]
|
|
37
|
+
use pyo3::wrap_pyfunction;
|
|
38
|
+
|
|
39
|
+
#[cfg(feature = "pyo3")]
|
|
40
|
+
#[pymodule]
|
|
41
|
+
#[pyo3(name="{{ module_name }}")]
|
|
42
|
+
fn {{ module_name }}(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
43
|
+
generated::register_pymodule(m)?;
|
|
44
|
+
{% if root_struct_name %}
|
|
45
|
+
#[cfg(all(feature = "serde", feature = "pyo3"))]
|
|
46
|
+
{
|
|
47
|
+
m.add_function(wrap_pyfunction!(load_yaml_{{ root_struct_fn_snake }}_py, m)?)?;
|
|
48
|
+
}
|
|
49
|
+
{% endif %}
|
|
50
|
+
Ok(())
|
|
51
|
+
}
|
|
52
|
+
{% endif %}
|
|
@@ -118,7 +118,9 @@
|
|
|
118
118
|
return Some(&self.{{ name }});
|
|
119
119
|
{% endif %}
|
|
120
120
|
{% else %}
|
|
121
|
-
{% if
|
|
121
|
+
{% if ct == 'list' or ct == 'mapping' %}
|
|
122
|
+
return &self.{{ name }};
|
|
123
|
+
{% elif is_copy %}
|
|
122
124
|
return self.{{ name }};
|
|
123
125
|
{% elif type_getter == "&'a str" or type_getter == "&str" %}
|
|
124
126
|
return &self.{{ name }}[..];
|
|
@@ -3,12 +3,21 @@
|
|
|
3
3
|
{% endif %}
|
|
4
4
|
{%- if is_key_value -%}
|
|
5
5
|
{% if container_mode == "list" and inline_mode == "inline" %}
|
|
6
|
-
#[cfg_attr(feature = "serde", serde(
|
|
6
|
+
#[cfg_attr(feature = "serde", serde(
|
|
7
|
+
deserialize_with = "serde_utils::deserialize_inlined_dict_list{% if optional %}_optional{% endif %}{% if recursive %}_box{% endif %}",
|
|
8
|
+
serialize_with = "serde_utils::serialize_inlined_dict_list{% if optional %}_optional{% endif %}"
|
|
9
|
+
))]
|
|
7
10
|
{% elif container_mode == "mapping" and inline_mode == "inline" %}
|
|
8
|
-
#[cfg_attr(feature = "serde", serde(
|
|
11
|
+
#[cfg_attr(feature = "serde", serde(
|
|
12
|
+
deserialize_with = "serde_utils::deserialize_inlined_dict_map{% if optional %}_optional{% endif %}{% if recursive %}_box{% endif %}",
|
|
13
|
+
serialize_with = "serde_utils::serialize_inlined_dict_map{% if optional %}_optional{% endif %}"
|
|
14
|
+
))]
|
|
9
15
|
{% endif %}
|
|
10
16
|
{% elif container_mode == "list" and inline_mode == "primitive" %}
|
|
11
|
-
#[cfg_attr(feature = "serde", serde(
|
|
17
|
+
#[cfg_attr(feature = "serde", serde(
|
|
18
|
+
deserialize_with = "serde_utils::deserialize_primitive_list_or_single_value{% if optional %}_optional{% endif %}",
|
|
19
|
+
serialize_with = "serde_utils::serialize_primitive_list_or_single_value{% if optional %}_optional{% endif %}"
|
|
20
|
+
))]
|
|
12
21
|
{% endif -%}
|
|
13
22
|
{% if hasdefault %}
|
|
14
23
|
#[cfg_attr(feature = "serde", serde(default))]
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
#[cfg(feature = "serde")]
|
|
2
|
-
use serde::{
|
|
2
|
+
use serde::{
|
|
3
|
+
de::{DeserializeOwned, IntoDeserializer},
|
|
4
|
+
Deserialize,
|
|
5
|
+
Deserializer,
|
|
6
|
+
Serialize,
|
|
7
|
+
Serializer,
|
|
8
|
+
};
|
|
3
9
|
#[cfg(feature = "serde")]
|
|
4
10
|
use serde::de::Error;
|
|
5
11
|
#[cfg(feature = "serde")]
|
|
6
|
-
use serde_value::{Value, ValueDeserializer};
|
|
12
|
+
use serde_value::{to_value, Value, ValueDeserializer};
|
|
7
13
|
#[cfg(feature = "serde")]
|
|
8
14
|
use std::collections::{BTreeMap, HashMap};
|
|
9
15
|
#[cfg(all(feature = "serde", feature = "pyo3"))]
|
|
@@ -20,6 +26,14 @@ pub trait InlinedPair: Sized {
|
|
|
20
26
|
fn from_pair_mapping(k: Self::Key, v: Value) -> Result<Self,Self::Error>;
|
|
21
27
|
fn from_pair_simple(k: Self::Key, v: Value) -> Result<Self,Self::Error>;
|
|
22
28
|
fn extract_key(&self) -> &Self::Key;
|
|
29
|
+
|
|
30
|
+
fn simple_value(&self) -> Option<&Self::Value> {
|
|
31
|
+
None
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fn compact_value(&self) -> Option<Value> {
|
|
35
|
+
None
|
|
36
|
+
}
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
#[cfg(feature = "serde")]
|
|
@@ -46,6 +60,14 @@ where
|
|
|
46
60
|
T::extract_key(self)
|
|
47
61
|
}
|
|
48
62
|
|
|
63
|
+
fn simple_value(&self) -> Option<&Self::Value> {
|
|
64
|
+
self.as_ref().simple_value()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fn compact_value(&self) -> Option<Value> {
|
|
68
|
+
self.as_ref().compact_value()
|
|
69
|
+
}
|
|
70
|
+
|
|
49
71
|
}
|
|
50
72
|
|
|
51
73
|
#[cfg(feature = "serde")]
|
|
@@ -176,8 +198,12 @@ where
|
|
|
176
198
|
}
|
|
177
199
|
|
|
178
200
|
pub fn deserialize_primitive_list_or_single_value<'de, D, T>(
|
|
179
|
-
deserializer:
|
|
180
|
-
) -> Result<Vec<T>, D::Error>
|
|
201
|
+
deserializer: D
|
|
202
|
+
) -> Result<Vec<T>, D::Error>
|
|
203
|
+
where
|
|
204
|
+
D: Deserializer<'de>,
|
|
205
|
+
T: Deserialize<'de>,
|
|
206
|
+
{
|
|
181
207
|
let ast: Value = Value::deserialize(deserializer)?;
|
|
182
208
|
match ast {
|
|
183
209
|
Value::Seq(seq) => {
|
|
@@ -197,8 +223,12 @@ pub fn deserialize_primitive_list_or_single_value<'de, D, T>(
|
|
|
197
223
|
|
|
198
224
|
|
|
199
225
|
pub fn deserialize_primitive_list_or_single_value_optional<'de, D, T>(
|
|
200
|
-
deserializer:
|
|
201
|
-
) -> Result<Option<Vec<T>>, D::Error>
|
|
226
|
+
deserializer: D
|
|
227
|
+
) -> Result<Option<Vec<T>>, D::Error>
|
|
228
|
+
where
|
|
229
|
+
D: Deserializer<'de>,
|
|
230
|
+
T: Deserialize<'de>,
|
|
231
|
+
{
|
|
202
232
|
let ast: Value = Value::deserialize(deserializer)?;
|
|
203
233
|
match ast {
|
|
204
234
|
Value::Unit => Ok(None),
|
|
@@ -274,6 +304,146 @@ fn py_any_to_value(bound: &Bound<'_, PyAny>) -> PyResult<Value> {
|
|
|
274
304
|
))
|
|
275
305
|
}
|
|
276
306
|
|
|
307
|
+
#[cfg(feature = "serde")]
|
|
308
|
+
#[allow(dead_code)]
|
|
309
|
+
pub fn serialize_inlined_dict_map<S, T>(
|
|
310
|
+
value: &HashMap<T::Key, T>,
|
|
311
|
+
serializer: S,
|
|
312
|
+
) -> Result<S::Ok, S::Error>
|
|
313
|
+
where
|
|
314
|
+
S: Serializer,
|
|
315
|
+
T: InlinedPair + Serialize,
|
|
316
|
+
T::Key: Serialize + Clone + Ord,
|
|
317
|
+
T::Value: Serialize,
|
|
318
|
+
{
|
|
319
|
+
let mut ordered: BTreeMap<T::Key, &T> = BTreeMap::new();
|
|
320
|
+
for (k, v) in value.iter() {
|
|
321
|
+
ordered.insert(k.clone(), v);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let mut as_values: BTreeMap<T::Key, Value> = BTreeMap::new();
|
|
325
|
+
for (k, v) in ordered.iter() {
|
|
326
|
+
if let Some(simple) = v.simple_value() {
|
|
327
|
+
let val = to_value(simple)
|
|
328
|
+
.map_err(|e| <S::Error as serde::ser::Error>::custom(e))?;
|
|
329
|
+
as_values.insert((*k).clone(), val);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if let Some(compact) = v.compact_value() {
|
|
333
|
+
as_values.insert((*k).clone(), compact);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
let val = to_value(*v)
|
|
337
|
+
.map_err(|e| <S::Error as serde::ser::Error>::custom(e))?;
|
|
338
|
+
as_values.insert((*k).clone(), val);
|
|
339
|
+
}
|
|
340
|
+
as_values.serialize(serializer)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
#[cfg(feature = "serde")]
|
|
344
|
+
#[allow(dead_code)]
|
|
345
|
+
pub fn serialize_inlined_dict_map_optional<S, T>(
|
|
346
|
+
value: &Option<HashMap<T::Key, T>>,
|
|
347
|
+
serializer: S,
|
|
348
|
+
) -> Result<S::Ok, S::Error>
|
|
349
|
+
where
|
|
350
|
+
S: Serializer,
|
|
351
|
+
T: InlinedPair + Serialize,
|
|
352
|
+
T::Key: Serialize + Clone + Ord,
|
|
353
|
+
T::Value: Serialize,
|
|
354
|
+
{
|
|
355
|
+
match value {
|
|
356
|
+
Some(map) => serialize_inlined_dict_map(map, serializer),
|
|
357
|
+
None => serializer.serialize_none(),
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
#[cfg(feature = "serde")]
|
|
362
|
+
#[allow(dead_code)]
|
|
363
|
+
pub fn serialize_inlined_dict_list<S, T>(
|
|
364
|
+
value: &Vec<T>,
|
|
365
|
+
serializer: S,
|
|
366
|
+
) -> Result<S::Ok, S::Error>
|
|
367
|
+
where
|
|
368
|
+
S: Serializer,
|
|
369
|
+
T: InlinedPair + Serialize,
|
|
370
|
+
T::Key: Serialize + Clone + Ord,
|
|
371
|
+
T::Value: Serialize,
|
|
372
|
+
{
|
|
373
|
+
let mut ordered: BTreeMap<T::Key, &T> = BTreeMap::new();
|
|
374
|
+
for item in value.iter() {
|
|
375
|
+
ordered.insert(item.extract_key().clone(), item);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let mut as_values: BTreeMap<T::Key, Value> = BTreeMap::new();
|
|
379
|
+
for (k, v) in ordered.iter() {
|
|
380
|
+
if let Some(simple) = v.simple_value() {
|
|
381
|
+
let val = to_value(simple)
|
|
382
|
+
.map_err(|e| <S::Error as serde::ser::Error>::custom(e))?;
|
|
383
|
+
as_values.insert((*k).clone(), val);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if let Some(compact) = v.compact_value() {
|
|
387
|
+
as_values.insert((*k).clone(), compact);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
let val = to_value(*v)
|
|
391
|
+
.map_err(|e| <S::Error as serde::ser::Error>::custom(e))?;
|
|
392
|
+
as_values.insert((*k).clone(), val);
|
|
393
|
+
}
|
|
394
|
+
as_values.serialize(serializer)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
#[cfg(feature = "serde")]
|
|
398
|
+
#[allow(dead_code)]
|
|
399
|
+
pub fn serialize_inlined_dict_list_optional<S, T>(
|
|
400
|
+
value: &Option<Vec<T>>,
|
|
401
|
+
serializer: S,
|
|
402
|
+
) -> Result<S::Ok, S::Error>
|
|
403
|
+
where
|
|
404
|
+
S: Serializer,
|
|
405
|
+
T: InlinedPair + Serialize,
|
|
406
|
+
T::Key: Serialize + Clone + Ord,
|
|
407
|
+
T::Value: Serialize,
|
|
408
|
+
{
|
|
409
|
+
match value {
|
|
410
|
+
Some(items) => serialize_inlined_dict_list(items, serializer),
|
|
411
|
+
None => serializer.serialize_none(),
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
#[cfg(feature = "serde")]
|
|
416
|
+
#[allow(dead_code)]
|
|
417
|
+
pub fn serialize_primitive_list_or_single_value<S, T>(
|
|
418
|
+
value: &Vec<T>,
|
|
419
|
+
serializer: S,
|
|
420
|
+
) -> Result<S::Ok, S::Error>
|
|
421
|
+
where
|
|
422
|
+
S: Serializer,
|
|
423
|
+
T: Serialize,
|
|
424
|
+
{
|
|
425
|
+
match value.as_slice() {
|
|
426
|
+
[single] => single.serialize(serializer),
|
|
427
|
+
_ => value.serialize(serializer),
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
#[cfg(feature = "serde")]
|
|
432
|
+
#[allow(dead_code)]
|
|
433
|
+
pub fn serialize_primitive_list_or_single_value_optional<S, T>(
|
|
434
|
+
value: &Option<Vec<T>>,
|
|
435
|
+
serializer: S,
|
|
436
|
+
) -> Result<S::Ok, S::Error>
|
|
437
|
+
where
|
|
438
|
+
S: Serializer,
|
|
439
|
+
T: Serialize,
|
|
440
|
+
{
|
|
441
|
+
match value {
|
|
442
|
+
Some(v) => serialize_primitive_list_or_single_value(v, serializer),
|
|
443
|
+
None => serializer.serialize_none(),
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
277
447
|
#[cfg(all(feature = "serde", feature = "pyo3"))]
|
|
278
448
|
pub fn deserialize_py_any<'py, T>(bound: &Bound<'py, PyAny>) -> PyResult<T>
|
|
279
449
|
where
|
|
@@ -299,6 +469,16 @@ impl<T> PyValue<T> {
|
|
|
299
469
|
}
|
|
300
470
|
}
|
|
301
471
|
|
|
472
|
+
#[cfg(all(feature = "pyo3", feature = "stubgen"))]
|
|
473
|
+
impl<T> ::pyo3_stub_gen::PyStubType for PyValue<T>
|
|
474
|
+
where
|
|
475
|
+
T: ::pyo3_stub_gen::PyStubType,
|
|
476
|
+
{
|
|
477
|
+
fn type_output() -> ::pyo3_stub_gen::TypeInfo {
|
|
478
|
+
T::type_output()
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
302
482
|
#[cfg(all(feature = "pyo3", feature = "serde"))]
|
|
303
483
|
impl<'py, T> FromPyObject<'py> for PyValue<T>
|
|
304
484
|
where
|
|
@@ -8,6 +8,9 @@ pub type AnyValue = Anything;
|
|
|
8
8
|
{% endif %}
|
|
9
9
|
#[derive(Debug, Clone, PartialEq)]
|
|
10
10
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
11
|
+
{% if stubgen %}
|
|
12
|
+
#[cfg_attr(feature = "stubgen", gen_stub_pyclass)]
|
|
13
|
+
{% endif %}
|
|
11
14
|
#[cfg_attr(feature = "pyo3", pyclass(subclass, get_all, set_all))]
|
|
12
15
|
{% if generate_merge %}
|
|
13
16
|
#[derive(Merge)]
|
|
@@ -22,6 +25,9 @@ pub struct {{ name }} {
|
|
|
22
25
|
}
|
|
23
26
|
{% if properties | length > 0 %}
|
|
24
27
|
#[cfg(feature = "pyo3")]
|
|
28
|
+
{% if stubgen %}
|
|
29
|
+
#[cfg_attr(feature = "stubgen", gen_stub_pymethods)]
|
|
30
|
+
{% endif %}
|
|
25
31
|
#[pymethods]
|
|
26
32
|
impl {{ name }} {
|
|
27
33
|
#[new]
|
|
@@ -75,7 +75,7 @@ impl<'py> FromPyObject<'py> for Box<{{ enum_name }}> {
|
|
|
75
75
|
{% if as_key_value %}
|
|
76
76
|
#[cfg(feature = "serde")]
|
|
77
77
|
impl serde_utils::InlinedPair for {{enum_name}} {
|
|
78
|
-
type Key =
|
|
78
|
+
type Key = {{ key_property_type }};
|
|
79
79
|
type Value = serde_value::Value;
|
|
80
80
|
type Error = String;
|
|
81
81
|
|
|
@@ -106,3 +106,6 @@ impl serde_utils::InlinedPair for {{enum_name}} {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
{% endif %}
|
|
109
|
+
|
|
110
|
+
#[cfg(feature = "stubgen")]
|
|
111
|
+
::pyo3_stub_gen::impl_stub_type!({{ enum_name }} = {{ struct_names | join(' | ') }});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#[cfg(feature = "stubgen")]
|
|
2
|
+
use {{ crate_module }}::stub_utils;
|
|
3
|
+
|
|
4
|
+
#[cfg(feature = "stubgen")]
|
|
5
|
+
fn main() -> pyo3_stub_gen::Result<()> {
|
|
6
|
+
let check_only = std::env::args().skip(1).any(|arg| arg == "--check");
|
|
7
|
+
let stub = {{ crate_name }}::stub_info()?;
|
|
8
|
+
|
|
9
|
+
if check_only {
|
|
10
|
+
check_stubs(&stub)
|
|
11
|
+
} else {
|
|
12
|
+
stub.generate()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#[cfg(feature = "stubgen")]
|
|
17
|
+
fn check_stubs(stub: &pyo3_stub_gen::StubInfo) -> pyo3_stub_gen::Result<()> {
|
|
18
|
+
use std::fs;
|
|
19
|
+
use std::io::ErrorKind;
|
|
20
|
+
|
|
21
|
+
let mut issues = Vec::new();
|
|
22
|
+
|
|
23
|
+
for (name, module) in &stub.modules {
|
|
24
|
+
let module_path = stub_utils::normalize_stub_module(name)?;
|
|
25
|
+
let mut dest = stub.python_root.join(&module_path);
|
|
26
|
+
if module.submodules.is_empty() {
|
|
27
|
+
dest.set_extension("pyi");
|
|
28
|
+
} else {
|
|
29
|
+
dest.push("__init__.pyi");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let expected = module.to_string();
|
|
33
|
+
match fs::read_to_string(&dest) {
|
|
34
|
+
Ok(actual) => {
|
|
35
|
+
if actual != expected {
|
|
36
|
+
issues.push(format!(
|
|
37
|
+
"updated content differs for `{}` (module `{name}`)",
|
|
38
|
+
dest.display()
|
|
39
|
+
));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
Err(err) if err.kind() == ErrorKind::NotFound => {
|
|
43
|
+
issues.push(format!(
|
|
44
|
+
"missing stub file `{}` for module `{name}`",
|
|
45
|
+
dest.display()
|
|
46
|
+
));
|
|
47
|
+
}
|
|
48
|
+
Err(err) => return Err(err.into()),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if issues.is_empty() {
|
|
53
|
+
Ok(())
|
|
54
|
+
} else {
|
|
55
|
+
let mut msg = String::from("Stub files are out of date:\n");
|
|
56
|
+
for issue in issues {
|
|
57
|
+
msg.push_str(" - ");
|
|
58
|
+
msg.push_str(&issue);
|
|
59
|
+
msg.push('\n');
|
|
60
|
+
}
|
|
61
|
+
msg.push_str("Run `cargo run --bin stub_gen --features stubgen` to regenerate.");
|
|
62
|
+
Err(std::io::Error::new(ErrorKind::Other, msg).into())
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#[cfg(not(feature = "stubgen"))]
|
|
67
|
+
fn main() {
|
|
68
|
+
eprintln!(
|
|
69
|
+
"Enable the `stubgen` feature (alongside `pyo3`) to run this generator."
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
use std::io::{Error, ErrorKind};
|
|
2
|
+
use std::path::{Component, PathBuf};
|
|
3
|
+
|
|
4
|
+
/// Convert a stub module name into a relative filesystem path.
|
|
5
|
+
///
|
|
6
|
+
/// The generator currently expects module names that mirror Python package
|
|
7
|
+
/// semantics (``foo.bar``) with optional hyphens. Hyphens are replaced with
|
|
8
|
+
/// underscores and dots map to directory separators. The helper rejects
|
|
9
|
+
/// absolute paths, traversal markers, or characters that cannot appear in
|
|
10
|
+
/// stub files so we never escape the designated stub root.
|
|
11
|
+
pub fn normalize_stub_module(name: &str) -> std::io::Result<PathBuf> {
|
|
12
|
+
if name.is_empty() {
|
|
13
|
+
return Err(Error::new(
|
|
14
|
+
ErrorKind::InvalidInput,
|
|
15
|
+
"module name may not be empty",
|
|
16
|
+
));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let normalized = name.replace('-', "_");
|
|
20
|
+
let path_str = normalized.replace('.', "/");
|
|
21
|
+
let path = PathBuf::from(&path_str);
|
|
22
|
+
|
|
23
|
+
if path.is_absolute() {
|
|
24
|
+
return Err(Error::new(
|
|
25
|
+
ErrorKind::InvalidInput,
|
|
26
|
+
format!("absolute module path `{name}` not allowed"),
|
|
27
|
+
));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for component in path.components() {
|
|
31
|
+
match component {
|
|
32
|
+
Component::Normal(part) => {
|
|
33
|
+
let segment = part.to_string_lossy();
|
|
34
|
+
if segment.is_empty() {
|
|
35
|
+
return Err(Error::new(
|
|
36
|
+
ErrorKind::InvalidInput,
|
|
37
|
+
format!("module name `{name}` contains an empty segment"),
|
|
38
|
+
));
|
|
39
|
+
}
|
|
40
|
+
if segment == "." || segment == ".." {
|
|
41
|
+
return Err(Error::new(
|
|
42
|
+
ErrorKind::InvalidInput,
|
|
43
|
+
format!(
|
|
44
|
+
"module name `{name}` contains disallowed segment `{segment}`"
|
|
45
|
+
),
|
|
46
|
+
));
|
|
47
|
+
}
|
|
48
|
+
if segment
|
|
49
|
+
.chars()
|
|
50
|
+
.any(|ch| matches!(ch, '*' | '?' | '<' | '>' | '|' | ':' | '\\' | '/'))
|
|
51
|
+
{
|
|
52
|
+
return Err(Error::new(
|
|
53
|
+
ErrorKind::InvalidInput,
|
|
54
|
+
format!(
|
|
55
|
+
"module name `{name}` contains invalid characters in `{segment}`"
|
|
56
|
+
),
|
|
57
|
+
));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
Component::CurDir | Component::ParentDir => {
|
|
61
|
+
return Err(Error::new(
|
|
62
|
+
ErrorKind::InvalidInput,
|
|
63
|
+
format!("module name `{name}` attempts directory traversal"),
|
|
64
|
+
));
|
|
65
|
+
}
|
|
66
|
+
Component::RootDir | Component::Prefix(_) => {
|
|
67
|
+
return Err(Error::new(
|
|
68
|
+
ErrorKind::InvalidInput,
|
|
69
|
+
format!("module name `{name}` resolves outside stub root"),
|
|
70
|
+
));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
Ok(path)
|
|
76
|
+
}
|
linkml/generators/yarrrmlgen.py
CHANGED
|
@@ -32,7 +32,18 @@ class YarrrmlGenerator(Generator):
|
|
|
32
32
|
visit_all_class_slots = False
|
|
33
33
|
|
|
34
34
|
def __init__(self, schema: str | TextIO | SchemaDefinition, format: str = "yml", **kwargs):
|
|
35
|
-
|
|
35
|
+
def _infer_source_suffix(path: str) -> str:
|
|
36
|
+
p = (path or "").lower()
|
|
37
|
+
if "~" in p:
|
|
38
|
+
return path # already has ~jsonpath or ~csv
|
|
39
|
+
if p.endswith(".json"):
|
|
40
|
+
return f"{path}~jsonpath"
|
|
41
|
+
if p.endswith(".csv") or p.endswith(".tsv"):
|
|
42
|
+
return f"{path}~csv"
|
|
43
|
+
return path
|
|
44
|
+
|
|
45
|
+
# in __init__ right after you read src:
|
|
46
|
+
raw_src = kwargs.pop("source", None)
|
|
36
47
|
it = kwargs.pop("iterator_template", None)
|
|
37
48
|
|
|
38
49
|
super().__init__(schema, **kwargs)
|
|
@@ -42,7 +53,12 @@ class YarrrmlGenerator(Generator):
|
|
|
42
53
|
|
|
43
54
|
self.format = format
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
# normalize source: if user passed file without "~csv/~jsonpath", infer it
|
|
57
|
+
if raw_src:
|
|
58
|
+
self.source = _infer_source_suffix(raw_src)
|
|
59
|
+
else:
|
|
60
|
+
self.source = DEFAULT_SOURCE_JSON
|
|
61
|
+
|
|
46
62
|
self.iterator_template: str = it or DEFAULT_ITERATOR
|
|
47
63
|
|
|
48
64
|
# public
|
|
@@ -70,7 +86,7 @@ class YarrrmlGenerator(Generator):
|
|
|
70
86
|
if self._is_json_source():
|
|
71
87
|
mapping["sources"] = [[self.source, self._iterator_for_class(cls)]]
|
|
72
88
|
else:
|
|
73
|
-
mapping["sources"] = [self.source]
|
|
89
|
+
mapping["sources"] = [[self.source]]
|
|
74
90
|
|
|
75
91
|
mappings[str(cls.name)] = mapping
|
|
76
92
|
|
|
@@ -136,7 +152,7 @@ class YarrrmlGenerator(Generator):
|
|
|
136
152
|
@click.command(name="yarrrml")
|
|
137
153
|
@click.option(
|
|
138
154
|
"--source",
|
|
139
|
-
help="YARRRML source shorthand, e.g., data.json~jsonpath or data.csv~csv",
|
|
155
|
+
help="YARRRML source shorthand, e.g., data.json~jsonpath or data.csv~csv (TSV works too)",
|
|
140
156
|
)
|
|
141
157
|
@click.option(
|
|
142
158
|
"--iterator-template",
|