1111import trio
1212from vcztools import cli as vcztools_cli
1313
14- from biofuse import access_log , fuse_adapter , plink_client , plink_ops
14+ from biofuse import access_log , encoder_client , encoder_ops , formats , fuse_adapter
1515
1616logger = logging .getLogger (__name__ )
1717
@@ -30,7 +30,7 @@ def wrapper(*args, **kwargs):
3030
3131
3232def _default_basename (vcz_url : str ) -> str :
33- """Strip every suffix off ``vcz_url`` to get a plink fileset stem."""
33+ """Strip every suffix off ``vcz_url`` to get a fileset stem."""
3434 name = pathlib .Path (vcz_url ).name
3535 while True :
3636 stem = pathlib .Path (name ).stem
@@ -46,12 +46,15 @@ def _default_basename(vcz_url: str) -> str:
4646 default = None ,
4747 help = "Write per-read access trace as JSONL to PATH." ,
4848)
49- basename_opt = click .option (
50- "--basename" ,
51- type = str ,
52- default = None ,
53- help = "Basename for the plink fileset (defaults to the VCZ stem)." ,
54- )
49+
50+
51+ def _basename_opt (format_label : str ) -> click .Option :
52+ return click .option (
53+ "--basename" ,
54+ type = str ,
55+ default = None ,
56+ help = f"Basename for the { format_label } fileset (defaults to the VCZ stem)." ,
57+ )
5558
5659
5760@click .group ()
@@ -66,7 +69,7 @@ def biofuse_main():
6669 "mount_dir" ,
6770 type = click .Path (file_okay = False , dir_okay = True , path_type = str ),
6871)
69- @basename_opt
72+ @_basename_opt ( "plink" )
7073@vcztools_cli .view_plink_options
7174@access_log_opt
7275@vcztools_cli .log_options
@@ -85,9 +88,52 @@ def mount_plink(vcz_url, mount_dir, basename, access_log_path, **kwargs):
8588
8689 The mount runs in the foreground until interrupted with Ctrl-C.
8790 """
88- log_config = vcztools_cli .LogConfig .pop_from_click_kwargs (kwargs )
89- reader_options = vcztools_cli .ViewPlinkOptions .pop_from_click_kwargs (kwargs )
90- assert kwargs == {}, kwargs
91+ _run_mount (
92+ formats .PLINK_SPEC , vcz_url , mount_dir , basename , access_log_path , kwargs
93+ )
94+
95+
96+ @biofuse_main .command (name = "mount-bgen" )
97+ @click .argument ("vcz_url" , type = str )
98+ @click .argument (
99+ "mount_dir" ,
100+ type = click .Path (file_okay = False , dir_okay = True , path_type = str ),
101+ )
102+ @_basename_opt ("bgen" )
103+ @vcztools_cli .view_plink_options
104+ @access_log_opt
105+ @vcztools_cli .log_options
106+ @handle_exception
107+ def mount_bgen (vcz_url , mount_dir , basename , access_log_path , ** kwargs ):
108+ """Mount an Oxford BGEN view of VCZ_URL at MOUNT_DIR.
109+
110+ Spawns a bgen-server subprocess that owns the ``VczReader`` and
111+ serves ``.bgen`` reads over an ``AF_UNIX`` socket. ``.sample`` and
112+ ``.bgen.bgi`` are precomputed once at mount time and held in the
113+ FUSE process's memory; only ``.bgen`` reads cross the wire.
114+
115+ The bcftools-view-style filter / backend / log options are inherited
116+ from ``vcztools view-bgen``; see ``vcztools view-bgen --help`` for the
117+ full reference. The encoder always uses zlib level 0 (stored,
118+ fixed-size blocks) so byte-range random access into the mounted
119+ ``.bgen`` is O(1).
120+
121+ The mount runs in the foreground until interrupted with Ctrl-C.
122+ """
123+ _run_mount (formats .BGEN_SPEC , vcz_url , mount_dir , basename , access_log_path , kwargs )
124+
125+
126+ def _run_mount (
127+ spec : formats .FormatSpec ,
128+ vcz_url : str ,
129+ mount_dir : str ,
130+ basename : str | None ,
131+ access_log_path : str | None ,
132+ extra_kwargs : dict ,
133+ ) -> None :
134+ log_config = vcztools_cli .LogConfig .pop_from_click_kwargs (extra_kwargs )
135+ reader_options = vcztools_cli .ViewPlinkOptions .pop_from_click_kwargs (extra_kwargs )
136+ assert extra_kwargs == {}, extra_kwargs
91137 log_config .apply ()
92138
93139 mount_dir_path = pathlib .Path (mount_dir )
@@ -98,9 +144,10 @@ def mount_plink(vcz_url, mount_dir, basename, access_log_path, **kwargs):
98144 resolved_basename = basename if basename is not None else _default_basename (vcz_url )
99145
100146 with tempfile .TemporaryDirectory (prefix = "biofuse-" ) as sock_dir :
101- sock_path = pathlib .Path (sock_dir ) / "plink .sock"
147+ sock_path = pathlib .Path (sock_dir ) / f" { spec . name } .sock"
102148 trio .run (
103149 _amount ,
150+ spec ,
104151 vcz_url ,
105152 str (mount_dir_path ),
106153 resolved_basename ,
@@ -112,6 +159,7 @@ def mount_plink(vcz_url, mount_dir, basename, access_log_path, **kwargs):
112159
113160
114161async def _amount (
162+ spec : formats .FormatSpec ,
115163 vcz_url : str ,
116164 mount_dir : str ,
117165 basename : str ,
@@ -120,14 +168,17 @@ async def _amount(
120168 log_path : pathlib .Path | None ,
121169 sock_path : pathlib .Path ,
122170) -> None :
123- async with await plink_client . PlinkClient .start (
171+ async with await encoder_client . EncoderClient .start (
124172 vcz_url ,
125173 sock_path ,
174+ spec ,
126175 reader_options = reader_options ,
127176 log_config = log_config ,
128177 ) as client :
129178 with access_log .AccessLogger (log_path ) as access_logger :
130- ops = plink_ops .PlinkOps (client , basename , access_logger = access_logger )
179+ ops = encoder_ops .EncoderOps (
180+ client , basename , spec , access_logger = access_logger
181+ )
131182 async with fuse_adapter .mount (ops , mount_dir ):
132183 click .echo (f"mounted at { mount_dir } " , err = True )
133184 await _wait_for_signal ()
0 commit comments