This is an automated email from the ASF dual-hosted git repository. xuanwo pushed a commit to branch xuanwo/drop-python-scheme in repository https://gitbox.apache.org/repos/asf/opendal.git
commit b53772e365c9d591c6f5d886b448ea2f05e17122 Author: Xuanwo <[email protected]> AuthorDate: Thu Dec 4 18:14:09 2025 +0800 refactor: Remove schema from python binding Signed-off-by: Xuanwo <[email protected]> --- bindings/python/python/opendal/operator.pyi | 14 ++ bindings/python/src/operator.rs | 58 ++--- bindings/python/src/services.rs | 376 ++++++++++++---------------- dev/src/generate/python.j2 | 81 +++--- 4 files changed, 245 insertions(+), 284 deletions(-) diff --git a/bindings/python/python/opendal/operator.pyi b/bindings/python/python/opendal/operator.pyi index 9ae772a22..54c9f2f9e 100644 --- a/bindings/python/python/opendal/operator.pyi +++ b/bindings/python/python/opendal/operator.pyi @@ -504,6 +504,13 @@ class AsyncOperator: The blocking operator. """ @typing.overload + def __new__( + cls, + scheme: builtins.str, + /, + **kwargs: builtins.str, + ) -> typing_extensions.Self: ... + @typing.overload def __new__( cls, scheme: opendal.services.Scheme.AliyunDrive | typing.Literal["aliyun-drive"], @@ -2737,6 +2744,13 @@ class Operator: The async operator. """ @typing.overload + def __new__( + cls, + scheme: builtins.str, + /, + **kwargs: builtins.str, + ) -> typing_extensions.Self: ... + @typing.overload def __new__( cls, scheme: opendal.services.Scheme.AliyunDrive | typing.Literal["aliyun-drive"], diff --git a/bindings/python/src/operator.rs b/bindings/python/src/operator.rs index c7c360240..cdc85ca2a 100644 --- a/bindings/python/src/operator.rs +++ b/bindings/python/src/operator.rs @@ -17,7 +17,6 @@ use std::collections::HashMap; use std::path::PathBuf; -use std::str::FromStr; use std::time::Duration; use pyo3::IntoPyObjectExt; @@ -29,16 +28,13 @@ use pyo3_async_runtimes::tokio::future_into_py; use crate::*; -fn build_operator( - scheme: ocore::Scheme, - map: HashMap<String, String>, -) -> PyResult<ocore::Operator> { +fn build_operator(scheme: &str, map: HashMap<String, String>) -> PyResult<ocore::Operator> { let op = ocore::Operator::via_iter(scheme, map).map_err(format_pyerr)?; Ok(op) } fn build_blocking_operator( - scheme: ocore::Scheme, + scheme: &str, map: HashMap<String, String>, ) -> PyResult<ocore::blocking::Operator> { let op = ocore::Operator::via_iter(scheme, map).map_err(format_pyerr)?; @@ -60,7 +56,7 @@ fn build_blocking_operator( #[pyclass(module = "opendal.operator")] pub struct Operator { core: ocore::blocking::Operator, - __scheme: ocore::Scheme, + __scheme: String, __map: HashMap<String, String>, } @@ -89,19 +85,14 @@ impl Operator { kwargs: Option<&Bound<PyDict>>, ) -> PyResult<Self> { let scheme = if let Ok(scheme_str) = scheme.extract::<&str>() { - ocore::Scheme::from_str(scheme_str) - .map_err(|err| { - ocore::Error::new(ocore::ErrorKind::Unexpected, "unsupported scheme") - .set_source(err) - }) - .map_err(format_pyerr) + scheme_str.to_string() } else if let Ok(py_scheme) = scheme.extract::<PyScheme>() { - Ok(py_scheme.into()) + String::from(py_scheme) } else { - Err(Unsupported::new_err( + return Err(Unsupported::new_err( "Invalid type for scheme, expected str or Scheme", - )) - }?; + )); + }; let map = kwargs .map(|v| { v.extract::<HashMap<String, String>>() @@ -110,7 +101,7 @@ impl Operator { .unwrap_or_default(); Ok(Operator { - core: build_blocking_operator(scheme, map.clone())?, + core: build_blocking_operator(&scheme, map.clone())?, __scheme: scheme, __map: map, }) @@ -135,7 +126,7 @@ impl Operator { let op = ocore::blocking::Operator::new(op).map_err(format_pyerr)?; Ok(Self { core: op, - __scheme: self.__scheme, + __scheme: self.__scheme.clone(), __map: self.__map.clone(), }) } @@ -670,7 +661,7 @@ impl Operator { pub fn to_async_operator(&self) -> PyResult<AsyncOperator> { Ok(AsyncOperator { core: self.core.clone().into(), - __scheme: self.__scheme, + __scheme: self.__scheme.clone(), __map: self.__map.clone(), }) } @@ -691,7 +682,7 @@ impl Operator { #[gen_stub(skip)] fn __getnewargs_ex__(&self, py: Python) -> PyResult<Py<PyAny>> { - let args = vec![self.__scheme.to_string()]; + let args = vec![self.__scheme.clone()]; let args = PyTuple::new(py, args)?.into_py_any(py)?; let kwargs = self.__map.clone().into_py_any(py)?; PyTuple::new(py, [args, kwargs])?.into_py_any(py) @@ -709,7 +700,7 @@ impl Operator { #[pyclass(module = "opendal.operator")] pub struct AsyncOperator { core: ocore::Operator, - __scheme: ocore::Scheme, + __scheme: String, __map: HashMap<String, String>, } @@ -738,19 +729,14 @@ impl AsyncOperator { kwargs: Option<&Bound<PyDict>>, ) -> PyResult<Self> { let scheme = if let Ok(scheme_str) = scheme.extract::<&str>() { - ocore::Scheme::from_str(scheme_str) - .map_err(|err| { - ocore::Error::new(ocore::ErrorKind::Unexpected, "unsupported scheme") - .set_source(err) - }) - .map_err(format_pyerr) + scheme_str.to_string() } else if let Ok(py_scheme) = scheme.extract::<PyScheme>() { - Ok(py_scheme.into()) + String::from(py_scheme) } else { - Err(Unsupported::new_err( + return Err(Unsupported::new_err( "Invalid type for scheme, expected str or Scheme", - )) - }?; + )); + }; let map = kwargs .map(|v| { @@ -760,7 +746,7 @@ impl AsyncOperator { .unwrap_or_default(); Ok(AsyncOperator { - core: build_operator(scheme, map.clone())?, + core: build_operator(&scheme, map.clone())?, __scheme: scheme, __map: map, }) @@ -781,7 +767,7 @@ impl AsyncOperator { let op = layer.0.layer(self.core.clone()); Ok(Self { core: op, - __scheme: self.__scheme, + __scheme: self.__scheme.clone(), __map: self.__map.clone(), }) } @@ -1608,7 +1594,7 @@ impl AsyncOperator { Ok(Operator { core: op, - __scheme: self.__scheme, + __scheme: self.__scheme.clone(), __map: self.__map.clone(), }) } @@ -1633,7 +1619,7 @@ impl AsyncOperator { #[gen_stub(skip)] fn __getnewargs_ex__(&self, py: Python) -> PyResult<Py<PyAny>> { - let args = vec![self.__scheme.to_string()]; + let args = vec![self.__scheme.clone()]; let args = PyTuple::new(py, args)?.into_py_any(py)?; let kwargs = self.__map.clone().into_py_any(py)?; PyTuple::new(py, [args, kwargs])?.into_py_any(py) diff --git a/bindings/python/src/services.rs b/bindings/python/src/services.rs index 719bee9f9..c9e48c678 100644 --- a/bindings/python/src/services.rs +++ b/bindings/python/src/services.rs @@ -32,6 +32,23 @@ See justfile at path ``../../justfile`` for more details. "# ); +submit! { + gen_methods_from_python! { + r#" + import builtins + import typing + import typing_extensions + class Operator: + @overload + def __new__(cls, + scheme: builtins.str, + /, + **kwargs: builtins.str, + ) -> typing_extensions.Self: ... + "# + } +} + #[gen_stub_pyclass_enum] #[pyclass( eq, @@ -149,8 +166,7 @@ impl PyScheme { #[getter] pub fn value(&self) -> &'static str { - let scheme: ocore::Scheme = (*self).into(); - scheme.into_static() + (*self).into() } } @@ -1006,6 +1022,7 @@ submit! { scheme: typing.Union[opendal.services.Scheme.Huggingface, typing.Literal["huggingface"]], /, *, + endpoint: builtins.str = ..., repo_id: builtins.str = ..., repo_type: builtins.str = ..., revision: builtins.str = ..., @@ -1017,13 +1034,17 @@ submit! { Parameters ---------- + endpoint : builtins.str, optional + Endpoint of the Huggingface Hub. + Default is "https://huggingface.co". repo_id : builtins.str, optional Repo id of this backend. This is required. repo_type : builtins.str, optional Repo type of this backend. Default is model. - Available values: - model - dataset + Available values: - model - dataset - datasets + (alias for dataset) revision : builtins.str, optional Revision of this backend. Default is main. @@ -1897,7 +1918,7 @@ submit! { HTTP headers. This is necessary when writing to AWS S3 Buckets with Object Lock enabled for example. - Available options: - "crc32c" + Available options: - "crc32c" - "md5" default_storage_class : builtins.str, optional default storage_class for this backend. Available values: - `DEEP_ARCHIVE` - `GLACIER` - @@ -1921,7 +1942,7 @@ submit! { Disable load credential from ec2 metadata. This option is used to disable the default behavior of opendal to load credential from ec2 metadata, - a.k.a, IMDSv2 + a.k.a., IMDSv2 disable_list_objects_v2 : builtins.bool, optional OpenDAL uses List Objects V2 by default to list objects. @@ -1937,7 +1958,7 @@ submit! { Disable write with if match so that opendal will not send write request with if match headers. For example, Ceph RADOS S3 doesn't support write - with if match. + with if matched. enable_request_payer : builtins.bool, optional Indicates whether the client agrees to pay for the requests made to the S3 bucket. @@ -2466,6 +2487,23 @@ submit! { } } +submit! { + gen_methods_from_python! { + r#" + import builtins + import typing + import typing_extensions + class AsyncOperator: + @overload + def __new__(cls, + scheme: builtins.str, + /, + **kwargs: builtins.str, + ) -> typing_extensions.Self: ... + "# + } +} + submit! { gen_methods_from_python! { r#" @@ -3318,6 +3356,7 @@ submit! { scheme: typing.Union[opendal.services.Scheme.Huggingface, typing.Literal["huggingface"]], /, *, + endpoint: builtins.str = ..., repo_id: builtins.str = ..., repo_type: builtins.str = ..., revision: builtins.str = ..., @@ -3329,13 +3368,17 @@ submit! { Parameters ---------- + endpoint : builtins.str, optional + Endpoint of the Huggingface Hub. + Default is "https://huggingface.co". repo_id : builtins.str, optional Repo id of this backend. This is required. repo_type : builtins.str, optional Repo type of this backend. Default is model. - Available values: - model - dataset + Available values: - model - dataset - datasets + (alias for dataset) revision : builtins.str, optional Revision of this backend. Default is main. @@ -4209,7 +4252,7 @@ submit! { HTTP headers. This is necessary when writing to AWS S3 Buckets with Object Lock enabled for example. - Available options: - "crc32c" + Available options: - "crc32c" - "md5" default_storage_class : builtins.str, optional default storage_class for this backend. Available values: - `DEEP_ARCHIVE` - `GLACIER` - @@ -4233,7 +4276,7 @@ submit! { Disable load credential from ec2 metadata. This option is used to disable the default behavior of opendal to load credential from ec2 metadata, - a.k.a, IMDSv2 + a.k.a., IMDSv2 disable_list_objects_v2 : builtins.bool, optional OpenDAL uses List Objects V2 by default to list objects. @@ -4249,7 +4292,7 @@ submit! { Disable write with if match so that opendal will not send write request with if match headers. For example, Ceph RADOS S3 doesn't support write - with if match. + with if matched. enable_request_payer : builtins.bool, optional Indicates whether the client agrees to pay for the requests made to the S3 bucket. @@ -4778,225 +4821,124 @@ submit! { } } -// --- Conversion Macro --- -macro_rules! impl_enum_from { - ($src:ty => $dst:ty { $( +macro_rules! impl_enum_to_str { + ($src:ty { $( $(#[$cfg:meta])? - $variant:ident + $variant:ident => $value:literal ),* $(,)? }) => { - impl From<$src> for $dst { + impl From<$src> for &'static str { fn from(value: $src) -> Self { match value { $( $(#[$cfg])? - <$src>::$variant => <$dst>::$variant, + <$src>::$variant => $value, )* - #[allow(unreachable_patterns)] - _ => unreachable!( - "Unsupported scheme variant: {:?}. \ - This likely means a new variant was added to `{}` \ - but `PyScheme` or the generated bindings were not updated.", - value, - stringify!($src) - ), } } } + + impl From<$src> for String { + fn from(value: $src) -> Self { + let v: &'static str = value.into(); + v.to_string() + } + } }; } -// --- PyScheme -> ocore::Scheme --- -impl_enum_from!( - PyScheme => ocore::Scheme { - #[cfg(feature = "services-aliyun-drive")] - AliyunDrive, - #[cfg(feature = "services-alluxio")] - Alluxio, - #[cfg(feature = "services-azblob")] - Azblob, - #[cfg(feature = "services-azdls")] - Azdls, - #[cfg(feature = "services-azfile")] - Azfile, - #[cfg(feature = "services-b2")] - B2, - #[cfg(feature = "services-cacache")] - Cacache, - #[cfg(feature = "services-cos")] - Cos, - #[cfg(feature = "services-dashmap")] - Dashmap, - #[cfg(feature = "services-dropbox")] - Dropbox, - #[cfg(feature = "services-fs")] - Fs, - #[cfg(feature = "services-ftp")] - Ftp, - #[cfg(feature = "services-gcs")] - Gcs, - #[cfg(feature = "services-gdrive")] - Gdrive, - #[cfg(feature = "services-ghac")] - Ghac, - #[cfg(feature = "services-gridfs")] - Gridfs, - #[cfg(feature = "services-hdfs-native")] - HdfsNative, - #[cfg(feature = "services-http")] - Http, - #[cfg(feature = "services-huggingface")] - Huggingface, - #[cfg(feature = "services-ipfs")] - Ipfs, - #[cfg(feature = "services-ipmfs")] - Ipmfs, - #[cfg(feature = "services-koofr")] - Koofr, - #[cfg(feature = "services-memcached")] - Memcached, - #[cfg(feature = "services-memory")] - Memory, - #[cfg(feature = "services-mini-moka")] - MiniMoka, - #[cfg(feature = "services-moka")] - Moka, - #[cfg(feature = "services-mongodb")] - Mongodb, - #[cfg(feature = "services-mysql")] - Mysql, - #[cfg(feature = "services-obs")] - Obs, - #[cfg(feature = "services-onedrive")] - Onedrive, - #[cfg(feature = "services-oss")] - Oss, - #[cfg(feature = "services-persy")] - Persy, - #[cfg(feature = "services-postgresql")] - Postgresql, - #[cfg(feature = "services-redb")] - Redb, - #[cfg(feature = "services-redis")] - Redis, - #[cfg(feature = "services-s3")] - S3, - #[cfg(feature = "services-seafile")] - Seafile, - #[cfg(feature = "services-sftp")] - Sftp, - #[cfg(feature = "services-sled")] - Sled, - #[cfg(feature = "services-sqlite")] - Sqlite, - #[cfg(feature = "services-swift")] - Swift, - #[cfg(feature = "services-upyun")] - Upyun, - #[cfg(feature = "services-vercel-artifacts")] - VercelArtifacts, - #[cfg(feature = "services-webdav")] - Webdav, - #[cfg(feature = "services-webhdfs")] - Webhdfs, - #[cfg(feature = "services-yandex-disk")] - YandexDisk, - } -); - -// --- ocore::Scheme -> PyScheme --- -impl_enum_from!( - ocore::Scheme => PyScheme { - #[cfg(feature = "services-aliyun-drive")] - AliyunDrive, - #[cfg(feature = "services-alluxio")] - Alluxio, - #[cfg(feature = "services-azblob")] - Azblob, - #[cfg(feature = "services-azdls")] - Azdls, - #[cfg(feature = "services-azfile")] - Azfile, - #[cfg(feature = "services-b2")] - B2, - #[cfg(feature = "services-cacache")] - Cacache, - #[cfg(feature = "services-cos")] - Cos, - #[cfg(feature = "services-dashmap")] - Dashmap, - #[cfg(feature = "services-dropbox")] - Dropbox, - #[cfg(feature = "services-fs")] - Fs, - #[cfg(feature = "services-ftp")] - Ftp, - #[cfg(feature = "services-gcs")] - Gcs, - #[cfg(feature = "services-gdrive")] - Gdrive, - #[cfg(feature = "services-ghac")] - Ghac, - #[cfg(feature = "services-gridfs")] - Gridfs, - #[cfg(feature = "services-hdfs-native")] - HdfsNative, - #[cfg(feature = "services-http")] - Http, - #[cfg(feature = "services-huggingface")] - Huggingface, - #[cfg(feature = "services-ipfs")] - Ipfs, - #[cfg(feature = "services-ipmfs")] - Ipmfs, - #[cfg(feature = "services-koofr")] - Koofr, - #[cfg(feature = "services-memcached")] - Memcached, - #[cfg(feature = "services-memory")] - Memory, - #[cfg(feature = "services-mini-moka")] - MiniMoka, - #[cfg(feature = "services-moka")] - Moka, - #[cfg(feature = "services-mongodb")] - Mongodb, - #[cfg(feature = "services-mysql")] - Mysql, - #[cfg(feature = "services-obs")] - Obs, - #[cfg(feature = "services-onedrive")] - Onedrive, - #[cfg(feature = "services-oss")] - Oss, - #[cfg(feature = "services-persy")] - Persy, - #[cfg(feature = "services-postgresql")] - Postgresql, - #[cfg(feature = "services-redb")] - Redb, - #[cfg(feature = "services-redis")] - Redis, - #[cfg(feature = "services-s3")] - S3, - #[cfg(feature = "services-seafile")] - Seafile, - #[cfg(feature = "services-sftp")] - Sftp, - #[cfg(feature = "services-sled")] - Sled, - #[cfg(feature = "services-sqlite")] - Sqlite, - #[cfg(feature = "services-swift")] - Swift, - #[cfg(feature = "services-upyun")] - Upyun, - #[cfg(feature = "services-vercel-artifacts")] - VercelArtifacts, - #[cfg(feature = "services-webdav")] - Webdav, - #[cfg(feature = "services-webhdfs")] - Webhdfs, - #[cfg(feature = "services-yandex-disk")] - YandexDisk, +impl_enum_to_str!( + PyScheme { + #[cfg(feature = "services-aliyun-drive")] + AliyunDrive => "aliyun-drive", + #[cfg(feature = "services-alluxio")] + Alluxio => "alluxio", + #[cfg(feature = "services-azblob")] + Azblob => "azblob", + #[cfg(feature = "services-azdls")] + Azdls => "azdls", + #[cfg(feature = "services-azfile")] + Azfile => "azfile", + #[cfg(feature = "services-b2")] + B2 => "b2", + #[cfg(feature = "services-cacache")] + Cacache => "cacache", + #[cfg(feature = "services-cos")] + Cos => "cos", + #[cfg(feature = "services-dashmap")] + Dashmap => "dashmap", + #[cfg(feature = "services-dropbox")] + Dropbox => "dropbox", + #[cfg(feature = "services-fs")] + Fs => "fs", + #[cfg(feature = "services-ftp")] + Ftp => "ftp", + #[cfg(feature = "services-gcs")] + Gcs => "gcs", + #[cfg(feature = "services-gdrive")] + Gdrive => "gdrive", + #[cfg(feature = "services-ghac")] + Ghac => "ghac", + #[cfg(feature = "services-gridfs")] + Gridfs => "gridfs", + #[cfg(feature = "services-hdfs-native")] + HdfsNative => "hdfs-native", + #[cfg(feature = "services-http")] + Http => "http", + #[cfg(feature = "services-huggingface")] + Huggingface => "huggingface", + #[cfg(feature = "services-ipfs")] + Ipfs => "ipfs", + #[cfg(feature = "services-ipmfs")] + Ipmfs => "ipmfs", + #[cfg(feature = "services-koofr")] + Koofr => "koofr", + #[cfg(feature = "services-memcached")] + Memcached => "memcached", + #[cfg(feature = "services-memory")] + Memory => "memory", + #[cfg(feature = "services-mini-moka")] + MiniMoka => "mini-moka", + #[cfg(feature = "services-moka")] + Moka => "moka", + #[cfg(feature = "services-mongodb")] + Mongodb => "mongodb", + #[cfg(feature = "services-mysql")] + Mysql => "mysql", + #[cfg(feature = "services-obs")] + Obs => "obs", + #[cfg(feature = "services-onedrive")] + Onedrive => "onedrive", + #[cfg(feature = "services-oss")] + Oss => "oss", + #[cfg(feature = "services-persy")] + Persy => "persy", + #[cfg(feature = "services-postgresql")] + Postgresql => "postgresql", + #[cfg(feature = "services-redb")] + Redb => "redb", + #[cfg(feature = "services-redis")] + Redis => "redis", + #[cfg(feature = "services-s3")] + S3 => "s3", + #[cfg(feature = "services-seafile")] + Seafile => "seafile", + #[cfg(feature = "services-sftp")] + Sftp => "sftp", + #[cfg(feature = "services-sled")] + Sled => "sled", + #[cfg(feature = "services-sqlite")] + Sqlite => "sqlite", + #[cfg(feature = "services-swift")] + Swift => "swift", + #[cfg(feature = "services-upyun")] + Upyun => "upyun", + #[cfg(feature = "services-vercel-artifacts")] + VercelArtifacts => "vercel-artifacts", + #[cfg(feature = "services-webdav")] + Webdav => "webdav", + #[cfg(feature = "services-webhdfs")] + Webhdfs => "webhdfs", + #[cfg(feature = "services-yandex-disk")] + YandexDisk => "yandex-disk", } ); diff --git a/dev/src/generate/python.j2 b/dev/src/generate/python.j2 index cab42dffb..fabd4807a 100644 --- a/dev/src/generate/python.j2 +++ b/dev/src/generate/python.j2 @@ -32,6 +32,23 @@ See justfile at path ``../../justfile`` for more details. "# ); +submit! { + gen_methods_from_python! { + r#" + import builtins + import typing + import typing_extensions + class Operator: + @overload + def __new__(cls, + scheme: builtins.str, + /, + **kwargs: builtins.str, + ) -> typing_extensions.Self: ... + "# + } +} + #[gen_stub_pyclass_enum] #[pyclass( eq, @@ -61,8 +78,7 @@ impl PyScheme { #[getter] pub fn value(&self) -> &'static str { - let scheme: ocore::Scheme = (*self).into(); - scheme.into_static() + (*self).into() } } @@ -107,6 +123,22 @@ submit! { } {% endfor %} +submit! { + gen_methods_from_python! { + r#" + import builtins + import typing + import typing_extensions + class AsyncOperator: + @overload + def __new__(cls, + scheme: builtins.str, + /, + **kwargs: builtins.str, + ) -> typing_extensions.Self: ... + "# + } +} {% for srv in srvs %} submit! { gen_methods_from_python! { @@ -147,49 +179,36 @@ submit! { } } {% endfor %} -// --- Conversion Macro --- -macro_rules! impl_enum_from { - ($src:ty => $dst:ty { $( +macro_rules! impl_enum_to_str { + ($src:ty { $( $(#[$cfg:meta])? - $variant:ident + $variant:ident => $value:literal ),* $(,)? }) => { - impl From<$src> for $dst { + impl From<$src> for &'static str { fn from(value: $src) -> Self { match value { $( $(#[$cfg])? - <$src>::$variant => <$dst>::$variant, + <$src>::$variant => $value, )* - #[allow(unreachable_patterns)] - _ => unreachable!( - "Unsupported scheme variant: {:?}. \ - This likely means a new variant was added to `{}` \ - but `PyScheme` or the generated bindings were not updated.", - value, - stringify!($src) - ), } } } + + impl From<$src> for String { + fn from(value: $src) -> Self { + let v: &'static str = value.into(); + v.to_string() + } + } }; } -// --- PyScheme -> ocore::Scheme --- -impl_enum_from!( - PyScheme => ocore::Scheme { +impl_enum_to_str!( + PyScheme { {%- for name in srvs %} - #[cfg(feature = "{{ service_to_feature(name) }}")] - {{ service_to_pascal(name) }}, -{%- endfor %} - } -); - -// --- ocore::Scheme -> PyScheme --- -impl_enum_from!( - ocore::Scheme => PyScheme { -{%- for name in srvs %} - #[cfg(feature = "{{ service_to_feature(name) }}")] - {{ service_to_pascal(name) }}, + #[cfg(feature = "{{ service_to_feature(name) }}")] + {{ service_to_pascal(name) }} => "{{ snake_to_kebab_case(name) }}", {%- endfor %} } );
