From 90add653c59e4522592c618aa0c3760cb78c4c7b Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sun, 12 May 2024 11:14:36 -0400 Subject: [PATCH 1/8] Expose path steps --- flatgfa-py/example.py | 8 ++++ flatgfa-py/src/lib.rs | 91 +++++++++++++++++++++++++++++++++++++++++-- flatgfa/src/pool.rs | 4 ++ 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/flatgfa-py/example.py b/flatgfa-py/example.py index aafb574e..1be5e640 100644 --- a/flatgfa-py/example.py +++ b/flatgfa-py/example.py @@ -9,3 +9,11 @@ print(len(g.segments)) for path in g.paths: print(path, path.name) + print(len(path)) + print(','.join( + '{}{}'.format( + s.segment.name, + '+' if s.is_forward else '-' + ) + for s in path + )) diff --git a/flatgfa-py/src/lib.rs b/flatgfa-py/src/lib.rs index 897d7736..090fe048 100644 --- a/flatgfa-py/src/lib.rs +++ b/flatgfa-py/src/lib.rs @@ -1,4 +1,4 @@ -use flatgfa::pool::Id; +use flatgfa::pool::{Id, Span}; use flatgfa::{self, FlatGFA, HeapGFAStore}; use pyo3::prelude::*; use pyo3::types::PyBytes; @@ -122,12 +122,12 @@ macro_rules! gen_container { fn __next__(&mut self) -> Option<$pytype> { let gfa = self.store.view(); if self.idx < gfa.$field.len() as u32 { - let seg = $pytype { + let obj = $pytype { store: self.store.clone(), id: Id::from(self.idx), }; self.idx += 1; - Some(seg) + Some(obj) } else { None } @@ -211,6 +211,90 @@ impl PyPath { fn __repr__(&self) -> String { format!("", u32::from(self.id)) } + + fn __iter__(&self) -> StepIter { + let path = self.store.view().paths[self.id]; + StepIter { + store: self.store.clone(), + span: path.steps, + cur: path.steps.start, + } + } + + fn __len__(&self) -> usize { + let path = self.store.view().paths[self.id]; + path.steps.len() + } +} + +/// An oriented segment reference. +#[pyclass(frozen)] +#[pyo3(name = "Handle", module = "flatgfa")] +struct PyHandle { + store: Arc, + handle: flatgfa::Handle, +} + +#[pymethods] +impl PyHandle { + /// The segment ID. + #[getter] + fn seg_id(&self) -> u32 { + self.handle.segment().into() + } + + /// The orientation. + #[getter] + fn is_forward(&self) -> bool { + self.handle.orient() == flatgfa::Orientation::Forward + } + + /// The segment. + #[getter] + fn segment(&self) -> PySegment { + PySegment { + store: self.store.clone(), + id: self.handle.segment(), + } + } + + fn __repr__(&self) -> String { + format!( + "", + u32::from(self.handle.segment()), + self.handle.orient() + ) + } +} + +/// An iterator over the steps in a path. +#[pyclass] +#[pyo3(module = "flatgfa")] +struct StepIter { + store: Arc, + span: Span, + cur: Id, +} + +#[pymethods] +impl StepIter { + fn __iter__(self_: Py) -> Py { + self_ + } + + fn __next__(&mut self) -> Option { + let gfa = self.store.view(); + if self.span.contains(self.cur) { + let handle = PyHandle { + store: self.store.clone(), + handle: gfa.steps[self.cur], + }; + self.cur = (u32::from(self.cur) + 1).into(); + Some(handle) + } else { + None + } + } } #[pymodule] @@ -221,5 +305,6 @@ fn pymod(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(load, m)?)?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/flatgfa/src/pool.rs b/flatgfa/src/pool.rs index bf61cb18..1c07c0ed 100644 --- a/flatgfa/src/pool.rs +++ b/flatgfa/src/pool.rs @@ -71,6 +71,10 @@ impl Span { (self.end.0 - self.start.0) as usize } + pub fn contains(&self, id: Id) -> bool { + self.start.0 <= id.0 && id.0 < self.end.0 + } + pub fn new(start: Id, end: Id) -> Self { Self { start, From 3e6a1ed69d733acc2fc91f3d6d263b316227c727 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sun, 12 May 2024 11:20:54 -0400 Subject: [PATCH 2/8] Expose the links --- flatgfa-py/example.py | 2 ++ flatgfa-py/src/lib.rs | 50 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/flatgfa-py/example.py b/flatgfa-py/example.py index 1be5e640..b81830cf 100644 --- a/flatgfa-py/example.py +++ b/flatgfa-py/example.py @@ -17,3 +17,5 @@ ) for s in path )) +for link in g.links: + print(link, link.from_, link.to) diff --git a/flatgfa-py/src/lib.rs b/flatgfa-py/src/lib.rs index 090fe048..acf46a63 100644 --- a/flatgfa-py/src/lib.rs +++ b/flatgfa-py/src/lib.rs @@ -73,6 +73,14 @@ impl PyFlatGFA { store: self.0.clone(), } } + + /// The links in the graph. + #[getter] + fn links(&self) -> LinkList { + LinkList { + store: self.0.clone(), + } + } } /// Generate the Python types for an iterable container of GFA objects. @@ -138,6 +146,7 @@ macro_rules! gen_container { gen_container!(Segment, segs, PySegment, SegmentList, SegmentIter); gen_container!(Path, paths, PyPath, PathList, PathIter); +gen_container!(Link, links, PyLink, LinkList, LinkIter); /// A segment in a GFA graph. /// @@ -297,6 +306,46 @@ impl StepIter { } } +/// A link in a GFA graph. +/// +/// Links are directed edges between oriented segments. The source and sink are both +/// `Handle`s, i.e., the "forward" or "backward" direction of a given segment. +#[pyclass(frozen)] +#[pyo3(name = "Link", module = "flatgfa")] +struct PyLink { + store: Arc, + id: Id, +} + +#[pymethods] +impl PyLink { + /// The unique identifier for the link. + #[getter] + fn id(&self) -> u32 { + self.id.into() + } + + fn __repr__(&self) -> String { + format!("", u32::from(self.id)) + } + + #[getter] + fn from_(&self) -> PyHandle { + PyHandle { + store: self.store.clone(), + handle: self.store.view().links[self.id].from, + } + } + + #[getter] + fn to(&self) -> PyHandle { + PyHandle { + store: self.store.clone(), + handle: self.store.view().links[self.id].to, + } + } +} + #[pymodule] #[pyo3(name = "flatgfa")] fn pymod(m: &Bound<'_, PyModule>) -> PyResult<()> { @@ -306,5 +355,6 @@ fn pymod(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; Ok(()) } From db47bb0b0e9a7fd0d507c2e1694e25dc81d3eb3c Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sun, 12 May 2024 11:21:55 -0400 Subject: [PATCH 3/8] Document from/to for links --- flatgfa-py/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flatgfa-py/src/lib.rs b/flatgfa-py/src/lib.rs index acf46a63..0424a43b 100644 --- a/flatgfa-py/src/lib.rs +++ b/flatgfa-py/src/lib.rs @@ -329,6 +329,7 @@ impl PyLink { format!("", u32::from(self.id)) } + /// The edge's source handle. #[getter] fn from_(&self) -> PyHandle { PyHandle { @@ -337,6 +338,7 @@ impl PyLink { } } + /// The edge's sink handle. #[getter] fn to(&self) -> PyHandle { PyHandle { From 886b8929afeaa4f98cd771ea2707844be9cbada7 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sun, 12 May 2024 12:01:33 -0400 Subject: [PATCH 4/8] Name lookup --- flatgfa-py/Cargo.lock | 7 +++++++ flatgfa-py/Cargo.toml | 2 +- flatgfa-py/example.py | 3 +++ flatgfa-py/src/lib.rs | 26 ++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/flatgfa-py/Cargo.lock b/flatgfa-py/Cargo.lock index 6539964e..3b85ab62 100644 --- a/flatgfa-py/Cargo.lock +++ b/flatgfa-py/Cargo.lock @@ -134,6 +134,12 @@ version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +[[package]] +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + [[package]] name = "libc" version = "0.2.154" @@ -266,6 +272,7 @@ checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" dependencies = [ "cfg-if", "indoc", + "inventory", "libc", "memoffset", "parking_lot", diff --git a/flatgfa-py/Cargo.toml b/flatgfa-py/Cargo.toml index e7579488..f2e12dba 100644 --- a/flatgfa-py/Cargo.toml +++ b/flatgfa-py/Cargo.toml @@ -8,6 +8,6 @@ name = "flatgfa" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.21.2", features = ["abi3-py38"] } +pyo3 = { version = "0.21.2", features = ["abi3-py38", "multiple-pymethods"] } flatgfa = { path = "../flatgfa" } memmap = "0.7.0" diff --git a/flatgfa-py/example.py b/flatgfa-py/example.py index b81830cf..e664c22b 100644 --- a/flatgfa-py/example.py +++ b/flatgfa-py/example.py @@ -19,3 +19,6 @@ )) for link in g.links: print(link, link.from_, link.to) + +print(g.paths.find(b"x")) +print(g.segments.find(2)) diff --git a/flatgfa-py/src/lib.rs b/flatgfa-py/src/lib.rs index 0424a43b..5466ec4a 100644 --- a/flatgfa-py/src/lib.rs +++ b/flatgfa-py/src/lib.rs @@ -190,6 +190,19 @@ impl PySegment { } } +#[pymethods] +impl SegmentList { + /// Find a segment by its name, or return None if not found. + fn find(&self, name: usize) -> Option { + let gfa = self.store.view(); + let id = gfa.find_seg(name)?; + Some(PySegment { + store: self.store.clone(), + id, + }) + } +} + /// A path in a GFA graph. /// /// Paths are walks through the GFA graph, where each step is an oriented segment. @@ -236,6 +249,19 @@ impl PyPath { } } +#[pymethods] +impl PathList { + /// Find a path by its name, or return None if not found. + fn find(&self, name: &[u8]) -> Option { + let gfa = self.store.view(); + let id = gfa.find_path(name.as_ref())?; + Some(PyPath { + store: self.store.clone(), + id, + }) + } +} + /// An oriented segment reference. #[pyclass(frozen)] #[pyo3(name = "Handle", module = "flatgfa")] From 1a6edbd2eb82587f5454812702a1feada82fa207 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sun, 12 May 2024 17:19:57 -0400 Subject: [PATCH 5/8] Refactor printing to use Display trait Now it's possible to print GFAs to places other than stdout, which seems like a good thing to me. --- flatgfa/src/main.rs | 6 +- flatgfa/src/print.rs | 136 ++++++++++++++++++++++++------------------- 2 files changed, 81 insertions(+), 61 deletions(-) diff --git a/flatgfa/src/main.rs b/flatgfa/src/main.rs index b85ebb0a..d46d2bf6 100644 --- a/flatgfa/src/main.rs +++ b/flatgfa/src/main.rs @@ -1,7 +1,7 @@ use argh::FromArgs; use flatgfa::flatgfa::FlatGFA; use flatgfa::parse::Parser; -use flatgfa::{cmds, file, parse, print}; +use flatgfa::{cmds, file, parse}; #[derive(FromArgs)] /// Convert between GFA text and FlatGFA binary formats. @@ -122,7 +122,9 @@ fn dump(gfa: &FlatGFA, output: &Option) { file::dump(gfa, &mut mmap); mmap.flush().unwrap(); } - None => print::print(gfa), + None => { + print!("{}", gfa); + } } } diff --git a/flatgfa/src/print.rs b/flatgfa/src/print.rs index cdf06fd3..5d0b054d 100644 --- a/flatgfa/src/print.rs +++ b/flatgfa/src/print.rs @@ -2,7 +2,7 @@ use crate::flatgfa; use std::fmt; impl fmt::Display for flatgfa::Orientation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { flatgfa::Orientation::Forward => write!(f, "+"), flatgfa::Orientation::Backward => write!(f, "-"), @@ -11,7 +11,7 @@ impl fmt::Display for flatgfa::Orientation { } impl fmt::Display for flatgfa::AlignOpcode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { flatgfa::AlignOpcode::Match => write!(f, "M"), flatgfa::AlignOpcode::Gap => write!(f, "N"), @@ -22,7 +22,7 @@ impl fmt::Display for flatgfa::AlignOpcode { } impl<'a> fmt::Display for flatgfa::Alignment<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for op in self.ops { write!(f, "{}{}", op.len(), op.op())?; } @@ -30,59 +30,70 @@ impl<'a> fmt::Display for flatgfa::Alignment<'a> { } } -fn print_step(gfa: &flatgfa::FlatGFA, handle: flatgfa::Handle) { - let seg = gfa.get_handle_seg(handle); - let name = seg.name; - print!("{}{}", name, handle.orient()); -} +/// A wrapper for displaying components from FlatGFA. +struct Display<'a, T>(&'a flatgfa::FlatGFA<'a>, T); -fn print_path(gfa: &flatgfa::FlatGFA, path: &flatgfa::Path) { - print!("P\t{}\t", gfa.get_path_name(path)); - let steps = &gfa.steps[path.steps]; - print_step(gfa, steps[0]); - for step in steps[1..].iter() { - print!(","); - print_step(gfa, *step); +impl<'a> fmt::Display for Display<'a, flatgfa::Handle> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let seg = self.0.get_handle_seg(self.1); + let name = seg.name; + write!(f, "{}{}", name, self.1.orient()) } - print!("\t"); - let overlaps = &gfa.overlaps[path.overlaps]; - if overlaps.is_empty() { - print!("*"); - } else { - print!("{}", gfa.get_alignment(overlaps[0])); - for overlap in overlaps[1..].iter() { - print!(",{}", gfa.get_alignment(*overlap)); +} + +impl<'a> fmt::Display for Display<'a, &flatgfa::Path> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "P\t{}\t", self.0.get_path_name(&self.1))?; + let steps = &self.0.steps[self.1.steps]; + write!(f, "{}", Display(self.0, steps[0]))?; + for step in steps[1..].iter() { + write!(f, ",{}", Display(self.0, *step))?; + } + write!(f, "\t")?; + let overlaps = &self.0.overlaps[self.1.overlaps]; + if overlaps.is_empty() { + write!(f, "*")?; + } else { + write!(f, "{}", self.0.get_alignment(overlaps[0]))?; + for overlap in overlaps[1..].iter() { + write!(f, ",{}", self.0.get_alignment(*overlap))?; + } } + writeln!(f) } - println!(); } -fn print_link(gfa: &flatgfa::FlatGFA, link: &flatgfa::Link) { - let from = link.from; - let from_name = gfa.get_handle_seg(from).name; - let to = link.to; - let to_name = gfa.get_handle_seg(to).name; - println!( - "L\t{}\t{}\t{}\t{}\t{}", - from_name, - from.orient(), - to_name, - to.orient(), - gfa.get_alignment(link.overlap) - ); +impl<'a> fmt::Display for Display<'a, &flatgfa::Link> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let from = self.1.from; + let from_name = self.0.get_handle_seg(from).name; + let to = self.1.to; + let to_name = self.0.get_handle_seg(to).name; + writeln!( + f, + "L\t{}\t{}\t{}\t{}\t{}", + from_name, + from.orient(), + to_name, + to.orient(), + self.0.get_alignment(self.1.overlap) + ) + } } -fn print_seg(gfa: &flatgfa::FlatGFA, seg: &flatgfa::Segment) { - let name = seg.name; - print!("S\t{}\t{}", name, gfa.get_seq(seg)); - if !seg.optional.is_empty() { - print!("\t{}", gfa.get_optional_data(seg)); +impl<'a> fmt::Display for Display<'a, &flatgfa::Segment> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = self.1.name; + write!(f, "S\t{}\t{}", name, self.0.get_seq(self.1))?; + if !self.1.optional.is_empty() { + write!(f, "\t{}", self.0.get_optional_data(self.1))?; + } + writeln!(f) } - println!(); } /// Print a graph in the order preserved from an original GFA file. -fn print_preserved(gfa: &flatgfa::FlatGFA) { +fn write_preserved(gfa: &flatgfa::FlatGFA, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut seg_iter = gfa.segs.all().iter(); let mut path_iter = gfa.paths.all().iter(); let mut link_iter = gfa.links.all().iter(); @@ -91,42 +102,49 @@ fn print_preserved(gfa: &flatgfa::FlatGFA) { flatgfa::LineKind::Header => { let version = gfa.header; assert!(!version.is_empty()); - println!("H\t{}", bstr::BStr::new(version.all())); + writeln!(f, "H\t{}", bstr::BStr::new(version.all()))?; } flatgfa::LineKind::Segment => { - print_seg(gfa, seg_iter.next().expect("too few segments")); + let seg = seg_iter.next().expect("too few segments"); + write!(f, "{}", Display(gfa, seg))?; } flatgfa::LineKind::Path => { - print_path(gfa, path_iter.next().expect("too few paths")); + let path = path_iter.next().expect("too few paths"); + write!(f, "{}", Display(gfa, path))?; } flatgfa::LineKind::Link => { - print_link(gfa, link_iter.next().expect("too few links")); + let link = link_iter.next().expect("too few links"); + write!(f, "{}", Display(gfa, link))?; } } } + Ok(()) } /// Print a graph in a normalized order, ignoring the original GFA line order. -pub fn print_normalized(gfa: &flatgfa::FlatGFA) { +pub fn write_normalized(gfa: &flatgfa::FlatGFA, f: &mut fmt::Formatter<'_>) -> fmt::Result { if !gfa.header.is_empty() { - println!("H\t{}", bstr::BStr::new(gfa.header.all())); + writeln!(f, "H\t{}", bstr::BStr::new(gfa.header.all()))?; } for seg in gfa.segs.all().iter() { - print_seg(gfa, seg); + write!(f, "{}", Display(gfa, seg))?; } for path in gfa.paths.all().iter() { - print_path(gfa, path); + write!(f, "{}", Display(gfa, path))?; } for link in gfa.links.all().iter() { - print_link(gfa, link); + write!(f, "{}", Display(gfa, link))?; } + Ok(()) } -/// Print our flat representation as a GFA text file to stdout. -pub fn print(gfa: &flatgfa::FlatGFA) { - if gfa.line_order.is_empty() { - print_normalized(gfa); - } else { - print_preserved(gfa); +/// Print our flat representation as in GFA text format. +impl<'a> fmt::Display for &'a flatgfa::FlatGFA<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.line_order.is_empty() { + write_normalized(self, f) + } else { + write_preserved(self, f) + } } } From 4da4c262f060ffb597cdc0fd91741aaea9aed3c3 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sun, 12 May 2024 17:22:29 -0400 Subject: [PATCH 6/8] Stringify entire GFAs --- flatgfa-py/example.py | 2 ++ flatgfa-py/src/lib.rs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/flatgfa-py/example.py b/flatgfa-py/example.py index e664c22b..8d785502 100644 --- a/flatgfa-py/example.py +++ b/flatgfa-py/example.py @@ -22,3 +22,5 @@ print(g.paths.find(b"x")) print(g.segments.find(2)) + +print(g) diff --git a/flatgfa-py/src/lib.rs b/flatgfa-py/src/lib.rs index 5466ec4a..448b269b 100644 --- a/flatgfa-py/src/lib.rs +++ b/flatgfa-py/src/lib.rs @@ -81,6 +81,11 @@ impl PyFlatGFA { store: self.0.clone(), } } + + fn __str__(&self) -> String { + let gfa = self.0.view(); + format!("{}", &gfa) + } } /// Generate the Python types for an iterable container of GFA objects. From a2567ebe9fafbb65d3ddf702c23cde11fb2b4565 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sun, 12 May 2024 19:38:41 -0400 Subject: [PATCH 7/8] Dump graphs to file from Python --- flatgfa-py/example.py | 2 ++ flatgfa-py/src/lib.rs | 26 +++++++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/flatgfa-py/example.py b/flatgfa-py/example.py index 8d785502..7ed710ea 100644 --- a/flatgfa-py/example.py +++ b/flatgfa-py/example.py @@ -24,3 +24,5 @@ print(g.segments.find(2)) print(g) +g.write_gfa("temp.gfa") +g.write_flatgfa("temp.flatgfa") diff --git a/flatgfa-py/src/lib.rs b/flatgfa-py/src/lib.rs index 448b269b..bc953fcf 100644 --- a/flatgfa-py/src/lib.rs +++ b/flatgfa-py/src/lib.rs @@ -1,7 +1,8 @@ use flatgfa::pool::{Id, Span}; -use flatgfa::{self, FlatGFA, HeapGFAStore}; +use flatgfa::{self, file, FlatGFA, HeapGFAStore}; use pyo3::prelude::*; use pyo3::types::PyBytes; +use std::io::Write; use std::sync::Arc; /// Storage for a FlatGFA. @@ -16,14 +17,14 @@ enum Store { impl Store { /// Parse a text GFA file. fn parse(filename: &str) -> Self { - let file = flatgfa::file::map_file(filename); + let file = file::map_file(filename); let store = flatgfa::parse::Parser::for_heap().parse_mem(file.as_ref()); Self::Heap(Box::new(store)) } /// Load a FlatGFA binary file. fn load(filename: &str) -> Self { - let mmap = flatgfa::file::map_file(filename); + let mmap = file::map_file(filename); Self::File(mmap) } @@ -34,7 +35,7 @@ impl Store { // e.g., with the `owning_ref` crate. match self { Store::Heap(ref store) => (**store).as_ref(), - Store::File(ref mmap) => flatgfa::file::view(mmap), + Store::File(ref mmap) => file::view(mmap), } } } @@ -83,8 +84,23 @@ impl PyFlatGFA { } fn __str__(&self) -> String { + format!("{}", &self.0.view()) + } + + /// Write the graph as a GFA text file. + fn write_gfa(&self, filename: &str) -> PyResult<()> { + let mut file = std::fs::File::create(filename)?; + write!(file, "{}", &self.0.view())?; + Ok(()) + } + + /// Write the graph as a binary FlatGFA file. + fn write_flatgfa(&self, filename: &str) -> PyResult<()> { let gfa = self.0.view(); - format!("{}", &gfa) + let mut mmap = file::map_new_file(filename, file::size(&gfa) as u64); + file::dump(&gfa, &mut mmap); + mmap.flush()?; + Ok(()) } } From 1bbf38fa057634dc740eed6a55e09e4297b983fc Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sun, 12 May 2024 19:42:10 -0400 Subject: [PATCH 8/8] Format example with Black --- flatgfa-py/example.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/flatgfa-py/example.py b/flatgfa-py/example.py index 7ed710ea..805bef9b 100644 --- a/flatgfa-py/example.py +++ b/flatgfa-py/example.py @@ -10,13 +10,11 @@ for path in g.paths: print(path, path.name) print(len(path)) - print(','.join( - '{}{}'.format( - s.segment.name, - '+' if s.is_forward else '-' + print( + ",".join( + "{}{}".format(s.segment.name, "+" if s.is_forward else "-") for s in path ) - for s in path - )) + ) for link in g.links: print(link, link.from_, link.to)