Repository: hasindu2008/slow5lib (language: python)

Issue:
initial implementation of getting a random batch



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index c893fa00..0848971a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,6 +76,7 @@ dist/
 pyslow5.egg-info/
 docs/doxygen
 examples/xample.slow5.idx
+/.eggs
 
 # testing output
 *actual*
diff --git a/Makefile b/Makefile
index fa10a117..2c5632fa 100644
--- a/Makefile
+++ b/Makefile
@@ -77,7 +77,7 @@ test: slow5lib
 
 pyslow5:
 	make clean
-	rm -rf *.so python/pyslow5.cpp build/lib.* build/temp.* sdist
+	rm -rf *.so python/pyslow5.cpp build/lib.* build/temp.* build/bdist.* sdist pyslow5.egg-info dist
 	python3 setup.py build
 	cp build/lib.*/*.so  ./
 	python3 < python/example.py
diff --git a/examples/.gitignore b/examples/.gitignore
index 4c7747e3..fa5d2564 100644
--- a/examples/.gitignore
+++ b/examples/.gitignore
@@ -5,3 +5,11 @@ header_attribute
 random_read_pthreads
 random_read_openmp
 *.idx
+example_write_aux_multi.slow5
+example_write.slow5
+example_write_append.blow5
+example_write_append_aux.blow5
+example_write_aux.slow5
+example_write_aux_multi.slow5
+example_write_aux_multi.slow5
+example_write_append.slow5
diff --git a/python/README.md b/python/README.md
index fced10c6..3b8ef9a2 100644
--- a/python/README.md
+++ b/python/README.md
@@ -1,11 +1,28 @@
 # pyslow5 python library
 
-The slow5 python library (pyslow5) allows a user to read slow5 and blow5 files.
+The slow5 python library (pyslow5) allows a user to read and write slow5/blow5 files.
 
 ## Installation
 
 Initial setup and example info for environment
+
 ###### slow5lib needs python3.4.2 or higher.
+
+If you only want to use the python library, then you can simply install using pip
+
+Using a virtual environment (see below if you need to install python)
+
+```bash
+python3 -m venv path/to/slow5libvenv
+source path/to/slow5libvenv/bin/activate
+python3 -m pip install --upgrade pip
+python3 -m pip install setuptools cython numpy wheel
+# do this separately, after the libs above
+python3 -m pip install pyslow5
+```
+
+### Dev install
+
 ```bash
 # If your native python3 meets this requirement, you can use that, or use a
 # specific version installed with deadsnakes below. If you install with deadsnakes,
@@ -25,7 +42,7 @@ sudo apt install python3.7 python3.7-dev python3.7-venv
 # get zlib1g-dev
 sudo apt-get update && sudo apt-get install -y zlib1g-dev
 
-# Chekc with
+# Check with
 python3 --version
 
 # You will also need the python headers if you don't already have them installed.
@@ -45,7 +62,8 @@ git clone git@github.com:hasindu2008/slow5lib.git
 cd slow5lib
 make
 
-# CHOOSE A OR B:
+# CHOOSE A OR B:  
+# (B is the cleanest method)
 # |=======================================================================|
 # |A. Install with pip if wheel is present, otherwise it uses setuptools  |
     python3 -m pip install . --use-feature=in-tree-build
@@ -71,12 +89,17 @@ python3 -m pip uninstall pyslow5
 
 ## Usage
 
+### Reading a file
+
 #### `Open(FILE, mode, DEBUG=0)`:
 
-The pyslow5 libraryr has one main Class, `pyslow5.Open` which opens a slow5/blow5 (slow5 for easy reference) file for reading.
+The pyslow5 library has one main Class, `pyslow5.Open` which opens a slow5/blow5 (slow5 for easy reference) file for reading/writing.
 
 `FILE`: the file or filepath of the slow5 file to open
-`mode`: mode in which to open the file. Currently, only `r` is accepted for read only.
+`mode`: mode in which to open the file.
++ `r`= read only
++ `w`= write/overwrite
++ `a`= append
 
 This is designed to mimic Python's native Open() to help users remember the syntax
 
@@ -122,6 +145,40 @@ for read in s5.seq_reads(pA=True, aux='all'):
     print("================================")
 ```
 
+
+#### `seq_reads_multi(threads=4, batchsize=4096, pA=False, aux=None)`:
+
+Access all reads sequentially in an opened slow5, using multiple threads.
++ If readID is not found, `None` is returned.
++ threads = number of threads to use in C backend.
++ batchsize = number of reads to fetch at a time. Higher numbers use more ram, but is more efficient with more threads.
++ pA = Bool for converting signal to picoamps.
++ aux = `str` '<attr_name>'/'all' or list of names of auxiliary fields added to return dictionary, `None` if `<attr_name>` not found
++ returns `dict` = dictionary of main fields for read_id, with any aux fields added
+
+Example:
+
+```python
+# create generator
+reads = s5.seq_reads_multi(threads=2, batchsize=3)
+
+# print all readIDs
+for read in reads:
+    print(read['read_id'])
+
+# or use directly in a for loop
+for read in s5.seq_reads_multi(threads=2, batchsize=3, pA=True, aux='all'):
+    print("read_id:", read['read_id'])
+    print("read_group:", read['read_group'])
+    print("digitisation:", read['digitisation'])
+    print("offset:", read['offset'])
+    print("range:", read['range'])
+    print("sampling_rate:", read['sampling_rate'])
+    print("len_raw_signal:", read['len_raw_signal'])
+    print("signal:", read['signal'][:10])
+    print("================================")
+```
+
 #### `get_read(readID, pA=False, aux=None)`:
 
 Access a specific read using a unique readID. This is a ranom access method, using the index.
@@ -143,7 +200,7 @@ if read is not None:
 
 #### `get_read_list(read_list, pA=False, aux=None)`:
 
-Access a list of specific reads using a list `read_list` of unique readIDs. This is a random access method using the index, so order of readIDs does impact access speed.
+Access a list of specific reads using a list `read_list` of unique readIDs. This is a random access method using the index. If an index does not exist, it will create one first.
 + If readID is not found, `None` is returned.
 + pA = Bool for converting signal to picoamps.
 + aux = `str` '<attr_name>'/'all' or list of names of auxiliary fields added to return dictionary, `None` if `<attr_name>` not found
@@ -162,6 +219,28 @@ for r, read in zip(read_list,selected_reads):
 ```
 
 
+#### `get_read_list_multi(read_list, threads=4, batchsize=100, pA=False, aux=None):`:
+
+Access a list of specific reads using a list `read_list` of unique readIDs using multiple threads. This is a random access method using the index. If an index does not exist, it will create one first.
++ If readID is not found, `None` is returned.
++ threads = number of threads to use in C backend
++ batchsize = number of reads to fetch at a time. Higher numbers use more ram, but is more efficient with more threads.
++ pA = Bool for converting signal to picoamps.
++ aux = `str` '<attr_name>'/'all' or list of names of auxiliary fields added to return dictionary, `None` if `<attr_name>` not found
++ returns `dict` = dictionary of main fields for read_id, with any aux fields added
+Example:
+
+```python
+read_list = ["r1", "r3", "null_read", "r5", "r2", "r1"]
+selected_reads = s5.get_read_list_multi(read_list, threads=2, batchsize=3)
+for r, read in zip(read_list, selected_reads):
+    if read is not None:
+        print(r, read['read_id'])
+    else:
+        print(r, "read not found")
+```
+
+
 #### `get_header_names()`:
 
 Returns a list containing the uninon of header names from all read_groups
@@ -188,4 +267,155 @@ Returns an ordered list of auxiliary attribute types (same order as get_aux_name
 
 This can mostly be ignored, but will be used in error tracing in the future, as auxiliary field requests have multiple types, each with their own calls, and not all are used. It could be the case a call for an auxiliary filed fails, and knowing which type the field is requesting is very helpful in understanding which function in C is being called, that could be causing the error.
 
-See documentation for full example
+### Writing a file
+
+To write a file, `mode` in `Open()` must be set to `'w'` and when appending, `'a'`
+
+#### `get_empty_header()`:
+
+Returns a dictionary containing all known header attributes with their values set to `None`.
+
+User can modify each value, and add or remove attributes to be used has header items.
+All values end up stored as strings, and anything left as `None` will be skipped.
+To write header, see `write_header()`
+
+Example:
+
+```python
+s5 = slow5.Open(file,'w')
+header = s5.get_empty_header()
+```
+
+#### `write_header(header, read_group=0)`:
+
+Write header to file
+
++ `header` = populated dictionary from `get_empty_header()`
++ read_group = read group integer for when multiple runs are written to the same slow5 file
++ returns 0 on success, <0 on error with error code
+
+You must write `read_group=0` (default) first before writing any other read_groups, and it is advised to write read_groups in sequential order.
+
+Example:
+
+```python
+# Get some empty headers
+header = s5.get_empty_header()
+header2 = s5.get_empty_header()
+
+# Populate headers with some test data
+counter = 0
+for i in header:
+    header[i] = "test_{}".format(counter)
+    counter += 1
+
+for i in header2:
+    header2[i] = "test_{}".format(counter)
+    counter += 1
+
+# Write first read group
+ret = s5.write_header(header)
+print("ret: write_header(): {}".format(ret))
+# Write second read group, etc
+ret = s5.write_header(header2, read_group=1)
+print("ret: write_header(): {}".format(ret))
+```
+
+#### `get_empty_record(aux=False)`:
+
+Get empty read record for populating with data. Use with `write_record()`
+
++ aux = Bool for returning empty aux dictionary as well as read dictionary
++ returns a single read dictionary or a read and aux dictionary depending on aux flag
+
+Example:
+```python
+# open some file to read. We will copy the data then write it
+# including aux fields
+s5_read = slow5.Open(read_file,'r')
+reads = s5_read.seq_reads(aux='all')
+
+# For each read in s5_read...
+for read in reads:
+    # get an empty record and aux dictionary
+    record, aux = s5.get_empty_record(aux=True)
+    # for each field in read...
+    for i in read:
+        # if the field is in the record dictionary...
+        if i in record:
+            # copy the value over...
+            record[i] = read[i]
+        do same for aux dictionary
+        if i in aux:
+            aux[i] = read[i]
+    # write the record
+    ret = s5.write_record(record, aux)
+    print("ret: write_record(): {}".format(ret))
+```
+
+#### `write_record(record, aux=None)`:
+
+Write a record and optional aux fields.
+
++ record = a populated dictionary from `get_empty_record()`
++ aux = an empty aux record returned by `get_empty_record(aux=True)`
++ returns 0 on success and -1 on error/failure
+
+Example:
+
+```python
+
+record, aux = s5.get_empty_record(aux=True)
+# populate record, aux dictionaries
+#....
+# Write record
+ret = s5.write_record(record, aux)
+print("ret: write_record(): {}".format(ret))
+```
+
+
+#### `write_record_batch(records, threads=4, batchsize=4096, aux=None)`:
+
+Write a record and optional aux fields, using multiple threads
+
++ records = a dictionary of dictionaries where each entry is a populated form of `get_empty_record()` with the key of each being the read['read_id'].
++ threads = number of threads to use in the C backend.
++ batchsize = number of reads to write at a time. If parsing 1000 records, with batchsize=250 and threads=4, 4 threads will be spawned 4 times to write 250 records to the file before returning
++ aux = an empty aux record returned by `get_empty_record(aux=True)`
++ returns 0 on success and -1 on error/failure
+
+Example:
+
+```python
+
+record, aux = s5.get_empty_record(aux=True)
+# populate record, aux
+#....
+records[record['read_id']] = record
+auxs[record['read_id']] = aux
+# Write record
+ret = s5.write_record_batch(records, threads=2, batchsize=3, aux=auxs)
+print("ret: write_record(): {}".format(ret))
+```
+
+#### `close()`:
+
+Closes a record open for writing or appending, and writes an End Of File (EOF) flag.
+
+If not explicitly closed, when the `s5` object goes out of context in python, it will also trigger a close to attempt to avoid having a missing EOF.
+
+Please call this when you are finished writing a file.
+
+Example:
+
+```python
+s5 = slow5.Open(file,'w')
+
+# do some writing....
+
+# Write's EOF and closes file
+s5.close()
+```
+
+
+See documentation for full examples
diff --git a/python/example.py b/python/example.py
index 71c20c52..356c2776 100644
--- a/python/example.py
+++ b/python/example.py
@@ -125,7 +125,8 @@
     val = s5.get_header_value(attr)
     print("{}: {}".format(attr, val))
 
-
+s5.close()
+print("==============================================")
 s52 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
 print("get_read check, 0d624d4b-671f-40b8-9798-84f2ccc4d7fc")
 read1 = s52.get_read("0d624d4b-671f-40b8-9798-84f2ccc4d7fc", aux=["read_number", "start_mux", "blah"])
@@ -184,5 +185,431 @@
     print(read['read_id'])
     print("read_number", read['read_number'])
 
+s52.close()
+print("==============================================")
+
+print("write reads no aux")
+
+F = slow5.Open('examples/example_write.slow5','w', DEBUG=debug)
+header = F.get_empty_header()
+
+counter = 0
+for i in header:
+    header[i] = "test_{}".format(counter)
+    counter += 1
+
+ret = F.write_header(header)
+print("ret: write_header(): {}".format(ret))
+
+s58 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+reads = s58.seq_reads()
+
+for read in reads:
+    record = F.get_empty_record()
+    for i in read:
+        if i in record:
+            record[i] = read[i]
+    ret = F.write_record(record)
+    print("ret: write_record(): {}".format(ret))
+
+F.close()
+
+print("==============================================")
+
+print("append reads no aux")
+
+F = slow5.Open('examples/example_write_append.blow5','w', DEBUG=debug)
+header = F.get_empty_header()
+
+counter = 0
+for i in header:
+    header[i] = "test_{}".format(counter)
+    counter += 1
+
+ret = F.write_header(header)
+print("ret: write_header(): {}".format(ret))
+
+s58 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+reads = s58.seq_reads()
+
+read_count = 0
+for read in reads:
+    record = F.get_empty_record()
+    for i in read:
+        if i in record:
+            record[i] = read[i]
+    ret = F.write_record(record)
+    print("ret: write_record(): {}".format(ret))
+
+s58.close()
+F.close()
+
+F2 = slow5.Open('examples/example_write_append.blow5','a', DEBUG=debug)
+
+print("get_all_headers")
+headers = F2.get_all_headers()
+print(headers)
+
+s58 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+reads = s58.seq_reads()
+
+for read in reads:
+    record = F2.get_empty_record()
+    for i in read:
+        if i in record:
+            if i == "read_id":
+                j = read[i]+"_append"
+                record[i] = j
+            else:
+                record[i] = read[i]
+    ret = F2.write_record(record)
+    print("ret: write_record(): {}".format(ret))
+
+s58.close()
+F.close()
+
+
+print("==============================================")
+print("write reads with aux")
+
+F = slow5.Open('examples/example_write_aux.slow5','w', DEBUG=debug)
+header = F.get_empty_header()
+header2 = F.get_empty_header()
+
+counter = 0
+for i in header:
+    header[i] = "test_{}".format(counter)
+    counter += 1
+
+for i in header2:
+    header2[i] = "test_{}".format(counter)
+    counter += 1
+
+ret = F.write_header(header)
+print("ret: write_header(): {}".format(ret))
+ret = F.write_header(header2, read_group=1)
+print("ret: write_header(): {}".format(ret))
+
+s58 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+reads = s58.seq_reads(aux='all')
+
+for read in reads:
+    record, aux = F.get_empty_record(aux=True)
+    for i in read:
+        if i in record:
+            record[i] = read[i]
+        if i in aux:
+            aux[i] = read[i]
+    ret = F.write_record(record, aux)
+    print("ret: write_record(): {}".format(ret))
+
+s58.close()
+F.close()
+
 print("==============================================")
+print("append reads with aux")
+
+F = slow5.Open('examples/example_write_append_aux.blow5','w', DEBUG=debug)
+header = F.get_empty_header()
+header2 = F.get_empty_header()
+
+counter = 0
+for i in header:
+    header[i] = "test_{}".format(counter)
+    counter += 1
+
+for i in header2:
+    header2[i] = "test_{}".format(counter)
+    counter += 1
+
+ret = F.write_header(header)
+print("ret: write_header(): {}".format(ret))
+ret = F.write_header(header2, read_group=1)
+print("ret: write_header(): {}".format(ret))
+
+s58 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+reads = s58.seq_reads(aux='all')
+
+for read in reads:
+    record, aux = F.get_empty_record(aux=True)
+    for i in read:
+        if i in record:
+            record[i] = read[i]
+        if i in aux:
+            aux[i] = read[i]
+    ret = F.write_record(record, aux)
+    print("ret: write_record(): {}".format(ret))
+
+s58.close()
+F.close()
+
+F2 = slow5.Open('examples/example_write_append_aux.blow5','a', DEBUG=debug)
+
+s58 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+reads = s58.seq_reads(aux='all')
+
+for read in reads:
+    record, aux = F2.get_empty_record(aux=True)
+    for i in read:
+        if i in record:
+            if i == "read_id":
+                j = read[i]+"_append"
+                record[i] = j
+            else:
+                record[i] = read[i]
+        if i in aux:
+            aux[i] = read[i]
+    ret = F2.write_record(record, aux)
+    print("ret: write_record(): {}".format(ret))
+
+s58.close()
+F.close()
+
+
+
+print("==============================================")
+
+print("seq_reads_multi with aux:")
+s53 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+reads = s53.seq_reads_multi(threads=2, batchsize=3, pA=True, aux='all')
+print("type check reads:", type(reads))
+for read in reads:
+    print(read['read_id'])
+    print("read_number", read['read_number'])
+
+print("==============================================")
+
+print("get_reads_multi with aux:")
+
+read_list = ['r0',
+             'r1',
+             'r2',
+             'r3',
+             'r4',
+             'r5',
+             '0a238451-b9ed-446d-a152-badd074006c4',
+             '0d624d4b-671f-40b8-9798-84f2ccc4d7fc']
+
+s53.close()
+
+s53 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+reads = s53.get_read_list_multi(read_list, threads=2, batchsize=3, pA=True, aux='all')
+print("type check reads:", type(reads))
+for read in reads:
+    print(read['read_id'])
+    print("read_number", read['read_number'])
+
+s53.close()
+print("==============================================")
+print("write reads with aux multi")
+
+F = slow5.Open('examples/example_write_aux_multi.slow5','w', DEBUG=debug)
+header = F.get_empty_header()
+header2 = F.get_empty_header()
+
+counter = 0
+for i in header:
+    header[i] = "test_{}".format(counter)
+    counter += 1
+
+for i in header2:
+    header2[i] = "test_{}".format(counter)
+    counter += 1
+
+ret = F.write_header(header)
+print("ret: write_header(): {}".format(ret))
+ret = F.write_header(header2, read_group=1)
+print("ret: write_header(): {}".format(ret))
+
+s58 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+reads = s58.seq_reads(aux='all')
+
+records = {}
+auxs = {}
+for read in reads:
+    record, aux = F.get_empty_record(aux=True)
+    # record = F.get_empty_record()
+    for i in read:
+        if i == "read_id":
+            readID = read[i]
+        if i in record:
+            record[i] = read[i]
+        if i in aux:
+            aux[i] = read[i]
+    records[readID] = record
+    auxs[readID] = aux
+print(records)
+print(auxs)
+ret = F.write_record_batch(records, threads=2, batchsize=3, aux=auxs)
+print("ret: write_record(): {}".format(ret))
+
+s58.close()
+F.close()
+
+print("==============================================")
+# print("seq_reads with big file:")
+# start_time = time.time()
+# s53 = slow5.Open('/home/jamfer/Data/SK/multi_fast5/s5/FAK40634_d1cc054609fe2c5fcdeac358864f9dc81c8bb793_95.blow5','r', DEBUG=debug)
+# reads = s53.seq_reads(pA=True, aux='all')
+# # print("type check reads:", type(reads))
+# for read in reads:
+#     print(read['read_id'])
+#     # print("read_number", read['read_number'])
+# seq_time = round(time.time() - start_time, 4)
+# print("get_read in: {} seconds".format(seq_time))
+#
+# print("==============================================")
+# print("seq_reads_multi with big file:")
+# start_time = time.time()
+# s53 = slow5.Open('/home/jamfer/Data/SK/multi_fast5/s5/FAK40634_d1cc054609fe2c5fcdeac358864f9dc81c8bb793_95.blow5','r', DEBUG=debug)
+# reads = s53.seq_reads_multi(threads=8, batchsize=10, pA=True, aux='all')
+# # print("type check reads:", type(reads))
+# for read in reads:
+#     print(read['read_id'])
+#     # print("read_number", read['read_number'])
+# seq_multi_time = round(time.time() - start_time, 4)
+# print("get_read in: {} seconds".format(seq_multi_time))
+#
+# print("==============================================")
+#
+# print("single seq/write with big file:")
+# start_time = time.time()
+# sR = slow5.Open('/home/jamfer/Data/SK/multi_fast5/s5/FAK40634_d1cc054609fe2c5fcdeac358864f9dc81c8bb793_95.blow5','r', DEBUG=debug)
+# reads = sR.seq_reads(aux='all')
+# sW = slow5.Open('/home/jamfer/Data/SK/multi_fast5/s5/single_read_write.blow5','w', DEBUG=debug)
+#
+# header = F.get_empty_header()
+#
+# counter = 0
+# for i in header:
+#     header[i] = "test_{}".format(counter)
+#     counter += 1
+#
+# ret = sW.write_header(header)
+# print("ret: write_header(): {}".format(ret))
+#
+# for read in reads:
+#     record, aux = sW.get_empty_record(aux=True)
+#     for i in read:
+#         if i in record:
+#             record[i] = read[i]
+#         if i in aux:
+#             aux[i] = read[i]
+#     ret = sW.write_record(record, aux)
+#
+# single_seq_write_time = round(time.time() - start_time, 4)
+# print("single seq/write in: {} seconds".format(single_seq_write_time))
+#
+# print("==============================================")
+# print("multi seq, single write with big file:")
+# start_time = time.time()
+# sR = slow5.Open('/home/jamfer/Data/SK/multi_fast5/s5/FAK40634_d1cc054609fe2c5fcdeac358864f9dc81c8bb793_95.blow5','r', DEBUG=debug)
+# reads = sR.seq_reads_multi(threads=4, batchsize=100, aux='all')
+# sW = slow5.Open('/home/jamfer/Data/SK/multi_fast5/s5/multi_read_single_write.blow5','w', DEBUG=debug)
+# header = F.get_empty_header()
+#
+# counter = 0
+# for i in header:
+#     header[i] = "test_{}".format(counter)
+#     counter += 1
+#
+# ret = sW.write_header(header)
+# print("ret: write_header(): {}".format(ret))
+#
+# for read in reads:
+#     record, aux = sW.get_empty_record(aux=True)
+#     for i in read:
+#         if i in record:
+#             record[i] = read[i]
+#         if i in aux:
+#             aux[i] = read[i]
+#     ret = sW.write_record(record, aux)
+#
+# multi_seq_single_write_time = round(time.time() - start_time, 4)
+# print("multi seq, single write in: {} seconds".format(multi_seq_single_write_time))
+#
+# print("==============================================")
+# print("single seq, multi write with big file:")
+# start_time = time.time()
+# sR = slow5.Open('/home/jamfer/Data/SK/multi_fast5/s5/FAK40634_d1cc054609fe2c5fcdeac358864f9dc81c8bb793_95.blow5','r', DEBUG=debug)
+# reads = sR.seq_reads(aux='all')
+# sW = slow5.Open('/home/jamfer/Data/SK/multi_fast5/s5/single_read_multi_write.blow5','w', DEBUG=debug)
+#
+# header = sW.get_empty_header()
+#
+# counter = 0
+# for i in header:
+#     header[i] = "test_{}".format(counter)
+#     counter += 1
+#
+# ret = sW.write_header(header)
+# print("ret: write_header(): {}".format(ret))
+#
+# records = {}
+# auxs = {}
+# for read in reads:
+#     record, aux = sW.get_empty_record(aux=True)
+#     for i in read:
+#         if i == "read_id":
+#             readID = read[i]
+#         if i in record:
+#             record[i] = read[i]
+#         if i in aux:
+#             aux[i] = read[i]
+#     records[readID] = record
+#     auxs[readID] = aux
+#     if len(records) >= 100:
+#         ret = sW.write_record_batch(records, threads=4, batchsize=100, aux=auxs)
+#         records = {}
+#         auxs = {}
+#
+# single_seq_multi_write_time = round(time.time() - start_time, 4)
+# print("single seq, multi write in: {} seconds".format(single_seq_multi_write_time))
+#
+# print("==============================================")
+# print("multi seq/write with big file:")
+# start_time = time.time()
+# sR = slow5.Open('/home/jamfer/Data/SK/multi_fast5/s5/FAK40634_d1cc054609fe2c5fcdeac358864f9dc81c8bb793_95.blow5','r', DEBUG=debug)
+# reads = sR.seq_reads_multi(threads=4, batchsize=100, aux='all')
+# sW = slow5.Open('/home/jamfer/Data/SK/multi_fast5/s5/multi_read_write.blow5','w', DEBUG=debug)
+#
+#
+# header = sW.get_empty_header()
+#
+# counter = 0
+# for i in header:
+#     header[i] = "test_{}".format(counter)
+#     counter += 1
+#
+# ret = sW.write_header(header)
+# print("ret: write_header(): {}".format(ret))
+#
+# records = {}
+# auxs = {}
+# for read in reads:
+#     record, aux = sW.get_empty_record(aux=True)
+#     for i in read:
+#         if i == "read_id":
+#             readID = read[i]
+#         if i in record:
+#             record[i] = read[i]
+#         if i in aux:
+#             aux[i] = read[i]
+#     records[readID] = record
+#     auxs[readID] = aux
+#     if len(records) >= 100:
+#         ret = sW.write_record_batch(records, threads=4, batchsize=100, aux=auxs)
+#         records = {}
+#         auxs = {}
+# multi_seq_write_time = round(time.time() - start_time, 4)
+# print("multi seq/write in: {} seconds".format(multi_seq_write_time))
+#
+# print("==============================================")
+#
+# print("big file times:")
+# print("single seq/write in: {} seconds".format(single_seq_write_time))
+# print("multi seq, single write in: {} seconds".format(multi_seq_single_write_time))
+# print("single seq, multi write in: {} seconds".format(single_seq_multi_write_time))
+# print("multi seq/write in: {} seconds".format(multi_seq_write_time))
+
 print("done")
diff --git a/python/pydebug.sh b/python/pydebug.sh
index 520cefc3..9da6db7f 100755
--- a/python/pydebug.sh
+++ b/python/pydebug.sh
@@ -8,3 +8,4 @@ CFLAGS="-fsanitize=address -fno-omit-frame-pointer" python3 setup.py build
 cp build/lib.*/*.so  ./
 echo $GCC_ASAN_PRELOAD
 LD_PRELOAD=$GCC_ASAN_PRELOAD  python3 < python/example.py
+# LD_PRELOAD=$GCC_ASAN_PRELOAD  python3 -m unittest -v python/test.py
diff --git a/python/pyslow5.pxd b/python/pyslow5.pxd
index 1d86bd78..9360f0eb 100644
--- a/python/pyslow5.pxd
+++ b/python/pyslow5.pxd
@@ -1,8 +1,8 @@
+#cython: language_level=3
 from libc.stdio cimport *
 from libc.stdint cimport *
 from libc.stdlib cimport *
 
-
 cdef extern from "pyslow5.h":
 
     cdef enum slow5_aux_type:
@@ -31,6 +31,11 @@ cdef extern from "pyslow5.h":
         SLOW5_STRING,
         SLOW5_ENUM_ARRAY
 
+    ctypedef struct slow5_aux_meta_t:
+        pass
+
+    ctypedef struct slow5_hdr_data_t:
+        pass
 
     ctypedef struct slow5_version:
         uint8_t major
@@ -42,7 +47,9 @@ cdef extern from "pyslow5.h":
     ctypedef struct slow5_hdr_t:
         slow5_version version;
         uint32_t num_read_groups;
-        pass
+        slow5_hdr_data_t data;
+        slow5_aux_meta_t *aux_meta;
+
     ctypedef struct slow5_idx_t:
         pass
     ctypedef enum slow5_fmt:
@@ -58,8 +65,8 @@ cdef extern from "pyslow5.h":
         slow5_idx_t *index
         slow5_file_meta_t meta
 
-
     ctypedef struct slow5_rec_t:
+        uint16_t read_id_len
         char* read_id
         uint32_t read_group
         double digitisation
@@ -71,18 +78,21 @@ cdef extern from "pyslow5.h":
         pass
 
 
-
     # Open a slow5 file
-    slow5_file_t *slow5_open(const char *pathname, const char *mode)
+    slow5_file_t *slow5_open(const char *pathname, const char *mode);
     const char **slow5_get_hdr_keys(const slow5_hdr_t *header, uint64_t *len);
     char *slow5_hdr_get(const char *attr, uint32_t read_group, const slow5_hdr_t *header);
-    int slow5_close(slow5_file_t *s5p)
-    int slow5_idx_load(slow5_file_t *s5p)
-    int slow5_get(const char *read_id, slow5_rec_t **read, slow5_file_t *s5p)
-    int slow5_get_next(slow5_rec_t **read, slow5_file_t *s5p)
+    void slow5_idx_unload(slow5_file_t *s5p);
+    int slow5_close(slow5_file_t *s5p);
+    int slow5_idx_load(slow5_file_t *s5p);
+    int slow5_get(const char *read_id, slow5_rec_t **read, slow5_file_t *s5p);
+    int slow5_get_next(slow5_rec_t **read, slow5_file_t *s5p);
     char **slow5_get_aux_names(const slow5_hdr_t *header, uint64_t *len);
     slow5_aux_type *slow5_get_aux_types(const slow5_hdr_t *header, uint64_t *len);
-    void slow5_rec_free(slow5_rec_t *read)
+    void slow5_rec_free(slow5_rec_t *read);
+
+
+
 
     int8_t slow5_aux_get_int8(const slow5_rec_t *read, const char *attr, int *err);
     int16_t slow5_aux_get_int16(const slow5_rec_t *read, const char *attr, int *err);
@@ -95,6 +105,7 @@ cdef extern from "pyslow5.h":
     float slow5_aux_get_float(const slow5_rec_t *read, const char *attr, int *err);
     double slow5_aux_get_double(const slow5_rec_t *read, const char *attr, int *err);
     char slow5_aux_get_char(const slow5_rec_t *read, const char *attr, int *err);
+    uint8_t slow5_aux_get_enum(const slow5_rec_t *read, const char *field, int *err);
     int8_t *slow5_aux_get_int8_array(const slow5_rec_t *read, const char *attr, uint64_t *len, int *err);
     int16_t *slow5_aux_get_int16_array(const slow5_rec_t *read, const char *attr, uint64_t *len, int *err);
     int32_t *slow5_aux_get_int32_array(const slow5_rec_t *read, const char *attr, uint64_t *len, int *err);
@@ -106,3 +117,40 @@ cdef extern from "pyslow5.h":
     float *slow5_aux_get_float_array(const slow5_rec_t *read, const char *attr, uint64_t *len, int *err);
     double *slow5_aux_get_double_array(const slow5_rec_t *read, const char *attr, uint64_t *len, int *err);
     char *slow5_aux_get_string(const slow5_rec_t *read, const char *attr, uint64_t *len, int *err);
+    uint8_t *slow5_aux_get_enum_array(const slow5_rec_t *read, const char *field, uint64_t *len, int *err);
+
+
+    # Write slow5 file
+
+    # from slow5.h
+    int slow5_hdr_add_attr(const char *attr, slow5_hdr_t *header);
+    int slow5_hdr_set(const char *attr, const char *value, uint32_t read_group, slow5_hdr_t *header);
+    int64_t slow5_hdr_add_rg(slow5_hdr_t *header);
+    slow5_rec_t *slow5_rec_init();
+
+    # from slow5_extra.h
+
+    int slow5_aux_meta_add(slow5_aux_meta_t *aux_meta, const char *attr, slow5_aux_type type);
+    int slow5_rec_set_string(slow5_rec_t *read, slow5_aux_meta_t *aux_meta, const char *attr, const char *data);
+    int slow5_rec_set(slow5_rec_t *read, slow5_aux_meta_t *aux_meta, const char *attr, const void *data);
+
+cdef extern from "slow5_write.h":
+
+    # Write slow5 file
+
+    #from slow5_write.h
+    slow5_file_t *slow5_open_write(char *filename, char *mode);
+    slow5_file_t *slow5_open_write_append(char *filename, char *mode);
+    int slow5_close_write(slow5_file_t *sf);
+    int slow5_header_write(slow5_file_t *sf);
+    int slow5_rec_write(slow5_file_t *sf, slow5_rec_t *rec);
+    int slow5_aux_meta_add_wrapper(slow5_hdr_t *header, const char *attr, slow5_aux_type type);
+    int slow5_rec_set_wrapper(slow5_rec_t *read, slow5_hdr_t *header, const char *attr, const void *data);
+    int slow5_rec_set_string_wrapper(slow5_rec_t *read, slow5_hdr_t *header, const char *attr, const char *data);
+
+cdef extern from "slow5threads.h":
+
+    int slow5_get_batch(slow5_rec_t ***read, slow5_file_t *s5p, char **rid, int num_rid, int num_threads);
+    int slow5_get_next_batch(slow5_rec_t ***read, slow5_file_t *s5p, int batch_size, int num_threads);
+    int slow5_write_batch(slow5_rec_t **read, slow5_file_t *s5p, int batch_size, int num_threads);
+    void slow5_free_batch(slow5_rec_t ***read, int num_rec);
diff --git a/python/pyslow5.pyx b/python/pyslow5.pyx
index a5e5fe74..3567cc27 100644
--- a/python/pyslow5.pyx
+++ b/python/pyslow5.pyx
@@ -1,8 +1,11 @@
-
 # distutils: language = c++
 # cython: language_level=3
+# cython: profile=True
 import sys
+import time
 import logging
+import copy
+from itertools import chain
 from libc.stdlib cimport malloc, free
 from libc.string cimport strdup
 cimport pyslow5
@@ -15,14 +18,22 @@ cimport numpy as np
 np.import_array()
 
 #
-# Class Open is for read-only of slow5/blow5 files.
+# Class Open for reading and writing slow5/blow5 files
+# m attribute sets the read/write state and file extension of p sets the type
 #
 
 cdef class Open:
     cdef pyslow5.slow5_file_t *s5
     cdef pyslow5.slow5_rec_t *rec
     cdef pyslow5.slow5_rec_t *read
+    cdef pyslow5.slow5_rec_t *write
+    cdef pyslow5.slow5_rec_t **twrite
+    cdef pyslow5.slow5_rec_t **trec
+    cdef char **rid
     cdef bint index_state
+    cdef bint header_state
+    cdef bint header_add_attr_state
+    cdef bint close_state
     cdef pyslow5.uint64_t head_len
     cdef pyslow5.uint64_t aux_len
     cdef pyslow5.slow5_aux_type *s5_aux_type
@@ -30,8 +41,11 @@ cdef class Open:
     cdef pyslow5.uint64_t aux_get_len
     cdef np.npy_intp shape_get[1]
     cdef np.npy_intp shape_seq[1]
-    cdef const char* p
-    cdef const char* m
+    cdef char* p
+    cdef str path
+    cdef char* m
+    cdef str mode
+    cdef int state
     cdef int V
     cdef object logger
     cdef list aux_names
@@ -48,6 +62,7 @@ cdef class Open:
     cdef pyslow5.float e8
     cdef double e9
     cdef char e10
+    cdef int e11
     cdef pyslow5.int8_t *e12
     cdef pyslow5.int16_t *e13
     cdef pyslow5.int32_t *e14
@@ -59,16 +74,48 @@ cdef class Open:
     cdef float *e20
     cdef double *e21
     cdef char *e22 # meant to be string, not sure about this
+    cdef char *channel_number
+    cdef char *median_before
+    cdef char *read_number
+    cdef char *start_mux
+    cdef char *start_time
+    cdef char *end_reason # need to add end_reason_val
+    cdef char *channel_number_val
+    cdef double median_before_val
+    cdef pyslow5.int32_t read_number_val
+    cdef pyslow5.uint8_t start_mux_val
+    cdef pyslow5.uint64_t start_time_val
+    cdef char **channel_number_val_array
+    cdef double *median_before_val_array
+    cdef pyslow5.int32_t *read_number_val_array
+    cdef pyslow5.uint8_t *start_mux_val_array
+    cdef pyslow5.uint64_t *start_time_val_array
 
+    cdef pyslow5.float total_time_slow5_get_next
+    cdef pyslow5.float total_time_yield_reads
+    cdef pyslow5.float total_single_write_time
+    cdef pyslow5.float total_multi_write_signal_time
+    cdef pyslow5.float total_multi_write_time
 
     def __cinit__(self, pathname, mode, DEBUG=0):
         # Set to default NULL type
         self.s5 = NULL
         self.rec = NULL
         self.read = NULL
+        self.write = NULL
+        self.twrite = NULL
+        self.path = ""
         self.p = ""
+        self.mode = ""
         self.m = ""
+        # state for read/write. -1=null, 0=read, 1=write, 2=append
+        self.state = -1
+        self.trec = NULL
+        self.rid = NULL
         self.index_state = False
+        self.header_state = False
+        self.header_add_attr_state = False
+        self.close_state = False
         self.s5_aux_type = NULL
         self.aux_get_err = 1
         self.aux_get_len = 0
@@ -87,6 +134,7 @@ cdef class Open:
         self.e8 = -1.0
         self.e9 = -1.0
         self.e10 = 0
+        self.e11 = 0
         self.e12 = NULL
         self.e13 = NULL
         self.e14 = NULL
@@ -98,6 +146,30 @@ cdef class Open:
         self.e20 = NULL
         self.e21 = NULL
         self.e22 = NULL
+        self.channel_number = strdup("channel_number")
+        self.median_before = strdup("median_before")
+        self.read_number = strdup("read_number")
+        self.start_mux = strdup("start_mux")
+        self.start_time = strdup("start_time")
+        self.end_reason = strdup("end_reason")
+        channel_number_val = NULL
+        median_before_val = -1.0
+        read_number_val = -1
+        start_mux_val = -1
+        start_time_val = -1
+        channel_number_val_array = NULL
+        median_before_val_array = NULL
+        read_number_val_array = NULL
+        start_mux_val_array = NULL
+        start_time_val_array = NULL
+
+
+        # cdef something end_reason # some enum
+        self.total_time_slow5_get_next = 0.0
+        self.total_time_yield_reads = 0.0
+        self.total_single_write_time = 0.0
+        self.total_multi_write_signal_time = 0.0
+        self.total_multi_write_time = 0.0
 
         # sets up logging level/verbosity
         self.V = DEBUG
@@ -118,24 +190,51 @@ cdef class Open:
                             -5: ["SLOW5_ERR_IO", "other file I/O error"],
                             -6: ["SLOW5_ERR_NOIDX", "index not loaded"],
                             -7: ["SLOW5_ERR_NOTFOUND", "read id not found"],
-                            }
+                            -8: ["SLOW5_ERR_OTH", "other error (big endian, internal error, etc.)"],
+                            -9: ["SLOW5_ERR_UNK", "file format unknown"],
+                            -10: ["SLOW5_ERR_MEM", "memory (re)allocation error"],
+                            -11: ["SLOW5_ERR_NOAUX", "no auxiliary map"],
+                            -12: ["SLOW5_ERR_NOFLD", "field not found"],
+                            -13: ["SLOW5_ERR_PRESS", "(de)compression failure"],
+                            -14: ["SLOW5_ERR_MAGIC", "magic number invalid"],
+                            -15: ["SLOW5_ERR_VERSION", "version incompatible"],
+                            -16: ["SLOW5_ERR_HDRPARSE", "header parsing error"],
+                            -17: ["SLOW5_ERR_TYPE", "error relating to slow5 type"]}
         p = str.encode(pathname)
+        self.path = pathname
         self.p = strdup(p)
         m = str.encode(mode)
+        self.mode = mode
         self.m = strdup(m)
-        # print binary strings of filepath and mode
-        self.logger.debug("FILE: {}, mode: {}".format(self.p.decode(), self.m.decode()))
+        self.logger.debug("FILE: {}, mode: {}".format(self.path, self.mode))
         self.logger.debug("FILE: {}, mode: {}".format(self.p, self.m))
-        # opens file and creates slow5 object
-        self.s5 = pyslow5.slow5_open(self.p, self.m)
+        # Set state based on mode for file opening
+        # state for read/write. -1=null, 0=read, 1=write, 2=append
+        if mode == "r":
+            self.state = 0
+        elif mode == "w":
+            self.state = 1
+        elif mode == "a":
+            self.state = 2
+        else:
+            self.state = -1
+        # opens file and creates slow5 object for reading
+        if self.state == 0:
+            self.s5 = pyslow5.slow5_open(self.p, self.m)
+            self.logger.debug("Number of read_groups: {}".format(self.s5.header.num_read_groups))
+        elif self.state == 1:
+            self.s5 = slow5_open_write(self.p, self.m)
+            if self.s5 is NULL:
+                self.logger.error("File '{}' could not be opened for writing.".format(self.path))
+        elif self.state == 2:
+            self.s5 = slow5_open_write_append(self.p, self.m)
+            if self.s5 is NULL:
+                self.logger.error("File '{}' could not be opened for writing - appending.".format(self.path))
+        else:
+            self.logger.error("File '{}' unknown open method: {}".format(self.path, self.mode))
+        # check object was actually created.
         if self.s5 is NULL:
             raise MemoryError()
-        # load or create and load index
-        self.logger.debug("Number of read_groups: {}".format(self.s5.header.num_read_groups))
-        # self.logger.debug("Creating/loading index...")
-        # ret = slow5_idx_load(self.s5)
-        # if ret != 0:
-        #     self.logger.warning("slow5_idx_load return not 0: {}: {}".format(ret, self.error_codes[ret]))
 
 
     def __init__(self, pathname, mode, DEBUG=0):
@@ -144,12 +243,45 @@ cdef class Open:
 
 
     def __dealloc__(self):
+        if self.p is not NULL:
+            free(self.p)
+        if self.m is not NULL:
+            free(self.m)
         if self.rec is not NULL:
             slow5_rec_free(self.rec)
         if self.read is not NULL:
             slow5_rec_free(self.read)
-        if self.s5 is not NULL:
-            pyslow5.slow5_close(self.s5)
+        if self.write is not NULL:
+            slow5_rec_free(self.write)
+        if self.state in [1, 2]:
+            if not self.close_state:
+                if self.s5 is not NULL:
+                    slow5_close_write(self.s5)
+                    self.close_state = True
+                    self.logger.debug("{} closed".format(self.path))
+            else:
+                self.logger.debug("{} already closed".format(self.path))
+        if self.state == 0:
+            if not self.close_state:
+                if self.s5 is not NULL:
+                    slow5_idx_unload(self.s5)
+                    slow5_close(self.s5)
+                    self.close_state = True
+                    self.logger.debug("{} closed".format(self.path))
+
+        free(self.channel_number)
+        free(self.median_before)
+        free(self.read_number)
+        free(self.start_mux)
+        free(self.start_time)
+        free(self.end_reason)
+
+        self.logger.debug("pathname: {}".format(self.path))
+        self.logger.debug("total_time_slow5_get_next: {} seconds".format(self.total_time_slow5_get_next))
+        self.logger.debug("total_time_yield_reads: {} seconds".format(self.total_time_yield_reads))
+        self.logger.debug("total_single_write_time: {} seconds".format(self.total_single_write_time))
+        self.logger.debug("total_multi_write_signal_time: {} seconds".format(self.total_multi_write_signal_time))
+        self.logger.debug("total_multi_write_time: {} seconds".format(self.total_multi_write_time))
 
 
     def _convert_to_pA(self, d):
@@ -159,6 +291,7 @@ cdef class Open:
         for (int32_t j = 0; j < nsample; j++) {
             rawptr[j] = (rawptr[j] + offset) * raw_unit;
         }
+        TODO: Use memory view for the loop so it's faster
         '''
         digitisation = d['digitisation']
         range = d['range']
@@ -168,9 +301,13 @@ cdef class Open:
         # for i in d['signal']:
         #     j = (i + offset) * raw_unit
         #     new_raw.append(float("{0:.2f}".format(round(j,2))))
-        new_raw = np.array(raw_unit * (d['signal'] + offset), dtype=np.float32)
+        # new_raw = np.array(raw_unit * (d['signal'] + offset), dtype=np.float32)
+        new_raw = (raw_unit * (d['signal'] + offset)).astype(np.float32)
         return new_raw
 
+    # ==========================================================================
+    #      Read SLOW5 file
+    # ==========================================================================
 
     def _get_read(self, read_id, pA, aux):
         '''
@@ -180,8 +317,8 @@ cdef class Open:
         returns dic = dictionary of main fields for read_id, with any aux fields added
         '''
         if not self.index_state:
-            self.logger.debug("FILE: {}, mode: {}".format(self.p.decode(), self.m.decode()))
-            self.logger.debug("FILE: {}, mode: {}".format(self.p, self.m))
+            self.logger.debug("FILE: {}, mode: {}".format(self.path, self.mode))
+            # self.logger.debug("FILE: {}, mode: {}".format(self.path, self.m))
             self.logger.debug("Creating/loading index...")
             ret = slow5_idx_load(self.s5)
             if ret != 0:
@@ -254,8 +391,8 @@ cdef class Open:
         # dic['signal'] = [self.rec.raw_signal[i] for i in range(self.rec.len_raw_signal)]
         # cdef np.npy_intp shape_get[1]
         self.shape_get[0] = <np.npy_intp> self.rec.len_raw_signal
-        signal = np.PyArray_SimpleNewFromData(1, self.shape_get,
-                    np.NPY_INT16, <void *> self.rec.raw_signal)
+        signal = copy.deepcopy(np.PyArray_SimpleNewFromData(1, self.shape_get,
+                    np.NPY_INT16, <void *> self.rec.raw_signal))
         np.PyArray_UpdateFlags(signal, signal.flags.num | np.NPY_OWNDATA)
         dic['signal'] = signal
 
@@ -267,6 +404,143 @@ cdef class Open:
             dic.update(aux_dic)
         return dic
 
+    def _get_batches(self, read_list, size):
+        """
+        batchify readIDs
+        """
+        for i in range(0, len(read_list), size):
+            yield read_list[i:i+size]
+
+    def _get_read_multi(self, read_list, threads, batchsize, pA, aux):
+        '''
+        read_list = list of readIDs
+        pA = Bool for converting signal to picoamps
+        aux = str 'name'/'all' or list of names of auxiliary fields added to return dictionary
+        returns dic = dictionary of main fields for read_id, with any aux fields added
+        threads = how many threads to use to go fast
+        '''
+        if not self.index_state:
+            self.logger.debug("FILE: {}, mode: {}".format(self.path, self.mode))
+            self.logger.debug("Creating/loading index...")
+            ret = slow5_idx_load(self.s5)
+            if ret != 0:
+                self.logger.warning("slow5_idx_load return not 0: {}: {}".format(ret, self.error_codes[ret]))
+            else:
+                self.index_state = True
+
+        self.logger.debug("Setting up batching...")
+        self.logger.debug("read_list: {}".format([i for i in read_list]))
+        num_reads = len(read_list)
+        if num_reads > batchsize:
+            batches = self._get_batches(read_list, size=batchsize)
+        else:
+            batches = self._get_batches(read_list, size=num_reads)
+
+        self.logger.debug("batch for loop start")
+        for batch in chain(batches):
+            self.logger.debug("Batch: {}".format(",".join([i for i in batch])))
+            batch_len = len(batch)
+            self.logger.debug("Starting rid assignment")
+            self.rid = <char **> malloc(sizeof(char*)*batch_len)
+            for i in range(batch_len):
+                self.logger.debug("readID: {}, bin: {}".format(batch[i], batch[i].encode()))
+                self.rid[i] = strdup(batch[i].encode())
+            self.logger.debug("rid assignment complete")
+
+
+            self.logger.debug("slow5_get_batch: num_reads: {}".format(batch_len))
+            ret = slow5_get_batch(&self.trec, self.s5, self.rid, batch_len, threads);
+            self.logger.debug("get_read_multi slow5_get_batch ret: {}".format(ret))
+            if ret < 0:
+                self.logger.error("slow5_get_next error code: {}: {}".format(ret, self.error_codes[ret]))
+                break
+            if ret == 0:
+                self.logger.debug("No more reads: {}".format(ret))
+                break
+
+            for i in range(ret):
+                python_parse_read_start = time.time()
+                self.rec = self.trec[i]
+                dic = {}
+                aux_dic = {}
+
+                # check for aux fields
+                if aux is not None:
+                    if not self.aux_names or not self.aux_types:
+                        self.aux_names = self.get_aux_names()
+                        self.aux_types = self.get_aux_types()
+                    if type(aux) is str:
+                        # special type 'all'
+                        if aux == "all":
+                            aux_dic = self._get_read_aux(self.aux_names, self.aux_types)
+                        else:
+                            found_single_aux = False
+                            for n, t in zip(self.aux_names, self.aux_types):
+                                if n == aux:
+                                    found_single_aux = True
+                                    aux_dic = self._get_read_aux([n], [t])
+                                    break
+                            if not found_single_aux:
+                                self.logger.warning("get_read unknown aux name: {}".format(aux))
+                                aux_dic.update({aux: None})
+                    elif type(aux) is list:
+                        n_list = []
+                        t_list = []
+                        for n, t in zip(self.aux_names, self.aux_types):
+                            if n in aux:
+                                n_list.append(n)
+                                t_list.append(t)
+
+                        aux_dic = self._get_read_aux(n_list, t_list)
+                        # Check for items given that did not exist
+                        n_set = set(n_list)
+                        aux_set = set(aux)
+                        if len(aux_set.difference(n_set)) > 0:
+                            for i in aux_set.difference(n_set):
+                                self.logger.warning("get_read unknown aux name: {}".format(i))
+                                aux_dic.update({i: None})
+
+                    else:
+                        self.logger.debug("get_read aux type unknown, accepts str or list: {}".format(aux))
+
+                # get read data
+                if type(self.rec.read_id) is bytes:
+                    dic['read_id'] = self.rec.read_id.decode()
+                else:
+                    dic['read_id'] = self.rec.read_id
+                dic['read_group'] = self.rec.read_group
+                dic['digitisation'] = self.rec.digitisation
+                dic['offset'] = self.rec.offset
+                dic['range'] = self.rec.range
+                dic['sampling_rate'] = self.rec.sampling_rate
+                dic['len_raw_signal'] = self.rec.len_raw_signal
+                # This could be handled by numpy.fromiter() or similar
+                # Probably MUCH faster
+                # https://stackoverflow.com/questions/7543675/how-to-convert-pointer-to-c-array-to-python-array
+                # https://groups.google.com/g/cython-users/c/KnjF7ViaHUM
+                # dic['signal'] = [self.rec.raw_signal[i] for i in range(self.rec.len_raw_signal)]
+                # cdef np.npy_intp shape_get[1]
+                self.shape_get[0] = <np.npy_intp> self.rec.len_raw_signal
+                signal = copy.deepcopy(np.PyArray_SimpleNewFromData(1, self.shape_get,
+                            np.NPY_INT16, <void *> self.rec.raw_signal))
+                np.PyArray_UpdateFlags(signal, signal.flags.num | np.NPY_OWNDATA)
+                dic['signal'] = signal
+
+                # if pA=True, convert signal to pA
+                if pA:
+                    dic['signal'] = self._convert_to_pA(dic)
+                # if aux data, add to main dic
+                if aux_dic:
+                    dic.update(aux_dic)
+                yield dic
+
+            slow5_free_batch(&self.trec, ret)
+            for i in range(batch_len):
+                free(self.rid[i])
+            free(self.rid)
+        self.rec = NULL
+        self.logger.debug("seq_reads_multi timings:")
+
 
     def _get_read_aux(self, aux_names, aux_types):
         '''
@@ -285,75 +559,82 @@ cdef class Open:
                 if self.aux_get_err == 0:
                     dic[name] = self.e0
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 1:
                 self.e1 = slow5_aux_get_int16(self.rec, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e1
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 2:
                 self.e2 = slow5_aux_get_int32(self.rec, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e2
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 3:
                 self.e3 = slow5_aux_get_int64(self.rec, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e3
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 4:
                 self.e4 = slow5_aux_get_uint8(self.rec, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e4
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 5:
                 self.e5 = slow5_aux_get_uint16(self.rec, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e5
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 6:
                 self.e6 = slow5_aux_get_uint32(self.rec, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e6
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 7:
                 self.e7 = slow5_aux_get_uint64(self.rec, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e7
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 8:
                 self.e8 = slow5_aux_get_float(self.rec, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e8
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 9:
                 self.e9 = slow5_aux_get_double(self.rec, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e9
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 10:
                 self.e10 = slow5_aux_get_char(self.rec, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e10
+                else:
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
+                    dic[name] = None
+            elif atype == 11:
+                self.e11 = slow5_aux_get_enum(self.rec, a_name, &self.aux_get_err)
+                if self.aux_get_err == 0:
+                    dic[name] = self.e11
                 else:
                     self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
                     dic[name] = None
@@ -365,7 +646,7 @@ cdef class Open:
                         l.append(self.e12[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 13:
                 self.e13 = slow5_aux_get_int16_array(self.rec, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -375,7 +656,7 @@ cdef class Open:
                         l.append(self.e13[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 14:
                 self.e14 = slow5_aux_get_int32_array(self.rec, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -385,7 +666,7 @@ cdef class Open:
                         l.append(self.e14[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 15:
                 self.e15 = slow5_aux_get_int64_array(self.rec, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -395,7 +676,7 @@ cdef class Open:
                         l.append(self.e15[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 16:
                 self.e16 = slow5_aux_get_uint8_array(self.rec, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -405,7 +686,7 @@ cdef class Open:
                         l.append(self.e16[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 17:
                 self.e17 = slow5_aux_get_uint16_array(self.rec, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -415,7 +696,7 @@ cdef class Open:
                         l.append(self.e17[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 18:
                 self.e18 = slow5_aux_get_uint32_array(self.rec, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -425,7 +706,7 @@ cdef class Open:
                         l.append(self.e18[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 19:
                 self.e19 = slow5_aux_get_uint64_array(self.rec, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -435,7 +716,7 @@ cdef class Open:
                         l.append(self.e19[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 20:
                 self.e20 = slow5_aux_get_float_array(self.rec, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -445,7 +726,7 @@ cdef class Open:
                         l.append(self.e20[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 21:
                 self.e21 = slow5_aux_get_double_array(self.rec, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -455,7 +736,7 @@ cdef class Open:
                         l.append(self.e21[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 22:
                 self.e22 = slow5_aux_get_string(self.rec, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -467,8 +748,11 @@ cdef class Open:
                         s = "".join(l)
                     dic[name] = s
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
+            elif atype == 22:
+                self.logger.debug("NOT IMPLEMENTED YET: get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                dic[name] = None
             else:
                 self.logger.debug("get_read_aux atype not known, skipping: {}".format(atype))
 
@@ -492,75 +776,82 @@ cdef class Open:
                 if self.aux_get_err == 0:
                     dic[name] = self.e0
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 1:
                 self.e1 = slow5_aux_get_int16(self.read, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e1
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 2:
                 self.e2 = slow5_aux_get_int32(self.read, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e2
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 3:
                 self.e3 = slow5_aux_get_int64(self.read, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e3
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 4:
                 self.e4 = slow5_aux_get_uint8(self.read, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e4
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 5:
                 self.e5 = slow5_aux_get_uint16(self.read, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e5
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 6:
                 self.e6 = slow5_aux_get_uint32(self.read, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e6
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 7:
                 self.e7 = slow5_aux_get_uint64(self.read, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e7
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 8:
                 self.e8 = slow5_aux_get_float(self.read, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e8
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 9:
                 self.e9 = slow5_aux_get_double(self.read, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e9
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 10:
                 self.e10 = slow5_aux_get_char(self.read, a_name, &self.aux_get_err)
                 if self.aux_get_err == 0:
                     dic[name] = self.e10
+                else:
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
+                    dic[name] = None
+            elif atype == 11:
+                self.e11 = slow5_aux_get_enum(self.read, a_name, &self.aux_get_err)
+                if self.aux_get_err == 0:
+                    dic[name] = self.e11
                 else:
                     self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
                     dic[name] = None
@@ -572,7 +863,7 @@ cdef class Open:
                         l.append(self.e12[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 13:
                 self.e13 = slow5_aux_get_int16_array(self.read, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -582,7 +873,7 @@ cdef class Open:
                         l.append(self.e13[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 14:
                 self.e14 = slow5_aux_get_int32_array(self.read, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -592,7 +883,7 @@ cdef class Open:
                         l.append(self.e14[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 15:
                 self.e15 = slow5_aux_get_int64_array(self.read, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -602,7 +893,7 @@ cdef class Open:
                         l.append(self.e15[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 16:
                 self.e16 = slow5_aux_get_uint8_array(self.read, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -612,7 +903,7 @@ cdef class Open:
                         l.append(self.e16[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 17:
                 self.e17 = slow5_aux_get_uint16_array(self.read, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -622,7 +913,7 @@ cdef class Open:
                         l.append(self.e17[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 18:
                 self.e18 = slow5_aux_get_uint32_array(self.read, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -632,7 +923,7 @@ cdef class Open:
                         l.append(self.e18[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 19:
                 self.e19 = slow5_aux_get_uint64_array(self.read, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -642,7 +933,7 @@ cdef class Open:
                         l.append(self.e19[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 20:
                 self.e20 = slow5_aux_get_float_array(self.read, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -652,7 +943,7 @@ cdef class Open:
                         l.append(self.e20[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 21:
                 self.e21 = slow5_aux_get_double_array(self.read, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -662,7 +953,7 @@ cdef class Open:
                         l.append(self.e21[i])
                     dic[name] = l
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
             elif atype == 22:
                 self.e22 = slow5_aux_get_string(self.read, a_name, &self.aux_get_len, &self.aux_get_err)
@@ -674,8 +965,11 @@ cdef class Open:
                         s = "".join(l)
                     dic[name] = s
                 else:
-                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                    self.logger.debug("get_aux_types {} self.aux_get_err is {}: {}".format(atype, self.aux_get_err, self.error_codes[self.aux_get_err]))
                     dic[name] = None
+            elif atype == 22:
+                self.logger.debug("NOT IMPLEMENTED YET: get_aux_types {} self.aux_get_err is {}: {}".format(atype), self.aux_get_err, self.error_codes[self.aux_get_err])
+                dic[name] = None
             else:
                 self.logger.debug("get_read_aux atype not known, skipping: {}".format(atype))
 
@@ -696,7 +990,10 @@ cdef class Open:
         ret = 0
         # While loops check ret of previous read for errors as fail safe
         while ret >= 0:
+            start_slow5_get_next = time.time()
             ret = slow5_get_next(&self.read, self.s5)
+            self.total_time_slow5_get_next = self.total_time_slow5_get_next + (time.time() - start_slow5_get_next)
+
             self.logger.debug("slow5_get_next return: {}".format(ret))
             # check for EOF or other errors
             if ret < 0:
@@ -752,21 +1049,17 @@ cdef class Open:
                 row['read_id'] = self.read.read_id.decode()
             else:
                 row['read_id'] = self.read.read_id
-            # row['read_id'] = self.read.read_id.decode()
             row['read_group'] = self.read.read_group
             row['digitisation'] = self.read.digitisation
             row['offset'] = self.read.offset
             row['range'] = self.read.range
             row['sampling_rate'] = self.read.sampling_rate
             row['len_raw_signal'] = self.read.len_raw_signal
-            # row['signal'] = [self.read.raw_signal[i] for i in range(self.read.len_raw_signal)]
             self.shape_seq[0] = <np.npy_intp> self.read.len_raw_signal
-            signal = np.PyArray_SimpleNewFromData(1, self.shape_seq,
-                        np.NPY_INT16, <void *> self.read.raw_signal)
+            signal = copy.deepcopy(np.PyArray_SimpleNewFromData(1, self.shape_seq,
+                        np.NPY_INT16, <void *> self.read.raw_signal))
             np.PyArray_UpdateFlags(signal, signal.flags.num | np.NPY_OWNDATA)
             row['signal'] = signal
-            # for i in range(self.read.len_raw_signal):
-            #     row['signal'].append(self.read.raw_signal[i])
 
             # if pA=True, convert signal to pA
             if pA:
@@ -774,10 +1067,124 @@ cdef class Open:
             # if aux data update main dic
             if aux_dic:
                 row.update(aux_dic)
-
+            self.total_time_yield_reads = self.total_time_yield_reads + (time.time() - start_slow5_get_next)
             yield row
 
 
+    def seq_reads_multi(self, threads=4, batchsize=4096, pA=False, aux=None):
+        '''
+        returns generator for sequential reading of slow5 file
+        for pA and aux, see _get_read
+        threads: number of threads to use
+        batchsize: Number of reads to process with thread pool in parallel
+        '''
+        aux_dic = {}
+        row = {}
+        ret = 1
+        timedic = {"aux_total_time": 0,
+                   "primary_total_time": 0,
+                   "pA_total_time": 0,
+                   "signal_total_time": 0}
+
+        # While loops check ret of previous read for errors as fail safe
+        while ret > 0:
+            start_slow5_get_next = time.time()
+            ret = slow5_get_next_batch(&self.trec, self.s5, batchsize, threads)
+            self.total_time_slow5_get_next = self.total_time_slow5_get_next + (time.time() - start_slow5_get_next)
+            self.logger.debug("slow5_get_next_multi return: {}".format(ret))
+            # check for EOF or other errors
+            if ret < 0:
+                if ret == -1:
+                    self.logger.debug("slow5_get_next_multi reached end of file (EOF)(-1): {}: {}".format(ret, self.error_codes[ret]))
+                else:
+                    self.logger.error("slow5_get_next_multi error code: {}: {}".format(ret, self.error_codes[ret]))
+
+                break
+            for i in range(ret):
+                python_parse_read_start = time.time()
+                self.read = self.trec[i]
+                aux_dic = {}
+                row = {}
+                # get aux fields
+                aux_time_start = time.time()
+                if aux is not None:
+                    if not self.aux_names or not self.aux_types:
+                        self.aux_names = self.get_aux_names()
+                        self.aux_types = self.get_aux_types()
+                    if type(aux) is str:
+                        if aux == "all":
+                            aux_dic = self._get_seq_read_aux(self.aux_names, self.aux_types)
+                        else:
+                            found_single_aux = False
+                            for n, t in zip(self.aux_names, self.aux_types):
+                                if n == aux:
+                                    found_single_aux = True
+                                    aux_dic = self._get_read_aux([n], [t])
+                                    break
+                            if not found_single_aux:
+                                self.logger.warning("slow5_get_next_multi unknown aux name: {}".format(aux))
+                                aux_dic.update({aux: None})
+                    elif type(aux) is list:
+                        n_list = []
+                        t_list = []
+                        for n, t in zip(self.aux_names, self.aux_types):
+                            if n in aux:
+                                n_list.append(n)
+                                t_list.append(t)
+
+                        aux_dic = self._get_read_aux(n_list, t_list)
+                        # check for items in given list that do not exist
+                        n_set = set(n_list)
+                        aux_set = set(aux)
+                        if len(aux_set.difference(n_set)) > 0:
+                            for i in aux_set.difference(n_set):
+                                self.logger.warning("slow5_get_next_multi unknown aux name: {}".format(i))
+                                aux_dic.update({i: None})
+
+                    else:
+                        self.logger.debug("slow5_get_next_multi aux type unknown, accepts str or list: {}".format(aux))
+                timedic["aux_total_time"] = timedic["aux_total_time"] + (time.time() - aux_time_start)
+                # Get read data
+                primary_start_time = time.time()
+                if type(self.read.read_id) is bytes:
+                    row['read_id'] = self.read.read_id.decode()
+                else:
+                    row['read_id'] = self.read.read_id
+                # row['read_id'] = self.read.read_id.decode()
+                row['read_group'] = self.read.read_group
+                row['digitisation'] = self.read.digitisation
+                row['offset'] = self.read.offset
+                row['range'] = self.read.range
+                row['sampling_rate'] = self.read.sampling_rate
+                row['len_raw_signal'] = self.read.len_raw_signal
+                signal_start_time = time.time()
+                self.shape_seq[0] = <np.npy_intp> self.read.len_raw_signal
+                signal = copy.deepcopy(np.PyArray_SimpleNewFromData(1, self.shape_seq,
+                            np.NPY_INT16, <void *> self.read.raw_signal))
+                np.PyArray_UpdateFlags(signal, signal.flags.num | np.NPY_OWNDATA)
+                row['signal'] = signal
+                timedic["signal_total_time"] = timedic["signal_total_time"] + (time.time() - signal_start_time)
+                timedic["primary_total_time"] = timedic["primary_total_time"] + (time.time() - primary_start_time)
+                # if pA=True, convert signal to pA
+                if pA:
+                    pA_start_time = time.time()
+                    row['signal'] = self._convert_to_pA(row)
+                    timedic["pA_total_time"] = timedic["pA_total_time"] + (time.time() - pA_start_time)
+                # if aux data update main dic
+                if aux_dic:
+                    row.update(aux_dic)
+                self.total_time_yield_reads = self.total_time_yield_reads + (time.time() - python_parse_read_start)
+                yield row
+            slow5_free_batch(&self.trec, ret)
+            if ret < batchsize:
+                self.logger.debug("slow5_get_next_multi has no more batches - batchsize:{} ret:{}".format(batchsize, ret))
+                break
+        self.read = NULL
+        self.logger.debug("seq_reads_multi timings:")
+        for i in timedic:
+            self.logger.debug("{}: {}".format(i, timedic[i]))
+
+
     def get_read_list(self, read_list, pA=False, aux=None):
         '''
         returns generator for random access of slow5 file
@@ -788,12 +1195,22 @@ cdef class Open:
             yield self._get_read(r, pA, aux)
 
 
+    def get_read_list_multi(self, read_list, threads=4, batchsize=100, pA=False, aux=None):
+        '''
+        returns generator for random access of slow5 file
+        read_list = list of readIDs, if readID not in file, None type returned (need EOF to work)
+        for pA and aux see _get_read
+        threads = number of threads to use to do batched random access
+        '''
+        for r in self._get_read_multi(read_list, threads, batchsize, pA, aux):
+            yield r
+
+
     def get_header_names(self):
         '''
         get all header names and return list
         '''
         headers = []
-        # ret = slow5_header_names(self.s5.header)
         ret = slow5_get_hdr_keys(self.s5.header, &self.head_len)
 
         self.logger.debug("slow5_get_hdr_keys head_len: {}".format(self.head_len))
@@ -802,6 +1219,7 @@ cdef class Open:
             return headers
 
         headers = [ret[i].decode() for i in range(self.head_len)]
+        free(ret)
         return headers
 
 
@@ -862,3 +1280,696 @@ cdef class Open:
 
         aux_types = [self.s5_aux_type[i] for i in range(self.aux_len)]
         return aux_types
+
+    # ==========================================================================
+    #      Write SLOW5 file
+    # ==========================================================================
+
+    def get_empty_header(self):
+        '''
+        returns empty header dic for user to populate
+        Any values not populated will be skipped
+        '''
+        header = {"asic_id": None,
+                  "asic_id_eeprom": None,
+                  "asic_temp": None,
+                  "asic_version": None,
+                  "auto_update": None,
+                  "auto_update_source": None,
+                  "barcoding_enabled": None,
+                  "bream_is_standard": None,
+                  "configuration_version": None,
+                  "device_id": None,
+                  "device_type": None,
+                  "distribution_status": None,
+                  "distribution_version": None,
+                  "exp_script_name": None,
+                  "exp_script_purpose": None,
+                  "exp_start_time": None,
+                  "experiment_duration_set": None,
+                  "flow_cell_id": None,
+                  "flow_cell_product_code": None,
+                  "guppy_version": None,
+                  "heatsink_temp": None,
+                  "hostname": None,
+                  "installation_type": None,
+                  "local_basecalling": None,
+                  "operating_system": None,
+                  "package": None,
+                  "protocol_group_id": None,
+                  "protocol_run_id": None,
+                  "protocol_start_time": None,
+                  "protocols_version": None,
+                  "run_id": None,
+                  "sample_frequency": None,
+                  "sample_id": None,
+                  "sequencing_kit": None,
+                  "usb_config": None,
+                  "version": None,
+                  "hublett_board_id": None,
+                  "satellite_firmware_version": None}
+
+        return header
+
+    def get_empty_record(self, aux=False):
+        '''
+        Return a dictionary of empty record and types
+        if axu=True, a second dictionary will be returned with optional aux record
+        '''
+        record = {"read_id": None,
+                  "read_group": 0,
+                  "digitisation": None,
+                  "offset": None,
+                  "range": None,
+                  "sampling_rate": None,
+                  "len_raw_signal": None,
+                  "signal": None}
+
+        aux_rec = {"channel_number": None,
+               "median_before": None,
+               "read_number": None,
+               "start_mux": None,
+               "start_time": None}
+
+        if aux:
+            return record, aux_rec
+        return record
+
+
+    def _header_type_validation(self, header):
+        '''
+        internal function to validate header types before pushing to C side
+        '''
+        for h in header:
+            if header[h] is not None:
+                t = type(header[h])
+                if t is not type("string"):
+                    self.logger.warning("_header_type_validation {}: {} is not a string type, attempting to convert".format(h, header[h]))
+                    #try and convert
+                    try:
+                        s = str(header[h])
+                        header[h] = s
+                        self.logger.warning("_header_type_validation {}: {} conversion successful".format(h, s))
+                    except:
+                        self.logger.error("_header_type_validation {}: {} could not convert value to string".format(h, header[h]))
+                        raise
+        return header
+
+
+    def _aux_header_type_validation(self, user_aux_types):
+        '''
+        internal function to validate and convert aux types before pushing to C side
+        '''
+        slow5_aux_types = {}
+
+        py_aux_types = {"channel_number": type("string"),
+                     "median_before": type(1.0),
+                     "read_number": type(10),
+                     "start_mux": type(1),
+                     "start_time": type(100),
+                     "end_reason": None}
+
+        C_aux_types = {"channel_number": SLOW5_STRING,
+                     "median_before":SLOW5_DOUBLE,
+                     "read_number": SLOW5_INT32_T,
+                     "start_mux": SLOW5_UINT8_T,
+                     "start_time": SLOW5_UINT64_T,
+                     "end_reason": None}
+
+        for a in user_aux_types:
+            if user_aux_types[a] is None:
+                continue
+            if a not in py_aux_types:
+                self.logger.error("_aux_type_validation {}: {} user aux field not in pyslow5 aux_types".format(a, user_aux_types[a]))
+                return None
+            if user_aux_types[a] == py_aux_types[a]:
+                slow5_aux_types[a] = C_aux_types[a]
+            else:
+                self.logger.error("_aux_type_validation {}: {} user aux field type mismatch with pyslow5 aux_types".format(a, user_aux_types[a]))
+                return None
+        return slow5_aux_types
+
+
+    def _record_type_validation(self, user_record, aux=None):
+        '''
+        internal function to validate and convert aux types before pushing to C side
+        returns None, None on error
+        '''
+
+        py_record_types = {"read_id": type("string"),
+                           "read_group": type(1),
+                           "digitisation": type(1.0),
+                           "offset": type(1.0),
+                           "range": type(1.0),
+                           "sampling_rate": type(1.0),
+                           "len_raw_signal": type(10),
+                           "signal": type(np.array([1, 2, 3], np.int16))}
+
+        py_aux_types = {"channel_number": type("string"),
+                        "median_before": type(1.0),
+                        "read_number": type(10),
+                        "start_mux": type(1),
+                        "start_time": type(100),
+                        "end_reason": None}
+
+        new_aux = {}
+
+        for a in user_record:
+            if user_record[a] is None:
+                continue
+            if a not in py_record_types:
+                self.logger.error("_record_type_validation {}: {} user primary field not in pyslow5 record_types".format(a, user_record[a]))
+                return None, None
+
+            if type(user_record[a]) != py_record_types[a]:
+                self.logger.error("_record_type_validation {}: {} user primary field type mismatch with pyslow5 record_types".format(a, user_record[a]))
+                return None, None
+
+        # check aux if given
+        if aux is not None:
+            self.logger.debug("_record_type_validation: doing aux stuff...")
+            for a in aux:
+                if a not in py_aux_types:
+                    self.logger.error("_record_type_validation {}: {} user aux field not in pyslow5 aux_types".format(a, aux[a]))
+                    return None, None
+                if aux[a] is None:
+                    continue
+                if type(aux[a]) != py_aux_types[a]:
+                    self.logger.error("_record_type_validation {}: {} user aux field type mismatch with pyslow5 aux_types".format(a, aux[a]))
+                    return None, None
+                else:
+                    self.logger.debug("_record_type_validation: aux passed tests...")
+                    if a == "channel_number":
+                        self.channel_number_val=strdup(aux[a].encode())
+                        new_aux[a] = aux[a]
+                    elif a == "median_before":
+                        self.median_before_val = <double>aux[a]
+                        new_aux[a] = aux[a]
+                    elif a == "read_number":
+                        self.read_number_val = <int32_t>aux[a]
+                        new_aux[a] = aux[a]
+                    elif a == "start_mux":
+                        self.start_mux_val = <uint8_t>aux[a]
+                        new_aux[a] = aux[a]
+                    elif a == "start_time":
+                        self.start_time_val = <uint64_t>aux[a]
+                        new_aux[a] = aux[a]
+                    elif a == "end_reason":
+                        continue
+            self.logger.debug("_record_type_validation: doing aux stuff...")
+
+        return user_record, new_aux
+
+
+    def _multi_record_type_validation(self, user_record, aux=None):
+        '''
+        internal function to validate and convert aux types before pushing to C side
+        returns None, None on error
+        '''
+
+        py_record_types = {"read_id": type("string"),
+                           "read_group": type(1),
+                           "digitisation": type(1.0),
+                           "offset": type(1.0),
+                           "range": type(1.0),
+                           "sampling_rate": type(1.0),
+                           "len_raw_signal": type(10),
+                           "signal": type(np.array([1, 2, 3], np.int16))}
+
+        py_aux_types = {"channel_number": type("string"),
+                        "median_before": type(1.0),
+                        "read_number": type(10),
+                        "start_mux": type(1),
+                        "start_time": type(100),
+                        "end_reason": None}
+
+        new_aux = {}
+
+        for a in user_record:
+            if user_record[a] is None:
+                continue
+            if a not in py_record_types:
+                self.logger.error("_record_type_validation {}: {} user primary field not in pyslow5 record_types".format(a, user_record[a]))
+                return None, None
+
+            if type(user_record[a]) != py_record_types[a]:
+                self.logger.error("_record_type_validation {}: {} user primary field type mismatch with pyslow5 record_types".format(a, user_record[a]))
+                return None, None
+
+        # check aux if given
+        if aux is not None:
+            self.logger.debug("_record_type_validation: doing aux stuff...")
+            for a in aux:
+                if a not in py_aux_types:
+                    self.logger.error("_record_type_validation {}: {} user aux field not in pyslow5 aux_types".format(a, aux[a]))
+                    return None, None
+                if aux[a] is None:
+                    continue
+                if type(aux[a]) != py_aux_types[a]:
+                    self.logger.error("_record_type_validation {}: {} user aux field type mismatch with pyslow5 aux_types".format(a, aux[a]))
+                    return None, None
+                else:
+                    self.logger.debug("_record_type_validation: aux passed tests...")
+                    if a == "channel_number":
+                        new_aux[a] = aux[a]
+                    elif a == "median_before":
+                        new_aux[a] = aux[a]
+                    elif a == "read_number":
+                        new_aux[a] = aux[a]
+                    elif a == "start_mux":
+                        new_aux[a] = aux[a]
+                    elif a == "start_time":
+                        new_aux[a] = aux[a]
+                    elif a == "end_reason":
+                        continue
+
+            self.logger.debug("_record_type_validation: aux stuff done")
+
+
+        return user_record, new_aux
+
+
+    def write_header(self, header, read_group=0):
+        '''
+        write slow5 header to file.
+        takes header dic for attributes, then write once.
+        Currently limited to ONLY 1 read_group
+        Use slow5tools merge after writing as a temporary solution
+        '''
+        checked_header = self._header_type_validation(header)
+
+        errors = False
+        if not self.header_add_attr_state:
+            if read_group > 0:
+                self.logger.error("write_header: You must set read_group=0 first")
+                self.logger.error("write_header: headers failed to initialise.")
+                return -1
+            for h in checked_header:
+                ret = slow5_hdr_add_attr(h.encode(), self.s5.header)
+                if ret < 0:
+                    self.logger.error("write_header: slow5_hdr_add_attr {}: {} could not initialise to C s5.header struct".format(h, checked_header[h]))
+                    errors = True
+                else:
+                    self.header_add_attr_state = True
+
+        if read_group > 0 and self.header_add_attr_state:
+            ret = slow5_hdr_add_rg(self.s5.header)
+            if ret < 0:
+                self.logger.error("write_header: slow5_hdr_add_rg failed for read_group: {}".format(read_group))
+                errors = True
+
+        if errors:
+            self.logger.error("write_header: headers failed to initialise.")
+            return -1
+
+        for h in checked_header:
+            ret = slow5_hdr_set(h.encode(), checked_header[h].encode(), read_group, self.s5.header)
+            if ret < 0:
+                self.logger.error("write_header: slow5_hdr_set {}: {} could not set to C s5.header struct".format(h, checked_header[h]))
+                errors = True
+
+        if not errors:
+            return 0
+        else:
+            self.logger.error("write_header: errors encountered setting up header, aborting write")
+            # sys.exit(1)
+            return -1
+
+    def write_record(self, record, aux=None):
+        '''
+        write a single record
+        check self.header_state, if False, header needs to be written. Use aux types to do first check.
+        if the validation works, then write the header just before the first record is written.
+        This should make the user experience a lot less complicated and have less steps
+        '''
+
+        aux_types = {"channel_number": type("string"),
+                     "median_before": type(1.0),
+                     "read_number": type(10),
+                     "start_mux": type(1),
+                     "start_time": type(100),
+                     "end_reason": None}
+
+        self.logger.debug("write_record: _record_type_validation running")
+        checked_record, checked_aux = self._record_type_validation(record, aux)
+        self.logger.debug("write_record: _record_type_validation done")
+
+        # check if in append state
+        # if so, we can skip header setup
+        if self.state == 2:
+            self.header_state = True
+
+        # if all checks are good, test self.header_state for 1 time write
+        if not self.header_state:
+            self.logger.debug("write_record: checking header stuff...")
+            error = False
+            if aux is not None:
+                if checked_aux is not None:
+                    slow5_aux_types = self._aux_header_type_validation(aux_types)
+                    for a in slow5_aux_types:
+                        if slow5_aux_types[a] is None:
+                            continue
+                        ret = slow5_aux_meta_add_wrapper(self.s5.header, a.encode(), slow5_aux_types[a])
+                        if ret < 0:
+                            self.logger.error("write_record: slow5_aux_meta_add_wrapper {}: {} could not set to C s5.header.aux_meta struct".format(a, checked_aux[a]))
+                            error = True
+                else:
+                    error = True
+
+            if error:
+                self.logger.error("write_record: aux_meta fields failed to initialise")
+                return -1
+            # write the header
+            self.logger.debug("write_record: writting header...")
+            ret = slow5_header_write(self.s5)
+            if ret < 0:
+                self.logger.error("write_record: slow5_header_write could not write header")
+                return -1
+            # set state true so only done once
+            self.header_state = True
+            self.logger.debug("write_record: header written")
+
+        # Add values to read struct
+        self.logger.debug("write_record: slow5_rec_init()")
+        self.write = slow5_rec_init()
+        if self.write == NULL:
+            self.logger.error("write_record: failed to allocate space for slow5 record (self.write)")
+            return -1
+
+        self.logger.debug("write_record: self.write assignments...")
+        checked_record["read_id"] = checked_record["read_id"].encode()
+        self.write.read_id = strdup(checked_record["read_id"])
+        self.write.read_id_len = len(checked_record["read_id"])
+        self.write.read_group = checked_record["read_group"]
+        self.write.digitisation = checked_record["digitisation"]
+        self.write.offset = checked_record["offset"]
+        self.write.range = checked_record["range"]
+        self.write.sampling_rate = checked_record["sampling_rate"]
+        self.write.len_raw_signal = checked_record["len_raw_signal"]
+        self.logger.debug("write_record: self.write.raw_signal malloc...")
+        self.write.raw_signal = <int16_t *> malloc(sizeof(int16_t)*checked_record["len_raw_signal"])
+        self.logger.debug("write_record: self.write.raw_signal malloc done")
+        self.logger.debug("write_record: self.write assignments done")
+
+        self.logger.debug("write_record: self.write processing raw_signal")
+        start_write_copy_signal = time.time()
+        # grabs buffer of numby array so the for loop operats in C not python = super fast
+        memview = memoryview(checked_record["signal"])
+        for i in range(checked_record["len_raw_signal"]):
+            self.write.raw_signal[i] = memview[i]
+        # for i in range(checked_record["len_raw_signal"]):
+        #     self.write.raw_signal[i] = checked_record["signal"][i]
+        end_write_copy_signal = (time.time() - start_write_copy_signal)
+        self.total_single_write_time = self.total_single_write_time + end_write_copy_signal
+        self.logger.debug("write_record: self.write raw_signal done")
+
+        if aux:
+            self.logger.debug("write_record: aux stuff...")
+            if checked_aux is None:
+                self.logger.error("write_record: checked_aux is None".format(a, checked_aux[a]))
+                return -1
+
+            for a in checked_aux:
+                self.logger.debug("write_record: checked_aux: {}: {}".format(a, checked_aux[a]))
+                if checked_aux[a] is None:
+                    continue
+                if a == "channel_number":
+                    self.logger.debug("write_record: slow5_rec_set_string_wrapper running...")
+                    self.logger.debug("write_record: slow5_rec_set_string_wrapper type: {}".format(type(checked_aux[a])))
+                    ret = slow5_rec_set_string_wrapper(self.write, self.s5.header, self.channel_number, <const char *>self.channel_number_val)
+                    self.logger.debug("write_record: slow5_rec_set_string_wrapper running done: ret = {}".format(ret))
+                    if ret < 0:
+                        self.logger.error("write_record: slow5_rec_set_string_wrapper could not write aux value {}: {}".format(a, checked_aux[a]))
+                        #### We should free here
+                        return -1
+                elif a == "median_before":
+                    ret = slow5_rec_set_wrapper(self.write, self.s5.header, self.median_before, <const void *>&self.median_before_val)
+                elif a == "read_number":
+                    ret = slow5_rec_set_wrapper(self.write, self.s5.header, self.read_number, <const void *>&self.read_number_val)
+                elif a == "start_mux":
+                    ret = slow5_rec_set_wrapper(self.write, self.s5.header, self.start_mux, <const void *>&self.start_mux_val)
+                elif a == "start_time":
+                    ret = slow5_rec_set_wrapper(self.write, self.s5.header, self.start_time, <const void *>&self.start_time_val)
+                elif a == "end_reason":
+                    # not implemented yet becuase of variability in ONT versioning
+                    ret = 0
+                if ret < 0:
+                    self.logger.error("write_record: slow5_rec_set_wrapper could not write aux value {}: {}".format(a, checked_aux[a]))
+                    return -1
+
+            self.logger.debug("write_record: aux stuff done")
+
+
+        self.logger.debug("write_record: slow5_rec_write()")
+        # write the record
+        ret = slow5_rec_write(self.s5, self.write)
+        self.logger.debug("write_record: slow5_rec_write() ret: {}".format(ret))
+
+        if aux is not None:
+            free(self.channel_number_val)
+            self.channel_number_val = NULL
+            self.median_before_val = -1.0
+            self.read_number_val = -1
+            self.start_mux_val = -1
+            self.start_time_val = -1
+
+
+        # free memory
+        self.logger.debug("write_record: slow5_rec_free()")
+        slow5_rec_free(self.write)
+        self.write = NULL
+
+        self.logger.debug("write_record: function complete, returning 0")
+        return 0
+
+
+    def write_record_batch(self, records, threads=4, batchsize=4096, aux=None):
+        '''
+        write a batch of records
+        same as write_record, but in batches
+        records = dic of read rics, where the key for each is the readID
+        if aux, aux is also a dic of dics, whhere the key for each is teh readID
+        records = {readID_0: {read_dic}, readID_1: {read_dic}}
+        '''
+        start_multi_write = time.time()
+        aux_types = {"channel_number": type("string"),
+                     "median_before": type(1.0),
+                     "read_number": type(10),
+                     "start_mux": type(1),
+                     "start_time": type(100),
+                     "end_reason": None}
+        # check an empty dic wasn't given
+        if aux is not None:
+            if len(aux) == 0:
+                aux = None
+        self.logger.debug("write_record_batch: Setting up batching...")
+        num_reads = len(records)
+        read_list = list(records.keys())
+        if num_reads > batchsize:
+            batches = self._get_batches(read_list, size=batchsize)
+        else:
+            batches = self._get_batches(read_list, size=num_reads)
+
+
+        self.logger.debug("write_record_batch: batch for loop start")
+        for batch in chain(batches):
+            batch_len = len(batch)
+            self.twrite = <slow5_rec_t **> malloc(sizeof(slow5_rec_t*)*batch_len)
+            self.logger.debug("write_record_batch: _record_type_validation running")
+            checked_records = {}
+            checked_auxs = {}
+            self.channel_number_val_array = <char **> malloc(sizeof(char*)*batch_len)
+            self.median_before_val_array = <double *> malloc(sizeof(double)*batch_len)
+            self.read_number_val_array = <int32_t *> malloc(sizeof(int32_t)*batch_len)
+            self.start_mux_val_array = <uint8_t *> malloc(sizeof(uint8_t)*batch_len)
+            self.start_time_val_array = <uint64_t *> malloc(sizeof(uint64_t)*batch_len)
+            for i, idx in enumerate(batch):
+                if aux is not None:
+                    checked_record, checked_aux = self._multi_record_type_validation(records[idx], aux[idx])
+                    checked_records[idx] = checked_record
+                    for a in checked_aux:
+                        if a == "channel_number":
+                            self.channel_number_val_array[i] = strdup(checked_aux[a].encode())
+                        elif a == "median_before":
+                            self.median_before_val_array[i] = <double>checked_aux[a]
+                        elif a == "read_number":
+                            self.read_number_val_array[i] = <int32_t>checked_aux[a]
+                        elif a == "start_mux":
+                            self.start_mux_val_array[i] = <uint8_t>checked_aux[a]
+                        elif a == "start_time":
+                            self.start_time_val_array[i] = <uint64_t>checked_aux[a]
+                    checked_auxs[idx] = checked_aux
+                else:
+                    checked_record, checked_aux = self._record_type_validation(records[idx], aux)
+                    checked_records[idx] = checked_record
+            self.logger.debug("write_record_batch: _record_type_validation done")
+            if len(checked_aux) == 0:
+                checked_aux = None
+            for idx in range(batch_len):
+
+                # check if in append state
+                # if so, we can skip header setup
+                if self.state == 2:
+                    self.header_state = True
+
+                # if all checks are good, test self.header_state for 1 time write
+                if not self.header_state:
+                    self.logger.debug("write_record_batch: checking header stuff...")
+                    error = False
+                    if aux is not None:
+                        if checked_aux is not None:
+                            slow5_aux_types = self._aux_header_type_validation(aux_types)
+                            for a in slow5_aux_types:
+                                if slow5_aux_types[a] is None:
+                                    continue
+                                ret = slow5_aux_meta_add_wrapper(self.s5.header, a.encode(), slow5_aux_types[a])
+                                if ret < 0:
+                                    self.logger.error("write_record_batch: slow5_aux_meta_add_wrapper {}: {} could not set to C s5.header.aux_meta struct".format(a, checked_aux[a]))
+                                    error = True
+                        else:
+                            error = True
+
+                    if error:
+                        self.logger.error("write_record_batch: aux_meta fields failed to initialise")
+                        return -1
+                    # write the header
+                    self.logger.debug("write_record_batch: writting header...")
+                    ret = slow5_header_write(self.s5)
+                    if ret < 0:
+                        self.logger.error("write_record_batch: slow5_header_write could not write header")
+                        return -1
+                    # set state true so only done once
+                    self.header_state = True
+                    self.logger.debug("write_record_batch: header written")
+
+                # Add values to read struct
+                self.logger.debug("write_record_batch: slow5_rec_init()")
+
+                self.twrite[idx] = slow5_rec_init()
+                if self.twrite[idx] == NULL:
+                    self.logger.error("write_record_batch: failed to allocate space for slow5 record (self.twrite[idx])")
+                    return -1
+
+                self.logger.debug("write_record_batch: self.write assignments...")
+                checked_records[batch[idx]]["read_id"] = checked_records[batch[idx]]["read_id"].encode()
+                self.twrite[idx].read_id = strdup(checked_records[batch[idx]]["read_id"])
+                self.twrite[idx].read_id_len = len(checked_records[batch[idx]]["read_id"])
+                self.twrite[idx].read_group = checked_records[batch[idx]]["read_group"]
+                self.twrite[idx].digitisation = checked_records[batch[idx]]["digitisation"]
+                self.twrite[idx].offset = checked_records[batch[idx]]["offset"]
+                self.twrite[idx].range = checked_records[batch[idx]]["range"]
+                self.twrite[idx].sampling_rate = checked_records[batch[idx]]["sampling_rate"]
+                self.twrite[idx].len_raw_signal = checked_records[batch[idx]]["len_raw_signal"]
+                self.logger.debug("write_record_batch: self.write.raw_signal malloc...")
+                self.twrite[idx].raw_signal = <int16_t *> malloc(sizeof(int16_t)*checked_records[batch[idx]]["len_raw_signal"])
+                self.logger.debug("write_record_batch: self.write.raw_signal malloc done")
+                self.logger.debug("write_record_batch: self.write assignments done")
+
+                self.logger.debug("write_record_batch: self.write processing raw_signal")
+                start_write_copy_signal = time.time()
+                # grabs buffer of numby array so the for loop operats in C not python = super fast
+                memview = memoryview(checked_records[batch[idx]]["signal"])
+                for i in range(checked_records[batch[idx]]["len_raw_signal"]):
+                    self.twrite[idx].raw_signal[i] = memview[i]
+                # for i in range(checked_records[batch[idx]]["len_raw_signal"]):
+                #     self.twrite[idx].raw_signal[i] = checked_records[batch[idx]]["signal"][i]
+                end_write_copy_signal = (time.time() - start_write_copy_signal)
+                self.total_multi_write_signal_time = self.total_multi_write_signal_time + end_write_copy_signal
+
+                self.logger.debug("write_record_batch: self.write raw_signal done")
+
+                if aux is not None:
+                    self.logger.debug("write_record_batch: aux stuff...")
+                    if checked_auxs[batch[idx]] is None:
+                        self.logger.error("write_record_batch: checked_aux is None".format(a, checked_auxs[batch[idx]][a]))
+                        return -1
+
+                    for a in checked_auxs[batch[idx]]:
+                        self.logger.debug("write_record_batch: checked_aux: {}: {}".format(a, checked_auxs[batch[idx]][a]))
+                        if checked_auxs[batch[idx]][a] is None:
+                            continue
+                        if a == "channel_number":
+                            self.logger.debug("write_record_batch: slow5_rec_set_string_wrapper running...")
+                            self.logger.debug("write_record_batch: slow5_rec_set_string_wrapper type: {}".format(type(checked_auxs[batch[idx]][a])))
+                            ret = slow5_rec_set_string_wrapper(self.twrite[idx], self.s5.header, self.channel_number, <const char *>self.channel_number_val_array[idx])
+                            self.logger.debug("write_record_batch: slow5_rec_set_string_wrapper running done: ret = {}".format(ret))
+                            if ret < 0:
+                                self.logger.error("write_record_batch: slow5_rec_set_string_wrapper could not write aux value {}: {}".format(a, checked_auxs[batch[idx]][a]))
+                                #### We should free here
+                                return -1
+                        elif a == "median_before":
+                            ret = slow5_rec_set_wrapper(self.twrite[idx], self.s5.header, self.median_before, <const void *>&self.median_before_val_array[idx])
+                        elif a == "read_number":
+                            ret = slow5_rec_set_wrapper(self.twrite[idx], self.s5.header, self.read_number, <const void *>&self.read_number_val_array[idx])
+                        elif a == "start_mux":
+                            ret = slow5_rec_set_wrapper(self.twrite[idx], self.s5.header, self.start_mux, <const void *>&self.start_mux_val_array[idx])
+                        elif a == "start_time":
+                            ret = slow5_rec_set_wrapper(self.twrite[idx], self.s5.header, self.start_time, <const void *>&self.start_time_val_array[idx])
+                        elif a == "end_reason":
+                            # not implemented yet becuase of variability in ONT versioning
+                            ret = 0
+                        if ret < 0:
+                            self.logger.error("write_record_batch: slow5_rec_set_wrapper could not write aux value {}: {}".format(a, checked_aux[a]))
+                            return -1
+
+                    self.logger.debug("write_record_batch: aux stuff done")
+
+            self.logger.debug("write_record_batch: slow5_write_batch()")
+
+            # write the record
+            if batch_len <= 0:
+                self.logger.debug("write_record_batch: batch_len 0 or less")
+                break
+
+            ret = slow5_write_batch(self.twrite, self.s5, batch_len, threads)
+            if ret < batch_len:
+                self.logger.error("write_record_batch: write failed")
+                return -1
+
+            self.logger.debug("write_record_batch: free()")
+            for i in range(batch_len):
+                slow5_rec_free(self.twrite[i])
+            free(self.twrite)
+
+
+            self.logger.debug("write_record_batch: free() aux")
+            if aux is not None:
+                for i in range(batch_len):
+                    free(self.channel_number_val_array[i])
+
+                free(self.channel_number_val_array)
+                free(self.median_before_val_array)
+                free(self.read_number_val_array)
+                free(self.start_mux_val_array)
+                free(self.start_time_val_array)
+
+        end_multi_write = time.time() - start_multi_write
+        self.total_multi_write_time = self.total_multi_write_time + end_multi_write
+        # free memory
+        # self.twrite = NULL
+        self.logger.debug("write_record_batch: function complete, returning 0")
+        return 0
+
+    def close(self):
+        '''
+        close file so EOF is written
+        '''
+        if self.state in [1,2]:
+            if not self.close_state:
+                if self.s5 is not NULL:
+                    slow5_close_write(self.s5)
+                    self.close_state = True
+                    self.logger.debug("{} closed".format(self.path))
+            else:
+                self.logger.warning("{} already closed".format(self.path))
+        elif self.state == 0:
+            if not self.close_state:
+                if self.s5 is not NULL:
+                    slow5_close(self.s5)
+                    self.close_state = True
+                    self.logger.debug("{} closed".format(self.path))
+            else:
+                self.logger.warning("{} already closed".format(self.path))
+        else:
+            self.logger.error("{} not open for writing, remake class object to refresh".format(self.path))
+        return
diff --git a/python/slow5_write.c b/python/slow5_write.c
new file mode 100644
index 00000000..2503f261
--- /dev/null
+++ b/python/slow5_write.c
@@ -0,0 +1,397 @@
+#include <stdio.h>
+#include <slow5/slow5.h>
+#include "../src/slow5_extra.h"
+
+extern enum slow5_log_level_opt  slow5_log_level;
+extern enum slow5_exit_condition_opt  slow5_exit_condition;
+
+slow5_file_t *slow5_open_write(const char *filename, const char *mode){
+
+    FILE *fp = fopen(filename, "w");
+    if(fp==NULL){
+        SLOW5_ERROR_EXIT("Error opening file '%s': %s.", filename, strerror(errno));
+        slow5_errno = SLOW5_ERR_IO;
+        return NULL;
+    }
+
+    slow5_file_t *sf = slow5_init_empty(fp, filename, SLOW5_FORMAT_UNKNOWN);
+    if(sf==NULL){
+        SLOW5_ERROR_EXIT("Error initialising an empty SLOW5 file '%s'",filename); //todo error handling down the chain
+        fclose(fp);
+        return NULL;
+    }
+
+    slow5_hdr_t *header=sf->header;
+    if (slow5_hdr_add_rg(header) < 0){
+        SLOW5_ERROR_EXIT("Error adding read group 0 for %s",filename); //todo error handling down the chain
+        slow5_close(sf);
+        return NULL;
+    }
+    header->num_read_groups = 1;
+
+    struct slow5_aux_meta *aux_meta = slow5_aux_meta_init_empty();
+    if(aux_meta == NULL){
+        SLOW5_ERROR_EXIT("Error initializing aux meta for %s",filename); //todo error handling down the chain
+        slow5_close(sf);
+        return NULL;
+    }
+    header->aux_meta = aux_meta;
+
+    //not thread safe, this structure is only used in single threaded writes
+    if(sf->format == SLOW5_FORMAT_BINARY){
+        slow5_press_method_t press_out = {SLOW5_COMPRESS_ZLIB, SLOW5_COMPRESS_SVB_ZD};
+        sf->compress = slow5_press_init(press_out);
+        if(!sf->compress){
+            SLOW5_ERROR_EXIT("Could not initialise the slow5 compression method. %s","");
+            slow5_close(sf);
+            return NULL;
+        }
+    }
+
+    return sf;
+}
+
+slow5_file_t *slow5_open_write_append(const char *filename, const char *mode){
+
+    slow5_file_t* slow5File = slow5_open(filename, "r");
+    if(!slow5File){
+        SLOW5_ERROR_EXIT("Error opening file '%s': %s.", filename, strerror(errno));
+        slow5_errno = SLOW5_ERR_IO;
+        return NULL;
+    }
+
+    if(slow5File->format==SLOW5_FORMAT_BINARY){
+        if(fseek(slow5File->fp , 0, SEEK_END) !=0 ){
+            SLOW5_ERROR_EXIT("Fseek to the end of file failed '%s': %s.", filename, strerror(errno));
+            slow5_errno = SLOW5_ERR_IO;
+            slow5_close(slow5File);
+            return NULL;
+        }
+        const char eof[] = SLOW5_BINARY_EOF;
+        if(slow5_is_eof(slow5File->fp, eof, sizeof eof)!=1){
+            SLOW5_ERROR_EXIT("No valid slow5 EOF marker at the end of the SLOW5 file %s.",filename); //should be a warning instead?
+            //todo error code
+            slow5_close(slow5File);
+            return NULL;
+        }
+    }
+
+    int ret = fclose(slow5File->fp);
+    if(ret != 0){
+        SLOW5_ERROR_EXIT("Closing %s after reading the header failed\n", filename);
+        //todo free what is inside slow5File
+        slow5_errno = SLOW5_ERR_IO;
+        return NULL;
+    }
+
+    //opening a file like this twice is unnecessary if we use open_with, but lazy for now
+    slow5File->fp = fopen(filename, "r+");
+    if(slow5File->fp == NULL){
+        SLOW5_ERROR_EXIT("Error opening file for appending'%s': %s.", filename, strerror(errno));
+        slow5_errno = SLOW5_ERR_IO;
+         //todo free what is inside slow5File
+        return NULL;
+    }
+
+    if(slow5File->format==SLOW5_FORMAT_BINARY){
+        const char eof[] = SLOW5_BINARY_EOF;
+        if(fseek(slow5File->fp, - (sizeof *eof) * (sizeof eof) , SEEK_END) != 0){
+            SLOW5_ERROR_EXIT("Fseek to the end of file failed '%s': %s.", filename, strerror(errno));
+            slow5_errno = SLOW5_ERR_IO;
+            slow5_close(slow5File);
+            return NULL;
+        }
+    } else if (slow5File->format==SLOW5_FORMAT_ASCII){
+        if(fseek(slow5File->fp, 0 , SEEK_END) != 0){
+            SLOW5_ERROR_EXIT("Fseek to the end of file failed '%s': %s.", filename, strerror(errno));
+            slow5_errno = SLOW5_ERR_IO;
+            slow5_close(slow5File);
+            return NULL;
+        }
+    } else {
+        SLOW5_ERROR("Unknown slow5 format for file '%s'. Extension must be '%s' or '%s'.",
+                filename, SLOW5_ASCII_EXTENSION, SLOW5_BINARY_EXTENSION);
+        slow5_errno = SLOW5_ERR_UNK;
+        slow5_close(slow5File);
+        return NULL;
+    }
+
+    return slow5File;
+
+}
+
+
+int slow5_close_write(slow5_file_t *sf){
+    if(sf->format == SLOW5_FORMAT_BINARY){
+        if(slow5_eof_fwrite(sf->fp) < 0){
+            SLOW5_ERROR_EXIT("%s","Error writing EOF!\n");
+            //todo free sf
+            return -1;
+        }
+    }
+    int ret = slow5_close(sf);
+    return ret;
+}
+
+
+int slow5_header_write(slow5_file_t *sf){
+
+    slow5_press_method_t method={SLOW5_COMPRESS_NONE,SLOW5_COMPRESS_NONE};
+    if (sf->format == SLOW5_FORMAT_BINARY){
+        method.record_method = sf->compress->record_press->method;
+        method.signal_method = sf->compress->signal_press->method;
+    }
+    int ret = slow5_hdr_fwrite(sf->fp, sf->header, sf->format, method);
+    return ret;
+}
+
+
+int slow5_rec_write(slow5_file_t *sf, slow5_rec_t *rec){
+    int ret = slow5_rec_fwrite(sf->fp, rec, sf->header->aux_meta, sf->format, sf->compress);
+    return ret;
+}
+
+
+int slow5_aux_meta_add_wrapper(slow5_hdr_t *header, const char *attr, enum slow5_aux_type type){
+    int ret = slow5_aux_meta_add(header->aux_meta, attr, type);
+    return ret;
+}
+
+int slow5_rec_set_wrapper(struct slow5_rec *read, slow5_hdr_t *header, const char *attr, const void *data){
+    int ret = slow5_rec_set(read, header->aux_meta, attr, data);
+    return ret;
+}
+
+int slow5_rec_set_string_wrapper(struct slow5_rec *read, slow5_hdr_t *header, const char *attr, const char *data) {
+    int ret = slow5_rec_set_string(read, header->aux_meta, attr, data);
+    return ret;
+}
+
+
+#ifdef PYSLOW5_WRITE_DEBUG
+
+#define FILE_NAME "test.slow5"
+
+void single_read_group_file(){
+
+    slow5_file_t *sf = slow5_open_write(FILE_NAME, "w");
+    if(sf==NULL){
+        fprintf(stderr,"Error opening file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+
+    /*********************** Header ******************/
+
+    slow5_hdr_t *header=sf->header;
+    //add a header group attribute called run_id
+    if (slow5_hdr_add_attr("run_id", header) != 0){
+        fprintf(stderr,"Error adding run_id attribute\n");
+        exit(EXIT_FAILURE);
+    }
+    //add another header group attribute called asic_id
+    if (slow5_hdr_add_attr("asic_id", header) != 0){
+        fprintf(stderr,"Error adding asic_id attribute\n");
+        exit(EXIT_FAILURE);
+    }
+
+    //set the run_id attribute to "run_0" for read group 0
+    if (slow5_hdr_set("run_id", "run_0", 0, header) != 0){
+        fprintf(stderr,"Error setting run_id attribute in read group 0\n");
+        exit(EXIT_FAILURE);
+    }
+    //set the asic_id attribute to "asic_0" for read group 1
+    if (slow5_hdr_set("asic_id", "asic_id_0", 0, header) != 0){
+        fprintf(stderr,"Error setting asic_id attribute in read group 0\n");
+        exit(EXIT_FAILURE);
+    }
+
+    //add auxilliary field: channel number
+    if (slow5_aux_meta_add(sf->header->aux_meta, "channel_number", SLOW5_STRING)!=0){
+        fprintf(stderr,"Error adding channel_number auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    //add axuilliary field: median_before
+    if (slow5_aux_meta_add(sf->header->aux_meta, "median_before", SLOW5_DOUBLE)!=0){
+        fprintf(stderr,"Error adding median_before auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    //add axuilliary field: read_number
+    if(slow5_aux_meta_add_wrapper(sf->header, "read_number", SLOW5_INT32_T)!=0){
+        fprintf(stderr,"Error adding read_number auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+    //add axuilliary field: start_mux
+    if(slow5_aux_meta_add_wrapper(sf->header, "start_mux", SLOW5_UINT8_T)!=0){
+        fprintf(stderr,"Error adding start_mux auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+    //add auxilliary field: start_time
+    if(slow5_aux_meta_add_wrapper(sf->header, "start_time", SLOW5_UINT64_T)!=0){
+        fprintf(stderr,"Error adding start_time auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    if(slow5_header_write(sf) < 0){
+        fprintf(stderr,"Error writing header!\n");
+        exit(EXIT_FAILURE);
+    }
+
+
+    /******************* A SLOW5 record ************************/
+    slow5_rec_t *slow5_record = slow5_rec_init();
+    if(slow5_record == NULL){
+        fprintf(stderr,"Could not allocate space for a slow5 record.");
+        exit(EXIT_FAILURE);
+    }
+
+    //primary fields
+    slow5_record -> read_id = strdup("read_0");
+    if(slow5_record->read_id == NULL){
+        fprintf(stderr,"Could not allocate space for strdup.");
+        exit(EXIT_FAILURE);
+    }
+    slow5_record -> read_id_len = strlen(slow5_record -> read_id);
+    slow5_record -> read_group = 0;
+    slow5_record -> digitisation = 4096.0;
+    slow5_record -> offset = 3.0;
+    slow5_record -> range = 10.0;
+    slow5_record -> sampling_rate = 4000.0;
+    slow5_record -> len_raw_signal = 10;
+    slow5_record -> raw_signal = malloc(sizeof(int16_t)*10);
+    if(slow5_record->raw_signal == NULL){
+        fprintf(stderr,"Could not allocate space for raw signal.");
+        exit(EXIT_FAILURE);
+    }
+    for(int i=0;i<10;i++){
+        slow5_record->raw_signal[i] = i;
+    }
+
+    //auxiliary fileds
+    char *channel_number = "channel_number";
+    double median_before = 0.1;
+    int32_t read_number = 10;
+    uint8_t start_mux = 1;
+    uint64_t start_time = 100;
+
+    if(slow5_rec_set_string_wrapper(slow5_record, sf->header, "channel_number", channel_number)!=0){
+        fprintf(stderr,"Error setting channel_number auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+    if(slow5_rec_set_wrapper(slow5_record, sf->header, "median_before", &median_before)!=0){
+        fprintf(stderr,"Error setting median_before auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+    if(slow5_rec_set_wrapper(slow5_record, sf->header, "read_number", &read_number)!=0){
+        fprintf(stderr,"Error setting read_number auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    if(slow5_rec_set_wrapper(slow5_record, sf->header, "start_mux", &start_mux)!=0){
+        fprintf(stderr,"Error setting start_mux auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    if(slow5_rec_set_wrapper(slow5_record, sf->header, "start_time", &start_time)!=0){
+        fprintf(stderr,"Error setting start_time auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    slow5_rec_write(sf, slow5_record);
+
+    slow5_rec_free(slow5_record);
+
+    slow5_close_write(sf);
+
+
+    // // now some appending fun
+
+    sf = slow5_open_write_append(FILE_NAME, "a");
+    if(sf==NULL){
+        fprintf(stderr,"Error opening file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    /******************* A SLOW5 record ************************/
+    slow5_record = slow5_rec_init();
+    if(slow5_record == NULL){
+        fprintf(stderr,"Could not allocate space for a slow5 record.");
+        exit(EXIT_FAILURE);
+    }
+
+    //primary fields
+    slow5_record -> read_id = strdup("read_1");
+    if(slow5_record->read_id == NULL){
+        fprintf(stderr,"Could not allocate space for strdup.");
+        exit(EXIT_FAILURE);
+    }
+    slow5_record -> read_id_len = strlen(slow5_record -> read_id);
+    slow5_record -> read_group = 0;
+    slow5_record -> digitisation = 4096.0;
+    slow5_record -> offset = 4.0;
+    slow5_record -> range = 12.0;
+    slow5_record -> sampling_rate = 4000.0;
+    slow5_record -> len_raw_signal = 12;
+    slow5_record -> raw_signal = malloc(sizeof(int16_t)*12);
+    if(slow5_record->raw_signal == NULL){
+        fprintf(stderr,"Could not allocate space for raw signal.");
+        exit(EXIT_FAILURE);
+    }
+    for(int i=0;i<12;i++){
+        slow5_record->raw_signal[i] = i;
+    }
+
+    //auxiliary fileds
+    channel_number = "channel_number";
+    median_before = 0.2;
+    read_number = 11;
+    start_mux = 2;
+    start_time = 200;
+
+    if(slow5_rec_set_string_wrapper(slow5_record, sf->header, "channel_number", channel_number)!=0){
+        fprintf(stderr,"Error setting channel_number auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+    if(slow5_rec_set_wrapper(slow5_record, sf->header, "median_before", &median_before)!=0){
+        fprintf(stderr,"Error setting median_before auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+    if(slow5_rec_set_wrapper(slow5_record, sf->header, "read_number", &read_number)!=0){
+        fprintf(stderr,"Error setting read_number auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    if(slow5_rec_set_wrapper(slow5_record, sf->header, "start_mux", &start_mux)!=0){
+        fprintf(stderr,"Error setting start_mux auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    if(slow5_rec_set_wrapper(slow5_record, sf->header, "start_time", &start_time)!=0){
+        fprintf(stderr,"Error setting start_time auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    slow5_rec_write(sf, slow5_record);
+
+    slow5_rec_free(slow5_record);
+
+    slow5_close_write(sf);
+
+}
+
+
+int main(){
+
+
+    single_read_group_file();
+
+
+    return EXIT_SUCCESS;
+
+
+}
+
+#endif
+//gcc -Wall python/slow5_write.c  -I include/ lib/libslow5.a -lm -lz  -O2 -g -D PYSLOW5_WRITE_DEBUG=1
diff --git a/python/slow5_write.h b/python/slow5_write.h
new file mode 100644
index 00000000..14a516d6
--- /dev/null
+++ b/python/slow5_write.h
@@ -0,0 +1,14 @@
+#ifndef PYSLOW5_WRITE_H
+#define PYSLOW5_WRITE_H
+
+//these functionalities will be moved to the slow5lib API in a while, but names will change
+slow5_file_t *slow5_open_write(const char *filename, const char *mode);
+slow5_file_t *slow5_open_write_append(const char *filename, const char *mode);
+int slow5_close_write(slow5_file_t *sf);
+int slow5_header_write(slow5_file_t *sf);
+int slow5_rec_write(slow5_file_t *sf, slow5_rec_t *rec);
+int slow5_aux_meta_add_wrapper(slow5_hdr_t *header, const char *attr, enum slow5_aux_type type);
+int slow5_rec_set_wrapper(struct slow5_rec *read, slow5_hdr_t *header, const char *attr, const void *data);
+int slow5_rec_set_string_wrapper(struct slow5_rec *read, slow5_hdr_t *header, const char *attr, const char *data);
+
+#endif
diff --git a/python/slow5threads.c b/python/slow5threads.c
new file mode 100644
index 00000000..9df0e7e0
--- /dev/null
+++ b/python/slow5threads.c
@@ -0,0 +1,646 @@
+/* @file slow5threads.c
+**
+** @@
+******************************************************************************/
+
+#include <assert.h>
+#include <math.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <slow5/slow5.h>
+#include "../src/slow5_extra.h"
+#include "slow5_write.h"
+
+#define SLOW5_WORK_STEAL 1 //simple work stealing enabled or not (no work stealing mean no load balancing)
+#define SLOW5_STEAL_THRESH 1 //stealing threshold
+
+extern enum slow5_log_level_opt  slow5_log_level;
+extern enum slow5_exit_condition_opt  slow5_exit_condition;
+
+#define SLOW5_MALLOC_CHK_LAZY_EXIT(ret) { \
+    SLOW5_MALLOC_CHK(ret) \
+    if (ret == NULL) { \
+        exit(EXIT_FAILURE); \
+    } \
+}
+
+/* a batch of read data (dynamic data based on the reads) */
+typedef struct {
+    int32_t n_rec;
+    int32_t capacity_rec;
+
+    char **mem_records; //unused in get()
+    size_t *mem_bytes;
+
+    slow5_rec_t **slow5_rec;
+    char **rid; //only used in get()
+
+} slow5_db_t;
+
+
+/* core data structure (mostly static data throughout the program lifetime) */
+typedef struct {
+    //slow5
+    slow5_file_t *sf;
+    int num_thread;
+    int batch_size;
+} slow5_core_t;
+
+
+/* argument wrapper for the multithreaded framework used for data processing */
+typedef struct {
+    slow5_core_t* core;
+    slow5_db_t* db;
+    int32_t starti;
+    int32_t endi;
+    void (*func)(slow5_core_t*,slow5_db_t*,int);
+    int32_t thread_index;
+#ifdef SLOW5_WORK_STEAL
+    void *all_pthread_args;
+#endif
+} slow5_pt_arg_t;
+
+
+/* initialise the core data structure */
+static slow5_core_t* slow5_init_core(slow5_file_t *s5p, int batch_size, int num_thread) {
+
+    slow5_core_t* core = (slow5_core_t*)malloc(sizeof(slow5_core_t));
+    SLOW5_MALLOC_CHK_LAZY_EXIT(core);
+
+    core->sf = s5p;
+    core->batch_size = batch_size;
+    core->num_thread = num_thread;
+
+    return core;
+}
+
+/* free the core data structure */
+static void slow5_free_core(slow5_core_t* core) {
+    free(core);
+}
+
+/* initialise a data batch */
+static slow5_db_t* slow5_init_db(slow5_core_t* core) {
+    slow5_db_t* db = (slow5_db_t*)(malloc(sizeof(slow5_db_t)));
+    SLOW5_MALLOC_CHK_LAZY_EXIT(db);
+
+    db->capacity_rec = core->batch_size;
+    db->n_rec = 0;
+
+    db->mem_records = (char**)(calloc(db->capacity_rec,sizeof(char*)));
+    SLOW5_MALLOC_CHK_LAZY_EXIT(db->mem_records);
+    db->mem_bytes = (size_t*)(calloc(db->capacity_rec,sizeof(size_t)));
+    SLOW5_MALLOC_CHK_LAZY_EXIT(db->mem_bytes);
+
+    db->slow5_rec = (slow5_rec_t**)calloc(db->capacity_rec,sizeof(slow5_rec_t*));
+    SLOW5_MALLOC_CHK_LAZY_EXIT(db->slow5_rec);
+
+    return db;
+}
+
+/* load a data batch from disk */
+static int slow5_load_db(slow5_core_t* core, slow5_db_t* db) {
+
+    db->n_rec = 0;
+
+    int32_t i = 0;
+    while (db->n_rec < db->capacity_rec) {
+        i=db->n_rec;
+        db->mem_records[i] = (char *)slow5_get_next_mem(&(db->mem_bytes[i]), core->sf);
+
+        if (db->mem_records[i] == NULL) {
+            if (slow5_errno != SLOW5_ERR_EOF) {
+                SLOW5_ERROR("Error reading from SLOW5 file %d\n", slow5_errno);
+                exit(EXIT_FAILURE);
+            }
+            else {
+                SLOW5_LOG_DEBUG("%s","Last Batch!\n");
+                break;
+            }
+        }
+        else {
+            db->n_rec++;
+        }
+    }
+
+    return db->n_rec;
+}
+
+
+static int slow5_write_db(slow5_core_t* core, slow5_db_t* db) {
+
+
+    int32_t i = 0;
+    for(i=0;i<db->n_rec;i++) {
+
+        size_t n = fwrite(db->mem_records[i], db->mem_bytes[i], 1, core->sf->fp);
+        if (n != 1) {
+            SLOW5_ERROR("Writing failed for read id %s!\n", db->slow5_rec[i]->read_id);
+        }
+
+    }
+
+    return i;
+}
+
+
+static void slow5_parse_single(slow5_core_t* core,slow5_db_t* db, int32_t i){
+
+    assert(db->mem_bytes[i]>0);
+    assert(db->mem_records[i]!=NULL);
+    int ret=slow5_rec_depress_parse(&db->mem_records[i], &db->mem_bytes[i], NULL, &db->slow5_rec[i], core->sf);
+    if(ret!=0){
+        SLOW5_ERROR("Error parsing the record %s",db->slow5_rec[i]->read_id);
+        exit(EXIT_FAILURE);
+    }
+
+}
+
+
+static void slow5_work_per_single_read(slow5_core_t* core,slow5_db_t* db, int32_t i){
+    slow5_parse_single(core,db,i);
+}
+
+static void slow5_work_per_single_read2(slow5_core_t* core,slow5_db_t* db, int32_t i){
+    assert(db->rid[i]!=NULL);
+    int ret = slow5_get(db->rid[i],&db->slow5_rec[i], core->sf);
+    if(ret<0){
+        SLOW5_ERROR("Error when fetching the read %s\n",db->rid[i]);
+        exit(EXIT_FAILURE);
+    }
+    db->mem_bytes[i]=ret;
+
+}
+
+static void slow5_work_per_single_read3(slow5_core_t* core,slow5_db_t* db, int32_t i){
+    assert(db->slow5_rec[i]!=NULL);
+    slow5_file_t *sf = core->sf;
+    //fprintf(stderr,"Here %d\n",i);
+    slow5_press_method_t press_out = {SLOW5_COMPRESS_ZLIB, SLOW5_COMPRESS_SVB_ZD};
+    slow5_press_t *press_ptr = slow5_press_init(press_out);
+    if(!press_ptr){
+        SLOW5_ERROR("Could not initialize the slow5 compression method%s","");
+        exit(EXIT_FAILURE);
+    }
+    db->mem_records[i] = slow5_rec_to_mem(db->slow5_rec[i], sf->header->aux_meta, sf->format, press_ptr, &(db->mem_bytes[i]));
+    //fprintf(stderr,"Here 2 %d\n",i);
+    slow5_press_free(press_ptr);
+
+    if(db->mem_records[i] == NULL){
+        SLOW5_ERROR("Error when converting the read %d to memory\n",i);
+        exit(EXIT_FAILURE);
+    }
+
+}
+
+
+/* partially free a data batch - only the read dependent allocations are freed */
+static void slow5_free_db_tmp(slow5_db_t* db) {
+    int32_t i = 0;
+    for (i = 0; i < db->n_rec; ++i) {
+        free(db->mem_records[i]);
+    }
+}
+
+/* completely free a data batch */
+static void slow5_free_db(slow5_db_t* db) {
+
+    free(db->mem_records);
+    free(db->mem_bytes);;
+
+    free(db);
+}
+
+
+static inline int32_t steal_work(slow5_pt_arg_t* all_args, int32_t num_thread) {
+	int32_t i, c_i = -1;
+	int32_t k;
+	for (i = 0; i < num_thread; ++i){
+        slow5_pt_arg_t args = all_args[i];
+        //fprintf(stderr,"endi : %d, starti : %d\n",args.endi,args.starti);
+		if (args.endi-args.starti > SLOW5_STEAL_THRESH) {
+            //fprintf(stderr,"gap : %d\n",args.endi-args.starti);
+            c_i = i;
+            break;
+        }
+    }
+    if(c_i<0){
+        return -1;
+    }
+	k = __sync_fetch_and_add(&(all_args[c_i].starti), 1);
+    //fprintf(stderr,"k : %d, end %d, start %d\n",k,all_args[c_i].endi,all_args[c_i].starti);
+	return k >= all_args[c_i].endi ? -1 : k;
+}
+
+
+static void* slow5_pthread_single(void* voidargs) {
+    int32_t i;
+    slow5_pt_arg_t* args = (slow5_pt_arg_t*)voidargs;
+    slow5_db_t* db = args->db;
+    slow5_core_t* core = args->core;
+
+#ifndef SLOW5_WORK_STEAL
+    for (i = args->starti; i < args->endi; i++) {
+        args->func(core,db,i);
+    }
+#else
+    slow5_pt_arg_t* all_args = (slow5_pt_arg_t*)(args->all_pthread_args);
+    //adapted from kthread.c in minimap2
+    for (;;) {
+		i = __sync_fetch_and_add(&args->starti, 1);
+		if (i >= args->endi) {
+            break;
+        }
+		args->func(core,db,i);
+	}
+	while ((i = steal_work(all_args,core->num_thread)) >= 0){
+		args->func(core,db,i);
+    }
+#endif
+
+    //fprintf(stderr,"Thread %d done\n",(myargs->position)/THREADS);
+    pthread_exit(0);
+}
+
+static void slow5_pthread_db(slow5_core_t* core, slow5_db_t* db, void (*func)(slow5_core_t*,slow5_db_t*,int)){
+    //create threads
+    pthread_t tids[core->num_thread];
+    slow5_pt_arg_t pt_args[core->num_thread];
+    int32_t t, ret;
+    int32_t i = 0;
+    int32_t num_thread = core->num_thread;
+    int32_t step = (db->n_rec + num_thread - 1) / num_thread;
+    //todo : check for higher num of threads than the data
+    //current works but many threads are created despite
+
+    SLOW5_LOG_DEBUG("Creating %d threads\n",num_thread);
+    //set the data structures
+    for (t = 0; t < num_thread; t++) {
+        pt_args[t].core = core;
+        pt_args[t].db = db;
+        pt_args[t].starti = i;
+        i += step;
+        if (i > db->n_rec) {
+            pt_args[t].endi = db->n_rec;
+        } else {
+            pt_args[t].endi = i;
+        }
+        pt_args[t].func=func;
+    #ifdef SLOW5_WORK_STEAL
+        pt_args[t].all_pthread_args =  (void *)pt_args;
+    #endif
+        //fprintf(stderr,"t%d : %d-%d\n",t,pt_args[t].starti,pt_args[t].endi);
+
+    }
+
+    //create threads
+    for(t = 0; t < core->num_thread; t++){
+        ret = pthread_create(&tids[t], NULL, slow5_pthread_single,
+                                (void*)(&pt_args[t]));
+        if(ret < 0){
+            SLOW5_ERROR("Error creating thread %d\n",t);
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    //pthread joining
+    for (t = 0; t < core->num_thread; t++) {
+        int ret = pthread_join(tids[t], NULL);
+        if(ret < 0){
+            SLOW5_ERROR("Error creating thread %d\n",t);
+            exit(EXIT_FAILURE);
+        }
+    }
+}
+
+/* process all reads in the given batch db */
+static void slow5_work_db(slow5_core_t* core, slow5_db_t* db, void (*func)(slow5_core_t*,slow5_db_t*,int)){
+
+    if (core->num_thread == 1) {
+        int32_t i=0;
+        for (i = 0; i < db->n_rec; i++) {
+            func(core,db,i);
+        }
+
+    }
+
+    else {
+        slow5_pthread_db(core,db,func);
+    }
+}
+
+int slow5_get_batch(slow5_rec_t ***read, slow5_file_t *s5p, char **rid, int num_rid, int num_threads){
+
+    slow5_core_t *core = slow5_init_core(s5p,num_rid,num_threads);
+    slow5_db_t* db = slow5_init_db(core);
+
+    db->rid = rid;
+    db->n_rec = num_rid;
+    slow5_work_db(core,db,slow5_work_per_single_read2);
+    SLOW5_LOG_DEBUG("loaded and parsed %d recs\n",num_rid);
+
+    *read = db->slow5_rec;
+
+    slow5_free_db_tmp(db);
+    slow5_free_db(db);
+    slow5_free_core(core);
+
+    return num_rid;
+}
+
+
+int slow5_get_next_batch(slow5_rec_t ***read, slow5_file_t *s5p, int batch_size, int num_threads){
+
+    slow5_core_t *core = slow5_init_core(s5p,batch_size,num_threads);
+    slow5_db_t* db = slow5_init_db(core);
+
+    int num_read=slow5_load_db(core,db);
+    SLOW5_LOG_DEBUG("Loaded %d recs\n",num_read);
+    slow5_work_db(core,db,slow5_work_per_single_read);
+    SLOW5_LOG_DEBUG("Parsed %d recs\n",num_read);
+
+    *read = db->slow5_rec;
+
+    slow5_free_db_tmp(db);
+    slow5_free_db(db);
+    slow5_free_core(core);
+
+    return num_read;
+}
+
+
+int slow5_write_batch(slow5_rec_t **read, slow5_file_t *s5p, int batch_size, int num_threads){
+
+    slow5_core_t *core = slow5_init_core(s5p,batch_size,num_threads);
+    slow5_db_t* db = slow5_init_db(core);
+
+    db->n_rec = batch_size;
+    free(db->slow5_rec); //stupid lazy for now
+    db->slow5_rec = read;
+    slow5_work_db(core,db,slow5_work_per_single_read3);
+    SLOW5_LOG_DEBUG("Processed %d recs\n",batch_size);
+
+    int num_wr=slow5_write_db(core,db);
+    SLOW5_LOG_DEBUG("Written %d recs\n",num_wr);
+
+    db->slow5_rec = NULL;
+    slow5_free_db_tmp(db);
+    slow5_free_db(db);
+    slow5_free_core(core);
+
+    return num_wr;
+}
+
+
+
+void slow5_free_batch(slow5_rec_t ***read, int num_rec){
+
+    slow5_rec_t **reads = *read;
+    for(int i=0;i<num_rec;i++){
+        slow5_rec_free(reads[i]);
+    }
+
+    free(reads);
+    *read = NULL;
+}
+
+#ifdef PYSLOW5_DEBUG_THREAD
+
+#define FILE_PATH "test.blow5" //for reading
+#define FILE_PATH_WRITE "test.blow5"
+//#define FILE_PATH "/home/jamfer/Data/SK/multi_fast5/s5/FAK40634_d1cc054609fe2c5fcdeac358864f9dc81c8bb793_95.blow5"
+
+int read_func(){
+
+    slow5_file_t *sp = slow5_open(FILE_PATH,"r");
+    if(sp==NULL){
+        fprintf(stderr,"Error in opening file\n");
+        exit(EXIT_FAILURE);
+    }
+    slow5_rec_t **rec = NULL;
+    int ret=0;
+    int batch_size = 4096;
+    int num_thread = 8;
+    while((ret = slow5_get_next_batch(&rec,sp,batch_size,num_thread)) > 0){
+
+        for(int i=0;i<ret;i++){
+            uint64_t len_raw_signal = rec[i]->len_raw_signal;
+            printf("%s\t%ld\n",rec[i]->read_id,len_raw_signal);
+        }
+        slow5_free_batch(&rec,ret);
+
+        if(ret<batch_size){ //this indicates nothing left to read //need to handle errors
+            break;
+        }
+    }
+
+    slow5_close(sp);
+
+
+    //now random read fun
+    sp = slow5_open(FILE_PATH,"r");
+    if(sp==NULL){
+       fprintf(stderr,"Error in opening file\n");
+       exit(EXIT_FAILURE);
+    }
+    rec = NULL;
+
+    ret = slow5_idx_load(sp);
+    if(ret<0){
+        fprintf(stderr,"Error in loading index\n");
+        exit(EXIT_FAILURE);
+    }
+
+    int num_rid = 4;
+    num_thread = 2;
+    char *rid[num_rid];
+    rid[0]="read_id_50";
+    rid[1]="read_id_3999",
+    rid[2]="read_id_0";
+    rid[3]="read_id_4";
+
+    ret = slow5_get_batch(&rec, sp, rid, num_rid, num_thread);
+    assert(ret==num_rid);
+    for(int i=0;i<ret;i++){
+        uint64_t len_raw_signal = rec[i]->len_raw_signal;
+        printf("%s\t%ld\n",rec[i]->read_id,len_raw_signal);
+    }
+    slow5_free_batch(&rec,ret);
+
+    slow5_idx_unload(sp);
+    slow5_close(sp);
+
+    return 0;
+}
+
+
+int write_func(){
+
+    slow5_file_t *sf = slow5_open_write(FILE_PATH_WRITE,"w");
+    if(sf==NULL){
+        fprintf(stderr,"Error in opening file\n");
+        exit(EXIT_FAILURE);
+    }
+
+  /*********************** Header ******************/
+
+    slow5_hdr_t *header=sf->header;
+    //add a header group attribute called run_id
+    if (slow5_hdr_add_attr("run_id", header) != 0){
+        fprintf(stderr,"Error adding run_id attribute\n");
+        exit(EXIT_FAILURE);
+    }
+    //add another header group attribute called asic_id
+    if (slow5_hdr_add_attr("asic_id", header) != 0){
+        fprintf(stderr,"Error adding asic_id attribute\n");
+        exit(EXIT_FAILURE);
+    }
+
+    //set the run_id attribute to "run_0" for read group 0
+    if (slow5_hdr_set("run_id", "run_0", 0, header) != 0){
+        fprintf(stderr,"Error setting run_id attribute in read group 0\n");
+        exit(EXIT_FAILURE);
+    }
+    //set the asic_id attribute to "asic_0" for read group 1
+    if (slow5_hdr_set("asic_id", "asic_id_0", 0, header) != 0){
+        fprintf(stderr,"Error setting asic_id attribute in read group 0\n");
+        exit(EXIT_FAILURE);
+    }
+
+    //add auxilliary field: channel number
+    if (slow5_aux_meta_add(sf->header->aux_meta, "channel_number", SLOW5_STRING)!=0){
+        fprintf(stderr,"Error adding channel_number auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    //add axuilliary field: median_before
+    if (slow5_aux_meta_add(sf->header->aux_meta, "median_before", SLOW5_DOUBLE)!=0){
+        fprintf(stderr,"Error adding median_before auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    //add axuilliary field: read_number
+    if(slow5_aux_meta_add_wrapper(sf->header, "read_number", SLOW5_INT32_T)!=0){
+        fprintf(stderr,"Error adding read_number auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+    //add axuilliary field: start_mux
+    if(slow5_aux_meta_add_wrapper(sf->header, "start_mux", SLOW5_UINT8_T)!=0){
+        fprintf(stderr,"Error adding start_mux auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+    //add auxilliary field: start_time
+    if(slow5_aux_meta_add_wrapper(sf->header, "start_time", SLOW5_UINT64_T)!=0){
+        fprintf(stderr,"Error adding start_time auxilliary field\n");
+        exit(EXIT_FAILURE);
+    }
+
+    if(slow5_header_write(sf) < 0){
+        fprintf(stderr,"Error writing header!\n");
+        exit(EXIT_FAILURE);
+    }
+
+
+    slow5_rec_t *rec[4000];
+    int ret=0;
+    int batch_size = 4000;
+    int num_thread = 8;
+
+
+    /******************* SLOW5 records ************************/
+    for(int i=0;i<batch_size;i++){
+
+
+        slow5_rec_t *slow5_record = rec[i] = slow5_rec_init();
+
+        if(slow5_record == NULL){
+            fprintf(stderr,"Could not allocate space for a slow5 record.");
+            exit(EXIT_FAILURE);
+        }
+
+        //primary fields
+        char tmp_read_id[100];
+        sprintf(tmp_read_id,"read_id_%d",i);
+        slow5_record -> read_id = strdup(tmp_read_id);
+        if(slow5_record->read_id == NULL){
+            fprintf(stderr,"Could not allocate space for strdup.");
+            exit(EXIT_FAILURE);
+        }
+        slow5_record -> read_id_len = strlen(slow5_record -> read_id);
+        slow5_record -> read_group = 0;
+        slow5_record -> digitisation = 4096.0;
+        slow5_record -> offset = 3.0+i;
+        slow5_record -> range = 10.0+i;
+        slow5_record -> sampling_rate = 4000.0;
+        slow5_record -> len_raw_signal = 10+i;
+        slow5_record -> raw_signal = malloc(sizeof(int16_t)*(10+i));
+        if(slow5_record->raw_signal == NULL){
+            fprintf(stderr,"Could not allocate space for raw signal.");
+            exit(EXIT_FAILURE);
+        }
+        for(int j=0;j<10+i;j++){
+            slow5_record->raw_signal[j] = j+i;
+        }
+
+        //auxiliary fileds
+        char *channel_number = "channel_number";
+        double median_before = 0.1+i;
+        int32_t read_number = 10+i;
+        uint8_t start_mux = (1+i)%4;
+        uint64_t start_time = 100+i;
+
+        if(slow5_rec_set_string_wrapper(slow5_record, sf->header, "channel_number", channel_number)!=0){
+            fprintf(stderr,"Error setting channel_number auxilliary field\n");
+            exit(EXIT_FAILURE);
+        }
+        if(slow5_rec_set_wrapper(slow5_record, sf->header, "median_before", &median_before)!=0){
+            fprintf(stderr,"Error setting median_before auxilliary field\n");
+            exit(EXIT_FAILURE);
+        }
+        if(slow5_rec_set_wrapper(slow5_record, sf->header, "read_number", &read_number)!=0){
+            fprintf(stderr,"Error setting read_number auxilliary field\n");
+            exit(EXIT_FAILURE);
+        }
+
+        if(slow5_rec_set_wrapper(slow5_record, sf->header, "start_mux", &start_mux)!=0){
+            fprintf(stderr,"Error setting start_mux auxilliary field\n");
+            exit(EXIT_FAILURE);
+        }
+
+        if(slow5_rec_set_wrapper(slow5_record, sf->header, "start_time", &start_time)!=0){
+            fprintf(stderr,"Error setting start_time auxilliary field\n");
+            exit(EXIT_FAILURE);
+        }
+    }
+    //end of record setup
+
+    ret = slow5_write_batch(rec,sf,batch_size,num_thread);
+
+    if(ret<batch_size){
+        fprintf(stderr,"Writing failed\n");
+        exit(EXIT_FAILURE);
+    }
+
+    slow5_close_write(sf);
+
+    for(int i=0;i<batch_size;i++){
+        slow5_rec_free(rec[i]);
+    }
+
+    return 0;
+}
+
+int main(){
+
+    write_func();
+    read_func();
+
+
+    return 0;
+}
+
+//gcc -Wall python/slow5threads.c python/slow5_write.c -I include/ lib/libslow5.a  -lpthread -lz -DPYSLOW5_DEBUG_THREAD=1 -O2 -g
+
+#endif
\ No newline at end of file
diff --git a/python/slow5threads.h b/python/slow5threads.h
new file mode 100644
index 00000000..2c09c48f
--- /dev/null
+++ b/python/slow5threads.h
@@ -0,0 +1,17 @@
+/* @file slow5threads.h
+**
+******************************************************************************/
+
+#ifndef SLOW5THREADS_H
+#define SLOW5THREADS_H
+
+//these functions will lazily exit on error (need to do proper error handling, but a bit too much work at the moment)
+//also these functions are not optimised for cases that are unlikely to be bottlenecks
+//that is they do superfluous mallocs and free and computations in cases which are unlikely to be bottlenecks
+//also each batch call will create and destruct threads rather than using a thread pool
+int slow5_get_batch(slow5_rec_t ***read, slow5_file_t *s5p, char **rid, int num_rid, int num_threads);
+int slow5_get_next_batch(slow5_rec_t ***read, slow5_file_t *s5p, int batch_size, int num_threads);
+int slow5_write_batch(slow5_rec_t **read, slow5_file_t *s5p, int batch_size, int num_threads);
+void slow5_free_batch(slow5_rec_t ***read, int num_rec);
+
+#endif
diff --git a/python/valgrind-python.supp b/python/valgrind-python.supp
new file mode 100644
index 00000000..487f8dc8
--- /dev/null
+++ b/python/valgrind-python.supp
@@ -0,0 +1,499 @@
+#
+# This is a valgrind suppression file that should be used when using valgrind.
+#
+#  Here's an example of running valgrind:
+#
+#	cd python/dist/src
+#	valgrind --tool=memcheck --suppressions=Misc/valgrind-python.supp \
+#		./python -E ./Lib/test/regrtest.py -u gui,network
+#
+# You must edit Objects/obmalloc.c and uncomment Py_USING_MEMORY_DEBUGGER
+# to use the preferred suppressions with address_in_range.
+#
+# If you do not want to recompile Python, you can uncomment
+# suppressions for _PyObject_Free and _PyObject_Realloc.
+#
+# See Misc/README.valgrind for more information.
+
+# all tool names: Addrcheck,Memcheck,cachegrind,helgrind,massif
+{
+   ADDRESS_IN_RANGE/Invalid read of size 4
+   Memcheck:Addr4
+   fun:address_in_range
+}
+
+{
+   ADDRESS_IN_RANGE/Invalid read of size 4
+   Memcheck:Value4
+   fun:address_in_range
+}
+
+{
+   ADDRESS_IN_RANGE/Invalid read of size 8 (x86_64 aka amd64)
+   Memcheck:Value8
+   fun:address_in_range
+}
+
+{
+   ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value
+   Memcheck:Cond
+   fun:address_in_range
+}
+
+#
+# Leaks (including possible leaks)
+#    Hmmm, I wonder if this masks some real leaks.  I think it does.
+#    Will need to fix that.
+#
+
+{
+   Suppress leaking the GIL.  Happens once per process, see comment in ceval.c.
+   Memcheck:Leak
+   fun:malloc
+   fun:PyThread_allocate_lock
+   fun:PyEval_InitThreads
+}
+
+{
+   Suppress leaking the GIL after a fork.
+   Memcheck:Leak
+   fun:malloc
+   fun:PyThread_allocate_lock
+   fun:PyEval_ReInitThreads
+}
+
+{
+   Suppress leaking the autoTLSkey.  This looks like it shouldn't leak though.
+   Memcheck:Leak
+   fun:malloc
+   fun:PyThread_create_key
+   fun:_PyGILState_Init
+   fun:Py_InitializeEx
+   fun:Py_Main
+}
+
+{
+   Hmmm, is this a real leak or like the GIL?
+   Memcheck:Leak
+   fun:malloc
+   fun:PyThread_ReInitTLS
+}
+
+{
+   Handle PyMalloc confusing valgrind (possibly leaked)
+   Memcheck:Leak
+   fun:realloc
+   fun:_PyObject_GC_Resize
+   fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING
+}
+
+{
+   Handle PyMalloc confusing valgrind (possibly leaked)
+   Memcheck:Leak
+   fun:malloc
+   fun:_PyObject_GC_New
+   fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING
+}
+
+{
+   Handle PyMalloc confusing valgrind (possibly leaked)
+   Memcheck:Leak
+   fun:malloc
+   fun:_PyObject_GC_NewVar
+   fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING
+}
+
+#
+# Non-python specific leaks
+#
+
+{
+   Handle pthread issue (possibly leaked)
+   Memcheck:Leak
+   fun:calloc
+   fun:allocate_dtv
+   fun:_dl_allocate_tls_storage
+   fun:_dl_allocate_tls
+}
+
+{
+   Handle pthread issue (possibly leaked)
+   Memcheck:Leak
+   fun:memalign
+   fun:_dl_allocate_tls_storage
+   fun:_dl_allocate_tls
+}
+
+{
+   ADDRESS_IN_RANGE/Invalid read of size 4
+   Memcheck:Addr4
+   fun:_PyObject_Free
+}
+
+{
+   ADDRESS_IN_RANGE/Invalid read of size 4
+   Memcheck:Value4
+   fun:_PyObject_Free
+}
+
+{
+   ADDRESS_IN_RANGE/Use of uninitialised value of size 8
+   Memcheck:Addr8
+   fun:_PyObject_Free
+}
+
+{
+   ADDRESS_IN_RANGE/Use of uninitialised value of size 8
+   Memcheck:Value8
+   fun:_PyObject_Free
+}
+
+{
+   ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value
+   Memcheck:Cond
+   fun:_PyObject_Free
+}
+
+{
+   ADDRESS_IN_RANGE/Invalid read of size 4
+   Memcheck:Addr4
+   fun:_PyObject_Realloc
+}
+
+{
+   ADDRESS_IN_RANGE/Invalid read of size 4
+   Memcheck:Value4
+   fun:_PyObject_Realloc
+}
+
+{
+   ADDRESS_IN_RANGE/Use of uninitialised value of size 8
+   Memcheck:Addr8
+   fun:_PyObject_Realloc
+}
+
+{
+   ADDRESS_IN_RANGE/Use of uninitialised value of size 8
+   Memcheck:Value8
+   fun:_PyObject_Realloc
+}
+
+{
+   ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value
+   Memcheck:Cond
+   fun:_PyObject_Realloc
+}
+
+###
+### All the suppressions below are for errors that occur within libraries
+### that Python uses.  The problems to not appear to be related to Python's
+### use of the libraries.
+###
+
+{
+   Generic ubuntu ld problems
+   Memcheck:Addr8
+   obj:/lib/ld-2.4.so
+   obj:/lib/ld-2.4.so
+   obj:/lib/ld-2.4.so
+   obj:/lib/ld-2.4.so
+}
+
+{
+   Generic gentoo ld problems
+   Memcheck:Cond
+   obj:/lib/ld-2.3.4.so
+   obj:/lib/ld-2.3.4.so
+   obj:/lib/ld-2.3.4.so
+   obj:/lib/ld-2.3.4.so
+}
+
+{
+   DBM problems, see test_dbm
+   Memcheck:Param
+   write(buf)
+   fun:write
+   obj:/usr/lib/libdb1.so.2
+   obj:/usr/lib/libdb1.so.2
+   obj:/usr/lib/libdb1.so.2
+   obj:/usr/lib/libdb1.so.2
+   fun:dbm_close
+}
+
+{
+   DBM problems, see test_dbm
+   Memcheck:Value8
+   fun:memmove
+   obj:/usr/lib/libdb1.so.2
+   obj:/usr/lib/libdb1.so.2
+   obj:/usr/lib/libdb1.so.2
+   obj:/usr/lib/libdb1.so.2
+   fun:dbm_store
+   fun:dbm_ass_sub
+}
+
+{
+   DBM problems, see test_dbm
+   Memcheck:Cond
+   obj:/usr/lib/libdb1.so.2
+   obj:/usr/lib/libdb1.so.2
+   obj:/usr/lib/libdb1.so.2
+   fun:dbm_store
+   fun:dbm_ass_sub
+}
+
+{
+   DBM problems, see test_dbm
+   Memcheck:Cond
+   fun:memmove
+   obj:/usr/lib/libdb1.so.2
+   obj:/usr/lib/libdb1.so.2
+   obj:/usr/lib/libdb1.so.2
+   obj:/usr/lib/libdb1.so.2
+   fun:dbm_store
+   fun:dbm_ass_sub
+}
+
+{
+   GDBM problems, see test_gdbm
+   Memcheck:Param
+   write(buf)
+   fun:write
+   fun:gdbm_open
+
+}
+
+{
+   Uninitialised byte(s) false alarm, see bpo-35561
+   Memcheck:Param
+   epoll_ctl(event)
+   fun:epoll_ctl
+   fun:pyepoll_internal_ctl
+}
+
+{
+   ZLIB problems, see test_gzip
+   Memcheck:Cond
+   obj:/lib/libz.so.1.2.3
+   obj:/lib/libz.so.1.2.3
+   fun:deflate
+}
+
+{
+   Avoid problems w/readline doing a putenv and leaking on exit
+   Memcheck:Leak
+   fun:malloc
+   fun:xmalloc
+   fun:sh_set_lines_and_columns
+   fun:_rl_get_screen_size
+   fun:_rl_init_terminal_io
+   obj:/lib/libreadline.so.4.3
+   fun:rl_initialize
+}
+
+# Valgrind emits "Conditional jump or move depends on uninitialised value(s)"
+# false alarms on GCC builtin strcmp() function. The GCC code is correct.
+#
+# Valgrind bug: https://bugs.kde.org/show_bug.cgi?id=264936
+{
+   bpo-38118: Valgrind emits false alarm on GCC builtin strcmp()
+   Memcheck:Cond
+   fun:PyUnicode_Decode
+}
+
+
+###
+### These occur from somewhere within the SSL, when running
+###  test_socket_sll.  They are too general to leave on by default.
+###
+###{
+###   somewhere in SSL stuff
+###   Memcheck:Cond
+###   fun:memset
+###}
+###{
+###   somewhere in SSL stuff
+###   Memcheck:Value4
+###   fun:memset
+###}
+###
+###{
+###   somewhere in SSL stuff
+###   Memcheck:Cond
+###   fun:MD5_Update
+###}
+###
+###{
+###   somewhere in SSL stuff
+###   Memcheck:Value4
+###   fun:MD5_Update
+###}
+
+# Fedora's package "openssl-1.0.1-0.1.beta2.fc17.x86_64" on x86_64
+# See http://bugs.python.org/issue14171
+{
+   openssl 1.0.1 prng 1
+   Memcheck:Cond
+   fun:bcmp
+   fun:fips_get_entropy
+   fun:FIPS_drbg_instantiate
+   fun:RAND_init_fips
+   fun:OPENSSL_init_library
+   fun:SSL_library_init
+   fun:init_hashlib
+}
+
+{
+   openssl 1.0.1 prng 2
+   Memcheck:Cond
+   fun:fips_get_entropy
+   fun:FIPS_drbg_instantiate
+   fun:RAND_init_fips
+   fun:OPENSSL_init_library
+   fun:SSL_library_init
+   fun:init_hashlib
+}
+
+{
+   openssl 1.0.1 prng 3
+   Memcheck:Value8
+   fun:_x86_64_AES_encrypt_compact
+   fun:AES_encrypt
+}
+
+#
+# All of these problems come from using test_socket_ssl
+#
+{
+   from test_socket_ssl
+   Memcheck:Cond
+   fun:BN_bin2bn
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Cond
+   fun:BN_num_bits_word
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Value4
+   fun:BN_num_bits_word
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Cond
+   fun:BN_mod_exp_mont_word
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Cond
+   fun:BN_mod_exp_mont
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Param
+   write(buf)
+   fun:write
+   obj:/usr/lib/libcrypto.so.0.9.7
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Cond
+   fun:RSA_verify
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Value4
+   fun:RSA_verify
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Value4
+   fun:DES_set_key_unchecked
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Value4
+   fun:DES_encrypt2
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Cond
+   obj:/usr/lib/libssl.so.0.9.7
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Value4
+   obj:/usr/lib/libssl.so.0.9.7
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Cond
+   fun:BUF_MEM_grow_clean
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Cond
+   fun:memcpy
+   fun:ssl3_read_bytes
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Cond
+   fun:SHA1_Update
+}
+
+{
+   from test_socket_ssl
+   Memcheck:Value4
+   fun:SHA1_Update
+}
+
+{
+   test_buffer_non_debug
+   Memcheck:Addr4
+   fun:PyUnicodeUCS2_FSConverter
+}
+
+{
+   test_buffer_non_debug
+   Memcheck:Addr4
+   fun:PyUnicode_FSConverter
+}
+
+{
+   wcscmp_false_positive
+   Memcheck:Addr8
+   fun:wcscmp
+   fun:_PyOS_GetOpt
+   fun:Py_Main
+   fun:main
+}
+
+# Additional suppressions for the unified decimal tests:
+{
+   test_decimal
+   Memcheck:Addr4
+   fun:PyUnicodeUCS2_FSConverter
+}
+
+{
+   test_decimal2
+   Memcheck:Addr4
+   fun:PyUnicode_FSConverter
+}
+
diff --git a/setup.py b/setup.py
index f5c3a5b7..642ad355 100644
--- a/setup.py
+++ b/setup.py
@@ -36,13 +36,17 @@ def build_ext(*args, ** kwargs ):
 #adapted from https://github.com/lh3/minimap2/blob/master/setup.py
 
 sources=['python/pyslow5.pyx', 'src/slow5.c', 'src/slow5_press.c', 'src/slow5_misc.c', 'src/slow5_idx.c',
+            'python/slow5_write.c', 'python/slow5threads.c',
             'thirdparty/streamvbyte/src/streamvbyte_zigzag.c', 'thirdparty/streamvbyte/src/streamvbyte_decode.c', 'thirdparty/streamvbyte/src/streamvbyte_encode.c']
 depends=['python/pyslow5.pxd', 'python/pyslow5.h',
+            'python/slow5_write.h', 'python/slow5threads.h',
             'slow5/slow5.h', 'slow5/slow5_defs.h', 'slow5/slow5_error.h', 'slow5/slow5_press.h',
             'slow5/klib/khash.h', 'slow5/klib/kvec.h',
-            'src/slow5_extra.h', 'src/slow5_idx.h', 'src/slow5_misc.h', 'src/klib/ksort.h' ,
+            'src/slow5_extra.h', 'src/slow5_idx.h', 'src/slow5_misc.h', 'src/klib/ksort.h',
             'thirdparty/streamvbyte/include/streamvbyte.h', 'thirdparty/streamvbyte/include/streamvbyte_zigzag.h']
 extra_compile_args = ['-g', '-Wall', '-O2', '-std=c99']
+# extra_compile_args = []
+# os.environ["CFLAGS"] = '-g -Wall -O2 -std=c99'
 
 arch=platform.machine()
 if arch in ["aarch64", "arm64"]:


Test patch:
diff --git a/python/test.py b/python/test.py
index 87e008e0..a1390a0a 100644
--- a/python/test.py
+++ b/python/test.py
@@ -131,7 +131,7 @@ def test_reads_with_no_exist(self):
                 else:
                     self.assertEqual(read['read_id'], results[i])
 
-class testHeaders(unittest.TestCase):
+class TestHeaders(unittest.TestCase):
     def setUp(self):
         self.s5 = slow5.Open('examples/example.slow5','r', DEBUG=debug)
 
@@ -183,7 +183,7 @@ def test_get_all_headers(self):
                 val = self.s5.get_header_value(attr)
                 self.assertEqual(val, results[i])
 
-class testAuxAll(unittest.TestCase):
+class TestAuxAll(unittest.TestCase):
     def setUp(self):
         self.s5 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
     def test_get_aux_names(self):
@@ -218,7 +218,176 @@ def test_seq_reads_pA_aux_all(self):
                 self.assertEqual(int(sum([round(i, 2) for i in read['signal'][:10]])), int(results[i][1]))
                 self.assertEqual(read['start_time'] ,results[i][2])
 
+class TestWrite(unittest.TestCase):
+    def setUp(self):
+        self.s5 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+        self.F = slow5.Open('examples/example_write.slow5','w', DEBUG=debug)
+    def tearDown(self):
+        self.F.close()
+    def test_get_empty_header(self):
+        results = {"asic_id": None,
+                  "asic_id_eeprom": None,
+                  "asic_temp": None,
+                  "asic_version": None,
+                  "auto_update": None,
+                  "auto_update_source": None,
+                  "barcoding_enabled": None,
+                  "bream_is_standard": None,
+                  "configuration_version": None,
+                  "device_id": None,
+                  "device_type": None,
+                  "distribution_status": None,
+                  "distribution_version": None,
+                  "exp_script_name": None,
+                  "exp_script_purpose": None,
+                  "exp_start_time": None,
+                  "experiment_duration_set": None,
+                  "flow_cell_id": None,
+                  "flow_cell_product_code": None,
+                  "guppy_version": None,
+                  "heatsink_temp": None,
+                  "hostname": None,
+                  "installation_type": None,
+                  "local_basecalling": None,
+                  "operating_system": None,
+                  "package": None,
+                  "protocol_group_id": None,
+                  "protocol_run_id": None,
+                  "protocol_start_time": None,
+                  "protocols_version": None,
+                  "run_id": None,
+                  "sample_frequency": None,
+                  "sample_id": None,
+                  "sequencing_kit": None,
+                  "usb_config": None,
+                  "version": None,
+                  "hublett_board_id": None,
+                  "satellite_firmware_version": None}
+        header = self.F.get_empty_header()
+        for i, head in enumerate(header):
+            with self.subTest(i=i, attr=head):
+                self.assertEqual(header[head], results[head])
+    def test_write_header(self):
+        # This doesn't actually write the header, but adds it to object
+        # Header is actually written when writing first record
+        results = 0
+        header = self.F.get_empty_header()
+        counter = 0
+        for i in header:
+            header[i] = "test_{}".format(counter)
+            counter += 1
+        ret = self.F.write_header(header)
+        self.assertEqual(ret, results)
+    def test_get_empty_record(self):
+        results = {"read_id": None,
+                  "read_group": 0,
+                  "digitisation": None,
+                  "offset": None,
+                  "range": None,
+                  "sampling_rate": None,
+                  "len_raw_signal": None,
+                  "signal": None}
+        record = self.F.get_empty_record()
+        for i, rec in enumerate(record):
+            with self.subTest(i=i, attr=rec):
+                self.assertEqual(record[rec], results[rec])
+    def test_write_records(self):
+        results = [0]*8
+        ret_list = []
+        header = self.F.get_empty_header()
+        counter = 0
+        for i in header:
+            header[i] = "test_{}".format(counter)
+            counter += 1
+        ret = self.F.write_header(header)
+        reads = self.s5.seq_reads()
+        for read in reads:
+            record = self.F.get_empty_record()
+            for i in read:
+                if i in record:
+                    record[i] = read[i]
+            ret = self.F.write_record(record)
+            ret_list.append(ret)
+        self.assertEqual(ret_list, results)
+
+
+class testMultiThreading(unittest.TestCase):
+    def setUp(self):
+        self.s5 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+    def test_seq_reads_multi(self):
+        results = [['r0', 1106.3899999999999, 78470500],
+                   ['r1', 1585.4299999999998, 36886851],
+                   ['r2', 1106.3899999999999, 78470500],
+                   ['r3', 1585.4299999999998, 36886851],
+                   ['r4', 1106.3899999999999, 78470500],
+                   ['r5', 1585.4299999999998, 36886851],
+                   ['0a238451-b9ed-446d-a152-badd074006c4', 1106.3899999999999, 78470500],
+                   ['0d624d4b-671f-40b8-9798-84f2ccc4d7fc', 1585.4299999999998, 36886851]]
+        reads = self.s5.seq_reads_multi(threads=2, batchsize=3, pA=True, aux='all')
+        for i, read in enumerate(reads):
+            with self.subTest(i=i, attr=read['read_id']):
+                print(read['read_id'])
+                self.assertEqual(read['read_id'], results[i][0])
+                self.assertEqual(int(sum([round(i, 2) for i in read['signal'][:10]])), int(results[i][1]))
+                self.assertEqual(read['start_time'] ,results[i][2])
+
 
+class TestWriteData(unittest.TestCase):
+    def setUp(self):
+        self.s5 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+        self.F = slow5.Open('examples/example_write.slow5','r', DEBUG=debug)
+    def test_write_matches_read(self):
+        reads = self.s5.seq_reads()
+        for read in reads:
+            wread = self.F.get_read(read["read_id"])
+            for i, rec in enumerate(wread):
+                # TODO: remove this when read_groups are added
+                with self.subTest(i=i, attr=rec):
+                    if rec == "read_group":
+                        continue
+                    if rec == "signal":
+                        self.assertEqual(int(sum([round(i, 2) for i in wread[rec][:10]])), int(sum([round(i, 2) for i in read[rec][:10]])))
+                    else:
+                        self.assertEqual(wread[rec], read[rec])
+
+
+class TestWriteAux(unittest.TestCase):
+    def setUp(self):
+        self.s5 = slow5.Open('examples/example2.slow5','r', DEBUG=debug)
+        self.F = slow5.Open('examples/example_write_aux.slow5','w', DEBUG=debug)
+    def tearDown(self):
+        self.F.close()
+    def test_get_empty_aux(self):
+        results = {"channel_number": None,
+               "median_before": None,
+               "read_number": None,
+               "start_mux": None,
+               "start_time": None,
+               "end_reason": None}
+        _, aux = self.F.get_empty_record(aux=True)
+        for i, rec in enumerate(aux):
+            with self.subTest(i=i, attr=rec):
+                self.assertEqual(aux[rec], results[rec])
+    def test_write_records_aux(self):
+        results = [0]*8
+        ret_list = []
+        header = self.F.get_empty_header()
+        counter = 0
+        for i in header:
+            header[i] = "test_{}".format(counter)
+            counter += 1
+        ret = self.F.write_header(header)
+        reads = self.s5.seq_reads(aux='all')
+        for read in reads:
+            record, aux = self.F.get_empty_record(aux=True)
+            for i in read:
+                if i in record:
+                    record[i] = read[i]
+                if i in aux:
+                    aux[i] = read[i]
+            ret = self.F.write_record(record, aux)
+            ret_list.append(ret)
+        self.assertEqual(ret_list, results)
 
 
 # def test_bad_type(self):