1#![cfg(feature = "python")]
2
3use std::path::PathBuf;
4
5use pyo3::exceptions::{PyOSError, PyRuntimeError};
6use pyo3::prelude::*;
7use pyo3::types::PyAny;
8
9use zccache::cli::{
10 build_download_request, client_download, client_download_exists, client_session_end,
11 client_session_start, client_session_stats, client_start, client_status, client_stop,
12 fingerprint_check, fingerprint_invalidate, fingerprint_mark_failure, fingerprint_mark_success,
13 run_ino_convert_cached, DownloadParams, DownloadSource, InoConvertOptions, WaitMode,
14};
15
16fn runtime_to_py_err(message: String) -> PyErr {
17 PyErr::new::<PyRuntimeError, _>(message)
18}
19
20fn parse_download_source(source: &Bound<'_, PyAny>) -> PyResult<DownloadSource> {
21 if let Ok(url) = source.extract::<String>() {
22 return Ok(DownloadSource::Url(url));
23 }
24 if let Ok(urls) = source.extract::<Vec<String>>() {
25 return Ok(DownloadSource::MultipartUrls(urls));
26 }
27 Err(PyErr::new::<PyRuntimeError, _>(
28 "source must be a URL string or a list of URL strings",
29 ))
30}
31
32#[pyclass(module = "zccache._native")]
33#[derive(Clone)]
34pub struct NativeDaemonStatus {
35 #[pyo3(get)]
36 version: String,
37 #[pyo3(get)]
38 artifact_count: u64,
39 #[pyo3(get)]
40 cache_size_bytes: u64,
41 #[pyo3(get)]
42 metadata_entries: u64,
43 #[pyo3(get)]
44 uptime_secs: u64,
45 #[pyo3(get)]
46 cache_hits: u64,
47 #[pyo3(get)]
48 cache_misses: u64,
49 #[pyo3(get)]
50 total_compilations: u64,
51 #[pyo3(get)]
52 non_cacheable: u64,
53 #[pyo3(get)]
54 compile_errors: u64,
55 #[pyo3(get)]
56 time_saved_ms: u64,
57 #[pyo3(get)]
58 total_links: u64,
59 #[pyo3(get)]
60 link_hits: u64,
61 #[pyo3(get)]
62 link_misses: u64,
63 #[pyo3(get)]
64 link_non_cacheable: u64,
65 #[pyo3(get)]
66 dep_graph_contexts: u64,
67 #[pyo3(get)]
68 dep_graph_files: u64,
69 #[pyo3(get)]
70 sessions_total: u64,
71 #[pyo3(get)]
72 sessions_active: u64,
73 #[pyo3(get)]
74 cache_dir: String,
75 #[pyo3(get)]
76 dep_graph_version: u32,
77 #[pyo3(get)]
78 dep_graph_disk_size: u64,
79 #[pyo3(get)]
80 dep_graph_persisted: bool,
81}
82
83impl From<zccache::protocol::DaemonStatus> for NativeDaemonStatus {
84 fn from(value: zccache::protocol::DaemonStatus) -> Self {
85 Self {
86 version: value.version,
87 artifact_count: value.artifact_count,
88 cache_size_bytes: value.cache_size_bytes,
89 metadata_entries: value.metadata_entries,
90 uptime_secs: value.uptime_secs,
91 cache_hits: value.cache_hits,
92 cache_misses: value.cache_misses,
93 total_compilations: value.total_compilations,
94 non_cacheable: value.non_cacheable,
95 compile_errors: value.compile_errors,
96 time_saved_ms: value.time_saved_ms,
97 total_links: value.total_links,
98 link_hits: value.link_hits,
99 link_misses: value.link_misses,
100 link_non_cacheable: value.link_non_cacheable,
101 dep_graph_contexts: value.dep_graph_contexts,
102 dep_graph_files: value.dep_graph_files,
103 sessions_total: value.sessions_total,
104 sessions_active: value.sessions_active,
105 cache_dir: value.cache_dir.display().to_string(),
106 dep_graph_version: value.dep_graph_version,
107 dep_graph_disk_size: value.dep_graph_disk_size,
108 dep_graph_persisted: value.dep_graph_persisted,
109 }
110 }
111}
112
113#[pyclass(module = "zccache._native")]
114#[derive(Clone)]
115pub struct NativeSessionStart {
116 #[pyo3(get)]
117 session_id: String,
118 #[pyo3(get)]
119 journal_path: Option<String>,
120}
121
122#[pyclass(module = "zccache._native")]
123#[derive(Clone)]
124pub struct NativeSessionStats {
125 #[pyo3(get)]
126 duration_ms: u64,
127 #[pyo3(get)]
128 compilations: u64,
129 #[pyo3(get)]
130 hits: u64,
131 #[pyo3(get)]
132 misses: u64,
133 #[pyo3(get)]
134 non_cacheable: u64,
135 #[pyo3(get)]
136 errors: u64,
137 #[pyo3(get)]
138 time_saved_ms: u64,
139 #[pyo3(get)]
140 unique_sources: u64,
141 #[pyo3(get)]
142 bytes_read: u64,
143 #[pyo3(get)]
144 bytes_written: u64,
145}
146
147impl From<zccache::protocol::SessionStats> for NativeSessionStats {
148 fn from(value: zccache::protocol::SessionStats) -> Self {
149 Self {
150 duration_ms: value.duration_ms,
151 compilations: value.compilations,
152 hits: value.hits,
153 misses: value.misses,
154 non_cacheable: value.non_cacheable,
155 errors: value.errors,
156 time_saved_ms: value.time_saved_ms,
157 unique_sources: value.unique_sources,
158 bytes_read: value.bytes_read,
159 bytes_written: value.bytes_written,
160 }
161 }
162}
163
164#[pyclass(module = "zccache._native")]
165#[derive(Clone)]
166pub struct NativeFingerprintCheck {
167 #[pyo3(get)]
168 decision: String,
169 #[pyo3(get)]
170 reason: Option<String>,
171 #[pyo3(get)]
172 changed_files: Vec<String>,
173}
174
175#[pyclass(module = "zccache._native")]
176#[derive(Clone)]
177pub struct NativeInoConvertResult {
178 #[pyo3(get)]
179 cache_hit: bool,
180 #[pyo3(get)]
181 skipped_write: bool,
182}
183
184#[pyclass(module = "zccache._native")]
185#[derive(Clone)]
186pub struct NativeDownloadStatus {
187 #[pyo3(get)]
188 phase: String,
189 #[pyo3(get)]
190 total_bytes: Option<u64>,
191 #[pyo3(get)]
192 downloaded_bytes: u64,
193 #[pyo3(get)]
194 percentage: Option<f32>,
195 #[pyo3(get)]
196 active_clients: u32,
197 #[pyo3(get)]
198 destination: String,
199 #[pyo3(get)]
200 source_url: String,
201 #[pyo3(get)]
202 error: Option<String>,
203}
204
205impl From<zccache::download::DownloadStatus> for NativeDownloadStatus {
206 fn from(value: zccache::download::DownloadStatus) -> Self {
207 Self {
208 phase: format!("{:?}", value.phase).to_lowercase(),
209 total_bytes: value.total_bytes,
210 downloaded_bytes: value.downloaded_bytes,
211 percentage: value.percentage,
212 active_clients: value.active_clients,
213 destination: value.destination.display().to_string(),
214 source_url: value.source_url,
215 error: value.error,
216 }
217 }
218}
219
220#[pyclass(module = "zccache._native")]
221#[derive(Clone)]
222pub struct NativeDownloadDaemonStatus {
223 #[pyo3(get)]
224 version: String,
225 #[pyo3(get)]
226 active_downloads: u64,
227 #[pyo3(get)]
228 connected_clients: u64,
229 #[pyo3(get)]
230 uptime_secs: u64,
231 #[pyo3(get)]
232 endpoint: String,
233}
234
235impl From<zccache::download::DownloadDaemonStatus> for NativeDownloadDaemonStatus {
236 fn from(value: zccache::download::DownloadDaemonStatus) -> Self {
237 Self {
238 version: value.version,
239 active_downloads: value.active_downloads,
240 connected_clients: value.connected_clients,
241 uptime_secs: value.uptime_secs,
242 endpoint: value.endpoint,
243 }
244 }
245}
246
247fn parse_archive_format(value: &str) -> zccache::download_client::ArchiveFormat {
248 match value.to_ascii_lowercase().as_str() {
249 "none" => zccache::download_client::ArchiveFormat::None,
250 "zst" => zccache::download_client::ArchiveFormat::Zst,
251 "zip" => zccache::download_client::ArchiveFormat::Zip,
252 "xz" => zccache::download_client::ArchiveFormat::Xz,
253 "tar.gz" | "targz" => zccache::download_client::ArchiveFormat::TarGz,
254 "tar.xz" | "tarxz" => zccache::download_client::ArchiveFormat::TarXz,
255 "tar.zst" | "tarzst" => zccache::download_client::ArchiveFormat::TarZst,
256 "7z" | "sevenz" => zccache::download_client::ArchiveFormat::SevenZip,
257 _ => zccache::download_client::ArchiveFormat::Auto,
258 }
259}
260
261#[pyclass(module = "zccache._native")]
262#[derive(Clone)]
263pub struct NativeFetchResult {
264 #[pyo3(get)]
265 status: String,
266 #[pyo3(get)]
267 cache_path: String,
268 #[pyo3(get)]
269 expanded_path: Option<String>,
270 #[pyo3(get)]
271 bytes: Option<u64>,
272 #[pyo3(get)]
273 sha256: String,
274}
275
276impl From<zccache::download_client::FetchResult> for NativeFetchResult {
277 fn from(value: zccache::download_client::FetchResult) -> Self {
278 Self {
279 status: format!("{:?}", value.status).to_lowercase(),
280 cache_path: value.cache_path.display().to_string(),
281 expanded_path: value.expanded_path.map(|path| path.display().to_string()),
282 bytes: value.bytes,
283 sha256: value.sha256,
284 }
285 }
286}
287
288#[pyclass(module = "zccache._native")]
289#[derive(Clone)]
290pub struct NativeFetchState {
291 #[pyo3(get)]
292 kind: String,
293 #[pyo3(get)]
294 cache_path: String,
295 #[pyo3(get)]
296 expanded_path: Option<String>,
297 #[pyo3(get)]
298 bytes: Option<u64>,
299 #[pyo3(get)]
300 sha256: Option<String>,
301 #[pyo3(get)]
302 reason: Option<String>,
303}
304
305impl From<zccache::download_client::FetchState> for NativeFetchState {
306 fn from(value: zccache::download_client::FetchState) -> Self {
307 Self {
308 kind: format!("{:?}", value.kind).to_lowercase(),
309 cache_path: value.cache_path.display().to_string(),
310 expanded_path: value.expanded_path.map(|path| path.display().to_string()),
311 bytes: value.bytes,
312 sha256: value.sha256,
313 reason: value.reason,
314 }
315 }
316}
317
318#[pyclass(module = "zccache._native")]
319pub struct NativeDownloadHandle {
320 handle: Option<zccache::download_client::DownloadHandle>,
321 initiator: bool,
322 download_id: String,
323}
324
325#[pymethods]
326impl NativeDownloadHandle {
327 #[getter]
328 fn initiator(&self) -> bool {
329 self.initiator
330 }
331
332 #[getter]
333 fn download_id(&self) -> String {
334 self.download_id.clone()
335 }
336
337 fn status(&mut self) -> PyResult<NativeDownloadStatus> {
338 let handle = self
339 .handle
340 .as_mut()
341 .ok_or_else(|| runtime_to_py_err("download handle is closed".to_string()))?;
342 handle
343 .status()
344 .map(NativeDownloadStatus::from)
345 .map_err(runtime_to_py_err)
346 }
347
348 #[pyo3(signature = (timeout_ms=None))]
349 fn wait(&mut self, timeout_ms: Option<u64>) -> PyResult<NativeDownloadStatus> {
350 let handle = self
351 .handle
352 .as_mut()
353 .ok_or_else(|| runtime_to_py_err("download handle is closed".to_string()))?;
354 handle
355 .wait(timeout_ms)
356 .map(NativeDownloadStatus::from)
357 .map_err(runtime_to_py_err)
358 }
359
360 fn cancel(&mut self) -> PyResult<NativeDownloadStatus> {
361 let handle = self
362 .handle
363 .as_mut()
364 .ok_or_else(|| runtime_to_py_err("download handle is closed".to_string()))?;
365 handle
366 .cancel()
367 .map(NativeDownloadStatus::from)
368 .map_err(runtime_to_py_err)
369 }
370
371 fn close(&mut self) -> PyResult<()> {
372 if let Some(handle) = self.handle.take() {
373 handle.close().map_err(runtime_to_py_err)?;
374 }
375 Ok(())
376 }
377}
378
379#[pyclass(module = "zccache._native")]
380pub struct NativeDownloadApi {
381 client: zccache::download_client::DownloadClient,
382}
383
384#[pymethods]
385impl NativeDownloadApi {
386 #[new]
387 #[pyo3(signature = (endpoint=None))]
388 fn new(endpoint: Option<String>) -> Self {
389 let client = zccache::download_client::DownloadClient::new(endpoint.clone());
390 Self { client }
391 }
392
393 fn start(&self) -> PyResult<()> {
394 self.client.start_daemon().map_err(runtime_to_py_err)
395 }
396
397 fn stop(&self) -> PyResult<bool> {
398 self.client.stop_daemon().map_err(runtime_to_py_err)
399 }
400
401 fn daemon_status(&self) -> PyResult<NativeDownloadDaemonStatus> {
402 self.client
403 .daemon_status()
404 .map(NativeDownloadDaemonStatus::from)
405 .map_err(runtime_to_py_err)
406 }
407
408 #[pyo3(signature = (
409 source_url,
410 destination,
411 force=false,
412 max_connections=None,
413 min_segment_size=None
414 ))]
415 fn download(
416 &self,
417 source_url: String,
418 destination: String,
419 force: bool,
420 max_connections: Option<usize>,
421 min_segment_size: Option<u64>,
422 ) -> PyResult<NativeDownloadHandle> {
423 let options = zccache::download::DownloadOptions {
424 force,
425 max_connections,
426 min_segment_size,
427 };
428 let handle = self
429 .client
430 .download(&source_url, PathBuf::from(destination).as_path(), options)
431 .map_err(runtime_to_py_err)?;
432 let initiator = handle.initiator();
433 let download_id = handle.download_id().to_string();
434 Ok(NativeDownloadHandle {
435 handle: Some(handle),
436 initiator,
437 download_id,
438 })
439 }
440
441 #[pyo3(signature = (
442 source,
443 destination=None,
444 expanded=None,
445 expected_sha256=None,
446 archive_format="auto".to_string(),
447 max_connections=None,
448 min_segment_size=None,
449 blocking=true,
450 dry_run=false,
451 force=false
452 ))]
453 fn fetch(
454 &self,
455 source: &Bound<'_, PyAny>,
456 destination: Option<String>,
457 expanded: Option<String>,
458 expected_sha256: Option<String>,
459 archive_format: String,
460 max_connections: Option<usize>,
461 min_segment_size: Option<u64>,
462 blocking: bool,
463 dry_run: bool,
464 force: bool,
465 ) -> PyResult<NativeFetchResult> {
466 let source = parse_download_source(source)?;
467 let request = build_download_request(DownloadParams {
468 source,
469 archive_path: destination.map(PathBuf::from),
470 unarchive_path: expanded.map(PathBuf::from),
471 expected_sha256,
472 archive_format: parse_archive_format(&archive_format),
473 max_connections,
474 min_segment_size,
475 wait_mode: if blocking {
476 WaitMode::Block
477 } else {
478 WaitMode::NoWait
479 },
480 dry_run,
481 force,
482 });
483 self.client
484 .fetch(request)
485 .map(NativeFetchResult::from)
486 .map_err(runtime_to_py_err)
487 }
488
489 #[pyo3(signature = (
490 source,
491 destination=None,
492 expanded=None,
493 expected_sha256=None,
494 archive_format="auto".to_string()
495 ))]
496 fn exists(
497 &self,
498 source: &Bound<'_, PyAny>,
499 destination: Option<String>,
500 expanded: Option<String>,
501 expected_sha256: Option<String>,
502 archive_format: String,
503 ) -> PyResult<NativeFetchState> {
504 let source = parse_download_source(source)?;
505 let request = build_download_request(DownloadParams {
506 source,
507 archive_path: destination.map(PathBuf::from),
508 unarchive_path: expanded.map(PathBuf::from),
509 expected_sha256,
510 archive_format: parse_archive_format(&archive_format),
511 max_connections: None,
512 min_segment_size: None,
513 wait_mode: WaitMode::Block,
514 dry_run: false,
515 force: false,
516 });
517 self.client
518 .exists(&request)
519 .map(NativeFetchState::from)
520 .map_err(runtime_to_py_err)
521 }
522}
523
524#[pyclass(module = "zccache._native")]
525pub struct NativeClient {
526 endpoint: Option<String>,
527}
528
529#[pymethods]
530impl NativeClient {
531 #[new]
532 #[pyo3(signature = (endpoint=None))]
533 fn new(endpoint: Option<String>) -> Self {
534 Self { endpoint }
535 }
536
537 fn start(&self) -> PyResult<()> {
538 client_start(self.endpoint.as_deref()).map_err(runtime_to_py_err)
539 }
540
541 fn stop(&self) -> PyResult<bool> {
542 client_stop(self.endpoint.as_deref()).map_err(runtime_to_py_err)
543 }
544
545 fn status(&self) -> PyResult<NativeDaemonStatus> {
546 client_status(self.endpoint.as_deref())
547 .map(NativeDaemonStatus::from)
548 .map_err(runtime_to_py_err)
549 }
550
551 #[pyo3(signature = (
552 source,
553 destination=None,
554 expanded=None,
555 expected_sha256=None,
556 max_connections=None,
557 min_segment_size=None,
558 blocking=true,
559 dry_run=false,
560 force=false
561 ))]
562 fn download(
563 &self,
564 source: &Bound<'_, PyAny>,
565 destination: Option<String>,
566 expanded: Option<String>,
567 expected_sha256: Option<String>,
568 max_connections: Option<usize>,
569 min_segment_size: Option<u64>,
570 blocking: bool,
571 dry_run: bool,
572 force: bool,
573 ) -> PyResult<NativeFetchResult> {
574 let source = parse_download_source(source)?;
575 client_download(
576 self.endpoint.as_deref(),
577 DownloadParams {
578 source,
579 archive_path: destination.map(PathBuf::from),
580 unarchive_path: expanded.map(PathBuf::from),
581 expected_sha256,
582 archive_format: zccache::download_client::ArchiveFormat::Auto,
583 max_connections,
584 min_segment_size,
585 wait_mode: if blocking {
586 WaitMode::Block
587 } else {
588 WaitMode::NoWait
589 },
590 dry_run,
591 force,
592 },
593 )
594 .map(NativeFetchResult::from)
595 .map_err(runtime_to_py_err)
596 }
597
598 #[pyo3(signature = (
599 source,
600 destination=None,
601 expanded=None,
602 expected_sha256=None
603 ))]
604 fn download_exists(
605 &self,
606 source: &Bound<'_, PyAny>,
607 destination: Option<String>,
608 expanded: Option<String>,
609 expected_sha256: Option<String>,
610 ) -> PyResult<NativeFetchState> {
611 let source = parse_download_source(source)?;
612 client_download_exists(
613 self.endpoint.as_deref(),
614 DownloadParams {
615 source,
616 archive_path: destination.map(PathBuf::from),
617 unarchive_path: expanded.map(PathBuf::from),
618 expected_sha256,
619 archive_format: zccache::download_client::ArchiveFormat::Auto,
620 max_connections: None,
621 min_segment_size: None,
622 wait_mode: WaitMode::Block,
623 dry_run: false,
624 force: false,
625 },
626 )
627 .map(NativeFetchState::from)
628 .map_err(runtime_to_py_err)
629 }
630
631 #[pyo3(signature = (cwd, log_file=None, track_stats=false, journal_path=None))]
632 fn session_start(
633 &self,
634 cwd: String,
635 log_file: Option<String>,
636 track_stats: bool,
637 journal_path: Option<String>,
638 ) -> PyResult<NativeSessionStart> {
639 let cwd = PathBuf::from(cwd);
640 let log_file = log_file.map(PathBuf::from);
641 let journal_path = journal_path.map(PathBuf::from);
642 client_session_start(
643 self.endpoint.as_deref(),
644 cwd.as_path(),
645 log_file.as_deref(),
646 track_stats,
647 journal_path.as_deref(),
648 )
649 .map(|result| NativeSessionStart {
650 session_id: result.session_id,
651 journal_path: result.journal_path,
652 })
653 .map_err(runtime_to_py_err)
654 }
655
656 fn session_end(&self, session_id: String) -> PyResult<Option<NativeSessionStats>> {
657 client_session_end(self.endpoint.as_deref(), &session_id)
658 .map(|stats| stats.map(NativeSessionStats::from))
659 .map_err(runtime_to_py_err)
660 }
661
662 fn session_stats(&self, session_id: String) -> PyResult<Option<NativeSessionStats>> {
663 client_session_stats(self.endpoint.as_deref(), &session_id)
664 .map(|stats| stats.map(NativeSessionStats::from))
665 .map_err(runtime_to_py_err)
666 }
667
668 #[pyo3(signature = (
669 cache_file,
670 cache_type="two-layer".to_string(),
671 root=".".to_string(),
672 extensions=vec![],
673 include_globs=vec![],
674 exclude=vec![]
675 ))]
676 fn fingerprint_check(
677 &self,
678 cache_file: String,
679 cache_type: String,
680 root: String,
681 extensions: Vec<String>,
682 include_globs: Vec<String>,
683 exclude: Vec<String>,
684 ) -> PyResult<NativeFingerprintCheck> {
685 fingerprint_check(
686 self.endpoint.as_deref(),
687 PathBuf::from(cache_file).as_path(),
688 &cache_type,
689 PathBuf::from(root).as_path(),
690 &extensions,
691 &include_globs,
692 &exclude,
693 )
694 .map(|result| NativeFingerprintCheck {
695 decision: result.decision,
696 reason: result.reason,
697 changed_files: result.changed_files,
698 })
699 .map_err(runtime_to_py_err)
700 }
701
702 fn fingerprint_mark_success(&self, cache_file: String) -> PyResult<()> {
703 fingerprint_mark_success(
704 self.endpoint.as_deref(),
705 PathBuf::from(cache_file).as_path(),
706 )
707 .map_err(runtime_to_py_err)
708 }
709
710 fn fingerprint_mark_failure(&self, cache_file: String) -> PyResult<()> {
711 fingerprint_mark_failure(
712 self.endpoint.as_deref(),
713 PathBuf::from(cache_file).as_path(),
714 )
715 .map_err(runtime_to_py_err)
716 }
717
718 fn fingerprint_invalidate(&self, cache_file: String) -> PyResult<()> {
719 fingerprint_invalidate(
720 self.endpoint.as_deref(),
721 PathBuf::from(cache_file).as_path(),
722 )
723 .map_err(runtime_to_py_err)
724 }
725}
726
727#[pyfunction]
728#[pyo3(signature = (
729 input,
730 output,
731 clang_args=vec![],
732 inject_arduino_include=true
733))]
734fn convert_ino(
735 input: String,
736 output: String,
737 clang_args: Vec<String>,
738 inject_arduino_include: bool,
739) -> PyResult<NativeInoConvertResult> {
740 run_ino_convert_cached(
741 PathBuf::from(input).as_path(),
742 PathBuf::from(output).as_path(),
743 &InoConvertOptions {
744 clang_args,
745 inject_arduino_include,
746 },
747 )
748 .map(|result| NativeInoConvertResult {
749 cache_hit: result.cache_hit,
750 skipped_write: result.skipped_write,
751 })
752 .map_err(|e| PyErr::new::<PyOSError, _>(e.to_string()))
753}
754
755#[pyfunction]
756fn default_endpoint() -> String {
757 zccache::cli::resolve_endpoint(None)
758}
759
760#[pyfunction]
761fn check_running_daemon() -> Option<u32> {
762 zccache::ipc::check_running_daemon()
763}
764
765#[pymodule]
766fn _native(m: &Bound<'_, PyModule>) -> PyResult<()> {
767 m.add_class::<NativeClient>()?;
768 m.add_class::<NativeDaemonStatus>()?;
769 m.add_class::<NativeSessionStart>()?;
770 m.add_class::<NativeSessionStats>()?;
771 m.add_class::<NativeFingerprintCheck>()?;
772 m.add_class::<NativeInoConvertResult>()?;
773 m.add_class::<NativeDownloadStatus>()?;
774 m.add_class::<NativeDownloadDaemonStatus>()?;
775 m.add_class::<NativeFetchResult>()?;
776 m.add_class::<NativeFetchState>()?;
777 m.add_class::<NativeDownloadHandle>()?;
778 m.add_class::<NativeDownloadApi>()?;
779 m.add_function(wrap_pyfunction!(convert_ino, m)?)?;
780 m.add_function(wrap_pyfunction!(default_endpoint, m)?)?;
781 m.add_function(wrap_pyfunction!(check_running_daemon, m)?)?;
782 Ok(())
783}