From 0ff09a5e419f254e2500217f5c1c16bd10b23330 Mon Sep 17 00:00:00 2001 From: Chris Nuernberger Date: Tue, 2 Mar 2021 08:36:25 -0700 Subject: [PATCH] V2 (#147) * Carefully working through initialization. * jdk-16 testing :-) * Working through ABI issues. * Adding 32bit analysis * Starting down 32bit compat testing. * Adding type kwd caching. * Lots of work bringing the system up. * Updating dtype-next and have 'dir' working * Simple dict working. * Unpacking dicts and lists,tuples is working. * Error handling. * Most of copying done and working on function bridging. * defining python functions somewhat works. * Started work on bridging. * Lots of fixes and bridging *from* python works atm. * Lots additions pulling the good parts of bridging over. * Starting to carry tests across. * Fixing initialization so loading local modules works. * Unit tests passing again :-). * Have just a couple more tests. * All tests pass :-). * v2 passes unit tests. * Fix leaking tuple refcount && avoid looking up atts we don't use. * Re-working how custom classes work. * Major refactoring to greatly reduce GC interactions. * Attempting to get more test feedback * Turned on vastly more logging... * Adding single test and working from there. * test-golf * Fixing unittest issues. * Adding a few more tests back. * The big daddy, require-python. If this passes we are doing quite well. * Final bit of tests. * Disable debug tracing * Carrying perf enhancements across from v1. * Commenting namespaces --- .gitignore | 4 +- .travis.yml | 3 + docs/Usage.html | 2 +- docs/design.html | 2 +- docs/index.html | 2 +- docs/libpython-clj2.python.html | 63 + docs/libpython-clj2.python.np-array.html | 4 + docs/new-to-clojure.html | 2 +- docs/scopes-and-gc.html | 2 +- docs/slicing.html | 2 +- project.clj | 16 +- questions/32bit.txt | 13 + questions/64bit.txt | 17 + questions/compile32.sh | 1 + questions/compile64.sh | 1 + questions/typeobject.cpp | 23 + src/libpython_clj/export_module_symbols.clj | 29 - src/libpython_clj/jna.clj | 421 ------ src/libpython_clj/jna/base.clj | 115 -- src/libpython_clj/jna/concrete/bytes.clj | 109 -- src/libpython_clj/jna/concrete/cfunction.clj | 76 -- src/libpython_clj/jna/concrete/code.clj | 33 - src/libpython_clj/jna/concrete/dict.clj | 235 ---- src/libpython_clj/jna/concrete/err.clj | 337 ----- src/libpython_clj/jna/concrete/import.clj | 130 -- src/libpython_clj/jna/concrete/list.clj | 128 -- src/libpython_clj/jna/concrete/module.clj | 124 -- .../jna/concrete/numeric/boolean.clj | 38 - .../jna/concrete/numeric/complex.clj | 57 - .../jna/concrete/numeric/float.clj | 65 - .../jna/concrete/numeric/integer.clj | 100 -- src/libpython_clj/jna/concrete/set.clj | 92 -- src/libpython_clj/jna/concrete/tuple.clj | 59 - src/libpython_clj/jna/concrete/type.clj | 137 -- src/libpython_clj/jna/concrete/unicode.clj | 87 -- src/libpython_clj/jna/interpreter.clj | 400 ------ src/libpython_clj/jna/protocols/buffer.clj | 108 -- src/libpython_clj/jna/protocols/iterator.clj | 31 - src/libpython_clj/jna/protocols/mapping.clj | 118 -- src/libpython_clj/jna/protocols/object.clj | 409 ------ src/libpython_clj/jna/protocols/sequence.clj | 178 --- src/libpython_clj/py_modules/numpy.clj | 15 - src/libpython_clj/python.clj | 539 -------- src/libpython_clj/python/bridge.clj | 1129 ---------------- src/libpython_clj/python/gc.clj | 67 - src/libpython_clj/python/interop.clj | 444 ------- src/libpython_clj/python/interpreter.clj | 532 -------- src/libpython_clj/python/logging.clj | 35 - src/libpython_clj/python/np_array.clj | 114 -- src/libpython_clj/python/object.clj | 1133 ----------------- src/libpython_clj/python/protocols.clj | 182 --- .../metadata.clj | 92 +- src/libpython_clj2/python.clj | 575 +++++++++ src/libpython_clj2/python/base.clj | 190 +++ src/libpython_clj2/python/bridge_as_jvm.clj | 435 +++++++ .../python/bridge_as_python.clj | 252 ++++ src/libpython_clj2/python/class.clj | 155 +++ src/libpython_clj2/python/copy.clj | 328 +++++ src/libpython_clj2/python/dechunk_map.clj | 11 + src/libpython_clj2/python/ffi.clj | 883 +++++++++++++ src/libpython_clj2/python/fn.clj | 218 ++++ src/libpython_clj2/python/gc.clj | 85 ++ src/libpython_clj2/python/info.clj | 107 ++ src/libpython_clj2/python/io_redirect.clj | 55 + src/libpython_clj2/python/jvm_handle.clj | 69 + src/libpython_clj2/python/np_array.clj | 198 +++ src/libpython_clj2/python/protocols.clj | 91 ++ .../python/windows.clj | 10 +- src/libpython_clj2/python/with.clj | 63 + .../require.clj | 54 +- .../sugar.clj | 6 +- test/libpython_clj/classes_test.clj | 52 - test/libpython_clj2/classes_test.clj | 42 + test/libpython_clj2/ffi_test.clj | 36 + .../fncall_test.clj | 11 +- .../iter_gen_seq_test.clj | 6 +- .../python => libpython_clj2}/numpy_test.clj | 7 +- .../python_test.clj | 116 +- .../require_python_test.clj | 14 +- .../stress_test.clj | 56 +- .../sugar_test.clj | 7 +- 81 files changed, 4127 insertions(+), 8060 deletions(-) create mode 100644 docs/libpython-clj2.python.html create mode 100644 docs/libpython-clj2.python.np-array.html create mode 100644 questions/32bit.txt create mode 100644 questions/64bit.txt create mode 100755 questions/compile32.sh create mode 100755 questions/compile64.sh delete mode 100644 src/libpython_clj/export_module_symbols.clj delete mode 100644 src/libpython_clj/jna.clj delete mode 100644 src/libpython_clj/jna/base.clj delete mode 100644 src/libpython_clj/jna/concrete/bytes.clj delete mode 100644 src/libpython_clj/jna/concrete/cfunction.clj delete mode 100644 src/libpython_clj/jna/concrete/code.clj delete mode 100644 src/libpython_clj/jna/concrete/dict.clj delete mode 100644 src/libpython_clj/jna/concrete/err.clj delete mode 100644 src/libpython_clj/jna/concrete/import.clj delete mode 100644 src/libpython_clj/jna/concrete/list.clj delete mode 100644 src/libpython_clj/jna/concrete/module.clj delete mode 100644 src/libpython_clj/jna/concrete/numeric/boolean.clj delete mode 100644 src/libpython_clj/jna/concrete/numeric/complex.clj delete mode 100644 src/libpython_clj/jna/concrete/numeric/float.clj delete mode 100644 src/libpython_clj/jna/concrete/numeric/integer.clj delete mode 100644 src/libpython_clj/jna/concrete/set.clj delete mode 100644 src/libpython_clj/jna/concrete/tuple.clj delete mode 100644 src/libpython_clj/jna/concrete/type.clj delete mode 100644 src/libpython_clj/jna/concrete/unicode.clj delete mode 100644 src/libpython_clj/jna/interpreter.clj delete mode 100644 src/libpython_clj/jna/protocols/buffer.clj delete mode 100644 src/libpython_clj/jna/protocols/iterator.clj delete mode 100644 src/libpython_clj/jna/protocols/mapping.clj delete mode 100644 src/libpython_clj/jna/protocols/object.clj delete mode 100644 src/libpython_clj/jna/protocols/sequence.clj delete mode 100644 src/libpython_clj/py_modules/numpy.clj delete mode 100644 src/libpython_clj/python.clj delete mode 100644 src/libpython_clj/python/bridge.clj delete mode 100644 src/libpython_clj/python/gc.clj delete mode 100644 src/libpython_clj/python/interop.clj delete mode 100644 src/libpython_clj/python/interpreter.clj delete mode 100644 src/libpython_clj/python/logging.clj delete mode 100644 src/libpython_clj/python/np_array.clj delete mode 100644 src/libpython_clj/python/object.clj delete mode 100644 src/libpython_clj/python/protocols.clj rename src/{libpython_clj => libpython_clj2}/metadata.clj (84%) create mode 100644 src/libpython_clj2/python.clj create mode 100644 src/libpython_clj2/python/base.clj create mode 100644 src/libpython_clj2/python/bridge_as_jvm.clj create mode 100644 src/libpython_clj2/python/bridge_as_python.clj create mode 100644 src/libpython_clj2/python/class.clj create mode 100644 src/libpython_clj2/python/copy.clj create mode 100644 src/libpython_clj2/python/dechunk_map.clj create mode 100644 src/libpython_clj2/python/ffi.clj create mode 100644 src/libpython_clj2/python/fn.clj create mode 100644 src/libpython_clj2/python/gc.clj create mode 100644 src/libpython_clj2/python/info.clj create mode 100644 src/libpython_clj2/python/io_redirect.clj create mode 100644 src/libpython_clj2/python/jvm_handle.clj create mode 100644 src/libpython_clj2/python/np_array.clj create mode 100644 src/libpython_clj2/python/protocols.clj rename src/{libpython_clj => libpython_clj2}/python/windows.clj (83%) create mode 100644 src/libpython_clj2/python/with.clj rename src/{libpython_clj => libpython_clj2}/require.clj (92%) rename src/{libpython_clj => libpython_clj2}/sugar.clj (90%) delete mode 100644 test/libpython_clj/classes_test.clj create mode 100644 test/libpython_clj2/classes_test.clj create mode 100644 test/libpython_clj2/ffi_test.clj rename test/{libpython_clj => libpython_clj2}/fncall_test.clj (82%) rename test/{libpython_clj => libpython_clj2}/iter_gen_seq_test.clj (83%) rename test/{libpython_clj/python => libpython_clj2}/numpy_test.clj (79%) rename test/{libpython_clj => libpython_clj2}/python_test.clj (79%) rename test/{libpython_clj => libpython_clj2}/require_python_test.clj (93%) rename test/{libpython_clj => libpython_clj2}/stress_test.clj (74%) rename test/{libpython_clj => libpython_clj2}/sugar_test.clj (93%) diff --git a/.gitignore b/.gitignore index 217fc6e..8cf6946 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ pom.xml.asc .hgignore .hg/ __pycache__ -.cpcache/ \ No newline at end of file +.cpcache/ +cpython +a.out \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 3883953..bad2cf7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,3 +14,6 @@ addons: apt: update: true install: clojure -Sdescribe +cache: + directories: + - "$HOME/.m2" diff --git a/docs/Usage.html b/docs/Usage.html index b205016..63a8655 100644 --- a/docs/Usage.html +++ b/docs/Usage.html @@ -1,6 +1,6 @@ -Usage

Usage

+Usage

Usage

Python objects are essentially two dictionaries, one for ‘attributes’ and one for ‘items’. When you use python and use the ‘.’ operator, you are referencing attributes. If you use the ‘[]’ operator, then you are referencing items. Attributes are built in, item access is optional and happens via the __getitem__ and __setitem__ attributes. This is important to realize in that the code below doesn’t look like python because we are referencing the item and attribute systems by name and not via ‘.’ or ‘[]’.

This would result in the following analogous code (full example further on):

table.loc[row_date]
diff --git a/docs/design.html b/docs/design.html
index 085c058..13bca35 100644
--- a/docs/design.html
+++ b/docs/design.html
@@ -1,6 +1,6 @@
 
-LibPython-CLJ Design Notes

LibPython-CLJ Design Notes

+LibPython-CLJ Design Notes

LibPython-CLJ Design Notes

Key Design Points

Code Organization

There are 3 rough sections of code: 1. A JNA layer which is a 1-1 mapping most of the C API with no changes and full documentation. The docstrings on the functions match the documentation if you lookup the 3.7 API documentation. Users must manually manage the GIL when using this API layer.

diff --git a/docs/index.html b/docs/index.html index 465e531..53464ed 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,3 +1,3 @@ -libpython-clj 2.00-alpha-7

libpython-clj 2.00-alpha-7

Released under the Eclipse Public License

libpython bindings for Clojure.

Installation

To install, add the following dependency to your project or build file:

[clj-python/libpython-clj "2.00-alpha-7"]

Topics

Namespaces

libpython-clj.python.np-array

Bindings for deeper intergration of numpy into the tech.v3.datatype system. This allows somewhat more seamless usage of numpy arrays in datatype and tensor functionality such as enabling the tech.v3.tensor/ensure-tensor call to work with numpy arrays (as zero copying when possible).

Public variables and functions:

libpython-clj.require

Namespace implementing requiring python modules as Clojure namespaces. This works via scanning the module for metadata and dynamically building the Clojure namespace.

Public variables and functions:

\ No newline at end of file +libpython-clj 2.00-alpha-8-SNAPSHOT

libpython-clj 2.00-alpha-8-SNAPSHOT

Released under the Eclipse Public License

libpython bindings for Clojure.

Installation

To install, add the following dependency to your project or build file:

[clj-python/libpython-clj "2.00-alpha-8-SNAPSHOT"]

Topics

Namespaces

libpython-clj2.python.np-array

Bindings for deeper intergration of numpy into the tech.v3.datatype system. This allows somewhat more seamless usage of numpy arrays in datatype and tensor functionality such as enabling the tech.v3.tensor/ensure-tensor call to work with numpy arrays (as zero copying when possible).

\ No newline at end of file diff --git a/docs/libpython-clj2.python.html b/docs/libpython-clj2.python.html new file mode 100644 index 0000000..e5dfadf --- /dev/null +++ b/docs/libpython-clj2.python.html @@ -0,0 +1,63 @@ + +libpython-clj2.python documentation

libpython-clj2.python

$a

macro

($a item attr & args)

Call an attribute of an object using automatic detection of the python kwargs. Keywords must be compile time constants. So this won’t work with ‘apply’. On the other hand, building the positional and kw argmaps happens at compile time as opposed to at runtime. The attr name can be a symbol.

$c

macro

($c item & args)

Call an object using automatic detection of the python kwargs. Keywords must be compile time constants. So this won’t work with ‘apply’. On the other hand, building the positional and kw argmaps happens at compile time as opposed to at runtime.

->jvm

(->jvm v & [opts])

Copy a python value into java datastructures

->py-dict

(->py-dict v)

Copy v into a python dict

->py-list

(->py-list v)

Copy the data into a python list

->py-tuple

(->py-tuple v)

Copy v into a python tuple

->python

(->python v)

Copy a jvm value into a python object

add-module

(add-module modname)

Add a python module. This can create a module if it doesn’t exist.

afn

(afn item attr & args)

Call an attribute of an object. Arguments are passed in positionally. Any keyword arguments are paired with the next arg, gathered, and passed into the system as *kwargs.

+

Not having an argument after a keyword is an error.

as-jvm

(as-jvm v & [opts])

Copy a python value into java datastructures

as-list

(as-list pobj)

Make a python object appear as a list

as-map

(as-map pobj)

Make a python object appear as a map of it’s items

as-python

(as-python v)

Bridge a jvm value into a python object

call-attr

(call-attr pyobj attname & args)

Call an attribute on a python object using only positional arguments

call-attr-kw

(call-attr-kw pyobj attname args kw-list)

Call an attribute passing in both positional and keyword arguments.

callable?

(callable? pyobj)

Return true if python object is callable.

cfn

(cfn item & args)

Call an object. Arguments are passed in positionally. Any keyword arguments are paired with the next arg, gathered, and passed into the system as *kwargs.

+

Not having an argument after a keyword argument is an error.

create-class

(create-class name bases cls-hashmap)

Create a new class object. Any callable values in the cls-hashmap will be presented as instance methods. If you want access to the ‘this’ object then you must use make-instance-fn.

+

Example:

+
user> (require '[libpython-clj2.python :as py])
+nil
+user> (def cls-obj (py/create-class
+                    "myfancyclass"
+                    nil
+                    {"__init__" (py/make-instance-fn
+                                 (fn [this arg]
+                                   (py/set-attr! this "arg" arg)
+                                   ;;If you don't return nil from __init__ that is an
+                                   ;;error.
+                                   nil))
+                     "addarg" (py/make-instance-fn
+                               (fn [this otherarg]
+                                 (+ (py/get-attr this "arg")
+                                    otherarg)))}))
+#'user/cls-obj
+user> cls-obj
+__no_module__.myfancyclass
+user> (def inst (cls-obj 10))
+#'user/inst
+user> (py/call-attr inst "addarg" 10)
+20
+

dir

(dir pyobj)

get-attr

(get-attr pyobj attname)

Get an attribute from a python object

get-item

(get-item pyobj item-name)

Get an item from a python object using getitem

has-attr?

(has-attr? pyobj att-name)

Return true if this python object has this attribute.

import-module

(import-module modname)

Import a python module returning an implementation of java.util.Map wrapping the module object and consisting of module attributes.

initialize!

(initialize! & [{:keys [windows-anaconda-activate-bat library-path no-io-redirect?]} options])

Initialize the python library. If library path is not provided, then the system attempts to execute a simple python program and have python return system info.

+

Returns either :ok in which case the initialization completed successfully or :already-initialized in which case we detected that python has already been initialized via Py_IsInitialized and we do nothing more.

+

Options:

+
    +
  • :library-path - Library path of the python library to use.
  • +
  • :program-name - Optional – will show up in error messages from python.
  • +
  • :no-io-redirect? - True if you don’t want python stdout and stderr redirection to out and err.
  • +
  • :python-executable - The python executable to use to find system information.
  • +
  • :python-home - Python home directory. The system first uses this variable, then the environment variable PYTHON_HOME, and finally information returned from python system info.
  • +
  • :signals? - defaults to false - true if you want python to initialized signals. Be aware that the JVM itself uses quite a few signals - SIGSEGV, for instance - during it’s normal course of operation. For more information see: + +
  • +

is-instance?

(is-instance? py-inst py-cls)

Return true if inst is an instance of cls. Note that arguments are reversed as compared to instance?

make-callable

(make-callable ifn options)(make-callable ifn)

Make a python callable object from a clojure function. This is called for you if you use as-python on an implementation of IFn.

+

Options: * :arg-converter - Function called for each function argument before your ifn gets access to it. Defaults to ->jvm. * :result-converter - Function called on return value before it gets returned to python. Must return a python object. Defaults to ->python; the result will get an extra incref before being returned to Python to account for the implied tracking of as-python or ->python. * :name - Name of the python method. This will appear in stack traces. * :doc - documentation for method.

make-instance-fn

(make-instance-fn ifn options)(make-instance-fn ifn)

Make an callable instance function - a function which will be passed the ‘this’ object as it’s first argument. In addition, this function calls make-callable with a arg-converter defaulted to as-jvm. See documentation for make-callable.

module-dict

(module-dict mod)

Get the module dictionary.

py*

macro

(py* x method args)(py* x method args kwargs)

Special syntax for passing along *args and **kwargs style arguments to methods.

+

Usage:

+

(py* obj method args kwargs)

+

Example:

+

(def d (python/dict)) d ;;=> {} (def iterable :a 1] [:b 2) (def kwargs {:cat “dog” :name “taco”}) (py* d update [iterable] kwargs) d ;;=> {“a”: 1, “b”: 2, “cat”: “dog”, “name”: “taco”}

py**

macro

(py** x method kwargs)(py** x method arg & args)

Like py*, but it is assumed that the LAST argument is kwargs.

py.

macro

(py. x & args)

Class/object method syntax. (py. obj method arg1 arg2 … argN) is equivalent to Python’s obj.method(arg1, arg2, …, argN) syntax.

py.-

macro

(py.- x arg)

Class/object getter syntax. (py.- obj attr) is equivalent to Python’s obj.attr syntax.

py..

macro

(py.. x & args)

Extended accessor notation, similar to the .. macro in Clojure.

+

(require-python ’sys) (py.. sys -path (append “/home/user/bin”))

+

is equivalent to Python’s

+

import sys sys.path.append(‘/home/user/bin’)

+

SPECIAL SYNTAX for programmatic *args and **kwargs

+

Special syntax is provided to meet the needs required by Python’s *args and **kwargs syntax programmatically.

+

(= (py.. obj (*method args)) (py* obj methods args))

+

(= (py.. obj (*method args kwargs)) (py* obj method args kwargs))

+

(= (py.. obj (**method kwargs)) (py** obj method kwargs))

+

(= (py.. obj (**method arg1 arg2 arg3 … argN kwargs)) (py** obj method arg1 arg2 arg3 … argN kwargs) (py* obj method [arg1 arg2 arg3 … argN] kwargs))

+

These forms exist for when you need to pass in a map of options in the same way you would use the f(*args, **kwargs) forms in Python.

python-type

(python-type v)

Get the type (as a keyword) of a python object

run-simple-string

(run-simple-string program & {:keys [globals locals]})

Run a string expression returning a map of {:globals :locals}. This uses the global main dict under the covers so it matches the behavior of the cpython implementation with the exception of returning the various maps used.

+

Note this will never return the result of the expression: https://mail.python.org/pipermail/python-list/1999-April/018011.html

+

Globals, locals may be provided but are not necessary.

+

Implemented in cpython as:

+

PyObject *m, *d, *v; m = PyImport_AddModule(“main”); if (m == NULL) return -1; d = PyModule_GetDict(m); v = PyRun_StringFlags(command, Py_file_input, d, d, flags); if (v == NULL) { PyErr_Print(); return -1; } Py_DECREF(v); return 0;

set-attr!

(set-attr! pyobj attname attval)

Set an attribute on a python object. Returns pyobj.

set-attrs!

(set-attrs! pyobj att-seq)

Set a sequence of [name value] attributes. Returns pyobj.

set-item!

(set-item! pyobj item-name item-val)

Get an item from a python object using setitem

set-items!

(set-items! pyobj item-seq)

Set a sequence of [name value]. Returns pyobj

stack-resource-context

macro

(stack-resource-context & body)

Create a stack-based resource context. All python objects allocated within this context will be released at the termination of this context. !!This means that no python objects can escape from this context!! You must use copy semantics (->jvm) for anything escaping this context. Furthermore, if you are returning generic python objects you may need to call (into {}) or something like that just to ensure that absolutely everything is copied into the jvm.

with

macro

(with bind-vec & body)

Support for the ‘with’ statement in python: (py/with [item (py/call-attr testcode-module “WithObjClass” true fn-list)] (py/call-attr item “doit_err”))

with-gil

macro

(with-gil & body)

Capture the gil for an extended amount of time. This can greatly speed up operations as the mutex is captured and held once as opposed to find grained grabbing/releasing of the mutex.

with-gil-stack-rc-context

macro

(with-gil-stack-rc-context & body)

Capture the gil, open a resource context. The resource context is released before the gil is leading to much faster resource collection. See documentation on stack-resource-context for multiple warnings; the most important one being that if a python object escapes this context your program will eventually, at some undefined point in the future crash. That being said, this is the recommended pathway to use in production contexts where you want defined behavior and timings related to use of python.

\ No newline at end of file diff --git a/docs/libpython-clj2.python.np-array.html b/docs/libpython-clj2.python.np-array.html new file mode 100644 index 0000000..d1a5aef --- /dev/null +++ b/docs/libpython-clj2.python.np-array.html @@ -0,0 +1,4 @@ + +libpython-clj2.python.np-array documentation

libpython-clj2.python.np-array

Bindings for deeper intergration of numpy into the tech.v3.datatype system. This allows somewhat more seamless usage of numpy arrays in datatype and tensor functionality such as enabling the tech.v3.tensor/ensure-tensor call to work with numpy arrays (as zero copying when possible).

+

All users need to do is call require this namespace; then as-jvm will convert a numpy array into a tech tensor in-place.

datatype->ptr-type-name

(datatype->ptr-type-name dtype)

descriptor->numpy

(descriptor->numpy {:keys [ptr shape strides elemwise-datatype], :as buffer-desc})

dtype->py-dtype-map

numpy->desc

(numpy->desc np-obj)

obj-dtype->dtype

(obj-dtype->dtype py-dtype)

py-dtype->dtype-map

\ No newline at end of file diff --git a/docs/new-to-clojure.html b/docs/new-to-clojure.html index 25f8226..29f7a7d 100644 --- a/docs/new-to-clojure.html +++ b/docs/new-to-clojure.html @@ -1,6 +1,6 @@ -So Many Parenthesis!

So Many Parenthesis!

+So Many Parenthesis!

So Many Parenthesis!

About Clojure

LISP stands for List Processing and it was originally designed by John McCarthy around 1958. It was the first language with a garbage collector making it the first truly high level language assuming you don’t consider Fortran a high level language. Here is Dr. McCarthy’s seminal paper and for a much better intro than I can give please see here.

Time passed and along came a man named Rich Hickey. Making a long story short, Rich was working in a variety of languages such as C++, Java, and C# when he did a project in Common Lisp and was hooked. There are many YouTube videos and documents that Rich has produced but simple made easy is one I found very compelling. If you enjoy that video, don’t stop there; Rich has many interesting, profound, and sometimes provocative things to add to the conversation. For more about his reasoning behind Clojure, please check out his rationale.

diff --git a/docs/scopes-and-gc.html b/docs/scopes-and-gc.html index 14b514e..1495a8b 100644 --- a/docs/scopes-and-gc.html +++ b/docs/scopes-and-gc.html @@ -1,6 +1,6 @@ -Scopes And Garbage Collection

Scopes And Garbage Collection

+Scopes And Garbage Collection

Scopes And Garbage Collection

libpython-clj now supports stack-based scoping rules so you can guarantee all python objects created during a section of code will be released by a certain point.

Using the stack-based scoping looks like:

user> (require '[libpython-clj.python :as py])
diff --git a/docs/slicing.html b/docs/slicing.html
index 8019215..b3088e9 100644
--- a/docs/slicing.html
+++ b/docs/slicing.html
@@ -1,6 +1,6 @@
 
-Slicing And Slices

Slicing And Slices

+Slicing And Slices

Slicing And Slices

The way Python implements slicing is via overloading the get-item function call. This is the call the Python interpreter makes under the covers whenever you use the square bracket [] syntax.

For quite a few objects, that function call take a tuple of arguments. The trick to numpy slicing is to create builtin slice objects with the appropriate arguments and pass them into the get-item call in a tuple.

user> (require '[libpython-clj.python :as py])
diff --git a/project.clj b/project.clj
index 1beae34..a65f78c 100644
--- a/project.clj
+++ b/project.clj
@@ -5,10 +5,14 @@
             :url  "http://www.eclipse.org/legal/epl-v10.html"}
   :dependencies [[org.clojure/clojure    "1.10.2" :scope "provided"]
                  [camel-snake-kebab      "0.4.0"]
-                 [cnuernber/dtype-next   "6.00"]
-                 [techascent/tech.jna    "4.04"]
+                 [cnuernber/dtype-next   "6.05"]
+                 [net.java.dev.jna/jna "5.7.0"]
                  [org.clojure/data.json  "1.0.0"]]
-  :profiles {:dev {:dependencies [[criterium "0.4.5"]]}
+  :profiles {:dev {:dependencies [[criterium "0.4.5"]
+                                  [ch.qos.logback/logback-classic "1.1.3"]]}
+             :jdk-16 {:jvm-opts ["--add-modules" "jdk.incubator.foreign"
+                                 "-Dforeign.restricted=permit"
+                                 "-Djava.library.path=/usr/lib/x86_64-linux-gnu"]}
              :codox
              {:dependencies [[codox-theme-rdash "0.1.2"]]
               :plugins [[lein-codox "0.10.7"]]
@@ -19,9 +23,9 @@
                       :output-path "docs"
                       :doc-paths ["topics"]
                       :source-uri "https://github.com/clj-python/libpython-clj/blob/master/{filepath}#L{line}"
-                      :namespaces [libpython-clj.python
-                                   libpython-clj.require
-                                   libpython-clj.python.np-array]}}}
+                      :namespaces [libpython-clj2.python
+                                   libpython-clj2.require
+                                   libpython-clj2.python.np-array]}}}
   :java-source-paths ["java"]
   :aliases {"codox" ["with-profile" "codox,dev" "codox"]}
 
diff --git a/questions/32bit.txt b/questions/32bit.txt
new file mode 100644
index 0000000..ae5615d
--- /dev/null
+++ b/questions/32bit.txt
@@ -0,0 +1,13 @@
+PyObject size: 8
+PyTypeObject size: 204
+type.tp_basicsize: 16
+type.tp_as_number: 48
+type.tp_as_buffer: 80
+type.tp_finalize: 196
+py_hash_t: 4
+Py_TPFLAGS_DEFAULT: 262144
+PyObject details
+object size: 8
+ob_refcnt offset: 0
+ob_type   offset: 4
+gilstate size: 4
diff --git a/questions/64bit.txt b/questions/64bit.txt
new file mode 100644
index 0000000..de4ccfc
--- /dev/null
+++ b/questions/64bit.txt
@@ -0,0 +1,17 @@
+PyObject size: 16
+PyTypeObject size: 408
+type.tp_basicsize: 32
+type.tp_as_number: 96
+type.tp_as_buffer: 160
+type.tp_finalize: 392
+py_hash_t: 8
+Py_TPFLAGS_DEFAULT: 262144
+PyObject details
+object size: 16
+ob_refcnt offset: 0
+ob_type   offset: 8
+gilstate size: 4
+PyMethodDef details
+object size: 32
+ml_flags offset: 16
+ml_doc   offset: 24
diff --git a/questions/compile32.sh b/questions/compile32.sh
new file mode 100755
index 0000000..8b9e315
--- /dev/null
+++ b/questions/compile32.sh
@@ -0,0 +1 @@
+g++ typeobject.cpp -I../cpython/Include -I../cpython/32bit
diff --git a/questions/compile64.sh b/questions/compile64.sh
new file mode 100755
index 0000000..c989f98
--- /dev/null
+++ b/questions/compile64.sh
@@ -0,0 +1 @@
+g++ typeobject.cpp -I../cpython/Include -I../cpython/64bit
diff --git a/questions/typeobject.cpp b/questions/typeobject.cpp
index f5b5a2b..c837361 100644
--- a/questions/typeobject.cpp
+++ b/questions/typeobject.cpp
@@ -26,4 +26,27 @@ int main(int c, char** v)
    sizeof(Py_hash_t),
    Py_TPFLAGS_DEFAULT
     );
+
+
+  printf(
+    "PyObject details\n"
+    "object size: %ld\n"
+    "ob_refcnt offset: %ld\n"
+    "ob_type   offset: %ld\n"
+    "gilstate size: %ld\n",
+    sizeof(PyObject),
+    offsetof(PyObject, ob_refcnt),
+    offsetof(PyObject, ob_type),
+    sizeof(PyGILState_STATE));
+
+
+    printf(
+    "PyMethodDef details\n"
+    "object size: %ld\n"
+    "ml_flags offset: %ld\n"
+    "ml_doc   offset: %ld\n",
+    sizeof(PyMethodDef),
+    offsetof(PyMethodDef, ml_flags),
+    offsetof(PyMethodDef, ml_doc));
+
 }
diff --git a/src/libpython_clj/export_module_symbols.clj b/src/libpython_clj/export_module_symbols.clj
deleted file mode 100644
index b6e48c4..0000000
--- a/src/libpython_clj/export_module_symbols.clj
+++ /dev/null
@@ -1,29 +0,0 @@
-(ns libpython-clj.export-module-symbols
-  "A macro that will export all the symbols from a python module and make
-  them functions in the current clojure namespace.
-
-  DEPRECATED - please use 'libpython-clj.require/require-python"
-  (:require [libpython-clj.python :as py]))
-
-
-(def primitive-types #{:float :int :bool :string})
-
-
-(defmacro export-module-symbols
-  "DEPRECATED - please use 'libpython-clj.require/require-python"
-  [py-mod-name]
-  (py/initialize!)
-  (let [mod-data (py/import-module py-mod-name)]
-    `(do ~@(->> (py/att-type-map mod-data)
-                (map (fn [[att-name att-type]]
-                       (let [att-value (py/get-attr mod-data att-name)
-                             doc-str (if (contains? primitive-types att-type)
-                                       (str py-mod-name "." att-value)
-                                       (try
-                                         (py/get-attr att-value "__doc__")
-                                         (catch Throwable e "")))]
-                         `(def ~(with-meta (symbol att-name)
-                                  {:doc doc-str
-                                   :py-module py-mod-name})
-                            (-> (py/import-module ~py-mod-name)
-                                (py/get-attr ~att-name))))))))))
diff --git a/src/libpython_clj/jna.clj b/src/libpython_clj/jna.clj
deleted file mode 100644
index 241ce7b..0000000
--- a/src/libpython_clj/jna.clj
+++ /dev/null
@@ -1,421 +0,0 @@
-(ns libpython-clj.jna
-  (:require [tech.v3.datatype.export-symbols :refer [export-symbols]]
-            [libpython-clj.jna.base :as libpy-base]
-            [libpython-clj.jna.interpreter :as jnainterp]
-            [libpython-clj.jna.protocols.object]
-            [libpython-clj.jna.protocols.iterator]
-            [libpython-clj.jna.protocols.sequence]
-            [libpython-clj.jna.protocols.mapping]
-            [libpython-clj.jna.protocols.buffer]
-            [libpython-clj.jna.concrete.numeric.integer]
-            [libpython-clj.jna.concrete.numeric.float]
-            [libpython-clj.jna.concrete.numeric.boolean]
-            [libpython-clj.jna.concrete.numeric.complex]
-            [libpython-clj.jna.concrete.tuple]
-            [libpython-clj.jna.concrete.list]
-            [libpython-clj.jna.concrete.set]
-            [libpython-clj.jna.concrete.dict]
-            [libpython-clj.jna.concrete.unicode]
-            [libpython-clj.jna.concrete.cfunction]
-            [libpython-clj.jna.concrete.import]
-            [libpython-clj.jna.concrete.module]
-            [libpython-clj.jna.concrete.type]
-            [libpython-clj.jna.concrete.err])
-  (:import [com.sun.jna Pointer NativeLibrary]))
-
-
-(export-symbols libpython-clj.jna.base
-                find-pylib-symbol
-                as-pyobj
-                ensure-pyobj)
-
-
-(export-symbols libpython-clj.jna.interpreter
-                PySys_SetArgv
-                Py_SetProgramName
-                Py_SetPythonHome
-                Py_InitializeEx
-                Py_IsInitialized
-                Py_FinalizeEx
-                PyRun_SimpleString
-                get-type-name
-                lookup-type-symbols
-                Py_None
-                Py_NotImplemented
-                PyMem_Free
-                PyGILState_Ensure
-                PyGILState_Release
-                PyGILState_Check
-                PyEval_RestoreThread
-                PyEval_SaveThread
-                PyThreadState_Get
-                PyThreadState_Swap
-                PyRun_String
-                PyRun_StringFlags)
-
-
-(defmacro ^:private export-type-symbols
-  []
-  `(do
-     ~@(->> (vals jnainterp/type-symbol-table)
-            (map (fn [sym-name]
-                   `(defn ~(symbol sym-name)
-                      ~(format "%s type object" sym-name)
-                      []
-                      (libpy-base/find-pylib-symbol ~sym-name)))))))
-
-
-(export-type-symbols)
-
-
-(export-symbols libpython-clj.jna.protocols.object
-                Py_DecRef
-                Py_IncRef
-                PyObject_Type
-                PyObject_Repr
-                PyObject_Str
-                PyObject_HasAttr
-                PyObject_HasAttrString
-                PyObject_GetAttr
-                PyObject_GetAttrString
-                PyObject_GenericGetAttr
-                PyObject_SetAttr
-                PyObject_SetAttrString
-                PyObject_GenericSetAttr
-                PyObject_DelAttr
-                PyObject_DelAttrString
-                PyObject_GenericGetDict
-                PyObject_GenericSetDict
-                PyObject_RichCompare
-                PyObject_RichCompareBool
-                PyCallable_Check
-                PyObject_Call
-                PyObject_CallObject
-                PyObject_Hash
-                PyObject_IsInstance
-                PyObject_IsTrue
-                PyObject_Not
-                PyObject_Length
-                PyObject_GetItem
-                PyObject_SetItem
-                PyObject_DelItem
-                PyObject_Dir
-                PyObject_GetIter)
-
-
-(export-symbols libpython-clj.jna.protocols.iterator
-                PyIter_Check
-                PyIter_Next)
-
-
-(export-symbols libpython-clj.jna.protocols.sequence
-                PySequence_Check
-                PySequence_Concat
-                PySequence_Contains
-                PySequence_Count
-                PySequence_DelItem
-                PySequence_DelSlice
-                PySequence_GetItem
-                PySequence_GetSlice
-                PySequence_InPlaceConcat
-                PySequence_InPlaceRepeat
-                PySequence_Index
-                PySequence_Length
-                PySequence_List
-                PySequence_Repeat
-                PySequence_SetItem
-                PySequence_SetSlice
-                PySequence_Tuple)
-
-
-(export-symbols libpython-clj.jna.protocols.mapping
-                PyMapping_Check
-                PyMapping_DelItem
-                PyMapping_DelItemString
-                PyMapping_GetItemString
-                PyMapping_HasKey
-                PyMapping_HasKeyString
-                PyMapping_Items
-                PyMapping_Keys
-                PyMapping_Length
-                PyMapping_SetItemString
-                PyMapping_Values)
-
-
-(export-symbols libpython-clj.jna.protocols.buffer
-                PyBUF_ANY_CONTIGUOUS
-                PyBUF_CONTIG
-                PyBUF_CONTIG_RO
-                PyBUF_C_CONTIGUOUS
-                PyBUF_FORMAT
-                PyBUF_FULL
-                PyBUF_FULL_RO
-                PyBUF_F_CONTIGUOUS
-                PyBUF_INDIRECT
-                PyBUF_MAX_NDIM
-                PyBUF_ND
-                PyBUF_READ
-                PyBUF_RECORDS
-                PyBUF_RECORDS_RO
-                PyBUF_SIMPLE
-                PyBUF_STRIDED
-                PyBUF_STRIDED_RO
-                PyBUF_STRIDES
-                PyBUF_WRITABLE
-                PyBUF_WRITE
-                PyBUF_WRITEABLE
-                PyBuffer_IsContiguous
-                PyBuffer_Release
-                PyBuffer_ToContiguous
-                PyObject_CheckBuffer
-                PyObject_GetBuffer)
-
-
-(export-symbols libpython-clj.jna.concrete.numeric.integer
-                PyLong_Check
-                PyLong_CheckExact
-                PyLong_FromLong
-                PyLong_FromUnsignedLong
-                PyLong_FromSsize_t
-                PyLong_FromLongLong
-                PyLong_FromUnsignedLongLong
-                PyLong_FromDouble
-                PyLong_AsLong
-                PyLong_AsLongLong)
-
-
-(export-symbols libpython-clj.jna.concrete.numeric.float
-                PyFloat_AsDouble
-                PyFloat_Check
-                PyFloat_CheckExact
-                PyFloat_FromDouble
-                PyFloat_FromString
-                PyFloat_GetInfo
-                PyFloat_GetMax
-                PyFloat_GetMin)
-
-
-(export-symbols libpython-clj.jna.concrete.numeric.boolean
-                PyBool_Check
-                PyBool_FromLong
-                Py_False
-                Py_True)
-
-
-(export-symbols libpython-clj.jna.concrete.numeric.complex
-                PyComplex_AsCComplex
-                PyComplex_Check
-                PyComplex_FromCComplex
-                PyComplex_FromDoubles
-                PyComplex_ImagAsDouble
-                PyComplex_RealAsDouble)
-
-
-(export-symbols libpython-clj.jna.concrete.tuple
-                PyTuple_Check
-                PyTuple_GetItem
-                PyTuple_GetSlice
-                PyTuple_New
-                PyTuple_SetItem)
-
-
-(export-symbols libpython-clj.jna.concrete.list
-                PyList_Append
-                PyList_AsTuple
-                PyList_Check
-                PyList_GetItem
-                PyList_GetSlice
-                PyList_Insert
-                PyList_New
-                PyList_Reverse
-                PyList_SetItem
-                PyList_SetSlice
-                PyList_Size
-                PyList_Sort)
-
-
-(export-symbols libpython-clj.jna.concrete.set
-                PyFrozenSet_Check
-                PyFrozenSet_New
-                PySet_Add
-                PySet_Check
-                PySet_Clear
-                PySet_Contains
-                PySet_Discard
-                PySet_New
-                PySet_Pop)
-
-
-(export-symbols libpython-clj.jna.concrete.dict
-                PyDictProxy_New
-                PyDict_Check
-                PyDict_Clear
-                PyDict_Contains
-                PyDict_Copy
-                PyDict_DelItem
-                PyDict_DelItemString
-                PyDict_GetItem
-                PyDict_GetItemString
-                PyDict_GetItemWithError
-                PyDict_Items
-                PyDict_Keys
-                PyDict_Merge
-                PyDict_MergeFromSeq2
-                PyDict_New
-                PyDict_Next
-                PyDict_SetDefault
-                PyDict_SetItem
-                PyDict_SetItemString
-                PyDict_Size
-                PyDict_Update
-                PyDict_Values)
-
-
-(export-symbols libpython-clj.jna.concrete.unicode
-                PyUnicode_AsEncodedString
-                PyUnicode_AsUTF8AndSize
-                PyUnicode_AsUTF8
-                PyUnicode_Decode)
-
-
-(export-symbols libpython-clj.jna.concrete.cfunction
-                METH_CLASS
-                METH_COEXIST
-                METH_KEYWORDS
-                METH_NOARGS
-                METH_O
-                METH_STATIC
-                METH_VARARGS
-                PyCFunction_NewEx
-                PyInstanceMethod_New)
-
-
-(export-symbols libpython-clj.jna.concrete.import
-                PyImport_ImportModule
-                PyImport_Import
-                PyImport_AddModule
-                PyImport_ImportModuleLevel
-                PyImport_GetModuleDict)
-
-
-(export-symbols libpython-clj.jna.concrete.type
-                PyObject_Del
-                PyType_Check
-                PyType_GenericNew
-                PyType_Ready
-                Py_TPFLAGS_BASETYPE
-                Py_TPFLAGS_DEFAULT
-                Py_TPFLAGS_HAVE_GC
-                Py_TPFLAGS_HAVE_STACKLESS_EXTENSION
-                Py_TPFLAGS_HAVE_VERSION_TAG
-                Py_TPFLAGS_HEAPTYPE
-                Py_TPFLAGS_IS_ABSTRACT
-                Py_TPFLAGS_READY
-                Py_TPFLAGS_READYING
-                Py_TPFLAGS_VALID_VERSION_TAG
-                _PyObject_New)
-
-
-(export-symbols libpython-clj.jna.concrete.module
-                PyModule_AddFunctions
-                PyModule_AddIntConstant
-                PyModule_AddObject
-                PyModule_AddStringConstant
-                PyModule_Check
-                PyModule_GetDef
-                PyModule_GetDict
-                PyModule_GetNameObject
-                PyModule_GetState
-                PyModule_New
-                PyModule_SetDocString)
-
-
-(export-symbols libpython-clj.jna.concrete.err
-                PyErr_BadArgument
-                PyErr_BadInternalCall
-                PyErr_Clear
-                PyErr_Fetch
-                PyErr_NoMemory
-                PyErr_Print
-                PyErr_PrintEx
-                PyErr_Restore
-                PyErr_SetFromErrno
-                PyErr_SetNone
-                PyErr_SetObject
-                PyErr_SetString
-                PyErr_WarnEx
-                PyErr_WarnExplicit
-                PyErr_WriteUnraisable
-                PyErr_Occurred
-                ;; Exception classes
-                PyExc_ArithmeticError
-                PyExc_AssertionError
-                PyExc_AttributeError
-                PyExc_BaseException
-                PyExc_BlockingIOError
-                PyExc_BrokenPipeError
-                PyExc_BufferError
-                PyExc_BytesWarning
-                PyExc_ChildProcessError
-                PyExc_ConnectionAbortedError
-                PyExc_ConnectionError
-                PyExc_ConnectionRefusedError
-                PyExc_ConnectionResetError
-                PyExc_DeprecationWarning
-                PyExc_EOFError
-                PyExc_Exception
-                PyExc_FileExistsError
-                PyExc_FileNotFoundError
-                PyExc_FloatingPointError
-                PyExc_FutureWarning
-                PyExc_GeneratorExit
-                PyExc_ImportError
-                PyExc_ImportWarning
-                PyExc_IndentationError
-                PyExc_IndexError
-                PyExc_InterruptedError
-                PyExc_IsADirectoryError
-                PyExc_KeyError
-                PyExc_KeyboardInterrupt
-                PyExc_LookupError
-                PyExc_MemoryError
-                PyExc_ModuleNotFoundError
-                PyExc_NameError
-                PyExc_NotADirectoryError
-                PyExc_NotImplementedError
-                PyExc_OSError
-                PyExc_OverflowError
-                PyExc_PendingDeprecationWarning
-                PyExc_PermissionError
-                PyExc_ProcessLookupError
-                PyExc_RecursionError
-                PyExc_ReferenceError
-                PyExc_ResourceWarning
-                PyExc_RuntimeError
-                PyExc_RuntimeWarning
-                PyExc_StopAsyncIteration
-                PyExc_StopIteration
-                PyExc_SyntaxError
-                PyExc_SyntaxWarning
-                PyExc_SystemError
-                PyExc_SystemExit
-                PyExc_TabError
-                PyExc_TimeoutError
-                PyExc_TypeError
-                PyExc_UnboundLocalError
-                PyExc_UnicodeDecodeError
-                PyExc_UnicodeEncodeError
-                PyExc_UnicodeError
-                PyExc_UnicodeTranslateError
-                PyExc_UnicodeWarning
-                PyExc_UserWarning
-                PyExc_ValueError
-                PyExc_Warning
-                PyExc_ZeroDivisionError
-                ;;Exception data system
-                PyException_GetCause
-                PyException_GetContext
-                PyException_GetTraceback
-                PyException_SetCause
-                PyException_SetContext
-                PyException_SetTraceback)
diff --git a/src/libpython_clj/jna/base.clj b/src/libpython_clj/jna/base.clj
deleted file mode 100644
index 1011fa9..0000000
--- a/src/libpython_clj/jna/base.clj
+++ /dev/null
@@ -1,115 +0,0 @@
-(ns libpython-clj.jna.base
-  (:require [tech.v3.jna :as jna]
-            [tech.v3.jna.base :as jna-base])
-  (:import [com.sun.jna Pointer NativeLibrary]
-           [libpython_clj.jna PyObject]
-           [java.util.concurrent.atomic AtomicLong]))
-
-(set! *warn-on-reflection* true)
-
-(def ^:dynamic *python-library* "python3.6m")
-
-(def ^:dynamic *python-library-names* ["python3.7m" "python3.6m"])
-
-
-(defn library-names
-  []
-  *python-library-names*)
-
-
-(defprotocol PToPyObjectPtr
-  (convertible-to-pyobject-ptr? [item])
-  (->py-object-ptr [item]))
-
-
-(extend-type PyObject
-  PToPyObjectPtr
-  (convertible-to-pyobject-ptr? [item] true)
-  (->py-object-ptr [item] (.getPointer item)))
-
-
-(extend-type Pointer
-  PToPyObjectPtr
-  (convertible-to-pyobject-ptr? [item] true)
-  (->py-object-ptr [item] item))
-
-(extend-type Object
-  PToPyObjectPtr
-  (convertible-to-pyobject-ptr? [item] (jna/is-jna-ptr-convertible? item))
-  (->py-object-ptr [item] (jna/->ptr-backing-store item)))
-
-
-(defn as-pyobj
-  [item]
-  (when (and item (convertible-to-pyobject-ptr? item))
-    (->py-object-ptr item)))
-
-
-(defn ensure-pyobj
-  [item]
-  (if-let [retval (as-pyobj item)]
-    retval
-    (throw (ex-info "Failed to get a pyobject pointer from object." {}))))
-
-
-(defn ensure-pydict
-  "The return value of this has to be a python dictionary object."
-  [item]
-  (ensure-pyobj item))
-
-
-(defn ensure-pytuple
-  "The return value of this has to be a python tuple object."
-  [item]
-  (ensure-pyobj item))
-
-
-(definline current-thread-id
-  ^long []
-  (-> (Thread/currentThread)
-      (.getId)))
-
-(defonce gil-thread-id (AtomicLong. Long/MAX_VALUE))
-
-
-(defn set-gil-thread-id!
-  ^long [^long expected ^long new-value]
-  (when-not (.compareAndSet ^AtomicLong gil-thread-id expected new-value)
-    (throw (Exception. "Failed to set gil thread id")))
-  new-value)
-
-
-(defmacro def-no-gil-pylib-fn
-  "Define a pylib function where the gil doesn't need to be captured to call."
-  [fn-name docstring & args]
-  `(jna/def-jna-fn *python-library* ~fn-name ~docstring ~@args))
-
-
-(defmacro def-pylib-fn
-  [fn-name docstring rettype & argpairs]
-  `(defn ~fn-name
-     ~docstring
-     ~(mapv first argpairs)
-     (when-not (== (current-thread-id) (.get ^AtomicLong gil-thread-id))
-       (throw (Exception. "Failure to capture gil when calling into libpython")))
-     (let [~'tvm-fn (jna/find-function ~(str fn-name) *python-library*)
-           ~'fn-args (object-array
-                      ~(mapv (fn [[arg-symbol arg-coersion]]
-                               (when (= arg-symbol arg-coersion)
-                                 (throw (ex-info (format "Argument symbol (%s) cannot match coersion (%s)"
-                                                         arg-symbol arg-coersion)
-                                                 {})))
-                               `(~arg-coersion ~arg-symbol))
-                             argpairs))]
-       ~(if rettype
-          `(.invoke (jna-base/to-typed-fn ~'tvm-fn) ~rettype ~'fn-args)
-          `(.invoke (jna-base/to-typed-fn ~'tvm-fn) ~'fn-args)))))
-
-
-(defonce size-t-type (type (jna/size-t 0)))
-
-
-(defn find-pylib-symbol
-  ^Pointer [sym-name]
-  (.getGlobalVariableAddress ^NativeLibrary (jna-base/load-library *python-library*)
-                             sym-name))
diff --git a/src/libpython_clj/jna/concrete/bytes.clj b/src/libpython_clj/jna/concrete/bytes.clj
deleted file mode 100644
index d091821..0000000
--- a/src/libpython_clj/jna/concrete/bytes.clj
+++ /dev/null
@@ -1,109 +0,0 @@
-(ns libpython-clj.jna.concrete.bytes
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     as-pyobj
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.datatype.nio-buffer :as nio-buffer]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna Pointer]))
-
-
-
-(def-pylib-fn PyBytes_Check
-  "Return true if the object o is a bytes object or an instance of a subtype of the
-  bytes type."
-  Integer
-  [o ensure-pyobj])
-
-
-(def-pylib-fn PyBytes_CheckExact
-  "Return true if the object o is a bytes object, but not an instance of a subtype of
-  the bytes type."
-  Integer
-  [o ensure-pyobj])
-
-
-
-(def-pylib-fn PyBytes_FromString
-  "Return value: New reference.
-
-   Return a new bytes object with a copy of the string v as value on success, and NULL
-   on failure. The parameter v must not be NULL; it will not be checked."
-  Pointer
-  [v str])
-
-
-(def-pylib-fn PyBytes_FromStringAndSize
-  "Return value: New reference.
-
-   Return a new bytes object with a copy of the string v as value and length len on
-   success, and NULL on failure. If v is NULL, the contents of the bytes object are
-   uninitialized."
-  Pointer
-  [v nio-buffer/->nio-buffer]
-  [len jna/size-t])
-
-
-(def-pylib-fn PyBytes_AsString
-  "Return a pointer to the contents of o. The pointer refers to the internal buffer of
-  o, which consists of len(o) + 1 bytes. The last byte in the buffer is always null,
-  regardless of whether there are any other null bytes. The data must not be modified in
-  any way, unless the object was just created using PyBytes_FromStringAndSize(NULL,
-  size). It must not be deallocated. If o is not a bytes object at all,
-  PyBytes_AsString() returns NULL and raises TypeError."
-  String
-  [o ensure-pyobj])
-
-
-(def-pylib-fn PyBytes_AsStringAndSize
-  "Return the null-terminated contents of the object obj through the output variables
-  buffer and length.
-
-   If length is NULL, the bytes object may not contain embedded null bytes; if it does,
-   the function returns -1 and a ValueError is raised.
-
-   The buffer refers to an internal buffer of obj, which includes an additional null
-   byte at the end (not counted in length). The data must not be modified in any way,
-   unless the object was just created using PyBytes_FromStringAndSize(NULL, size). It
-   must not be deallocated. If obj is not a bytes object at all,
-   PyBytes_AsStringAndSize() returns -1 and raises TypeError.
-
-   Changed in version 3.5: Previously, TypeError was raised when embedded null bytes
-   were encountered in the bytes object.
-
-   Signature:
-   int (PyObject *obj, char **buffer, Py_ssize_t *length)"
-  Integer
-  [obj ensure-pyobj]
-  [ptr-to-buffer jna/ensure-ptr-ptr]
-  [length as-pyobj])
-
-
-
-(def-pylib-fn PyBytes_Concat
-  "Create a new bytes object in *bytes containing the contents of newpart appended to
-  bytes; the caller will own the new reference. The reference to the old value of bytes
-  will be stolen. If the new object cannot be created, the old reference to bytes will
-  still be discarded and the value of *bytes will be set to NULL; the appropriate
-  exception will be set.
-  Signature:
-  void (PyObject **bytes, PyObject *newpart)"
-  nil
-  [out-bytes jna/ensure-ptr-ptr]
-  [newpart ensure-pyobj])
-
-
-
-(def-pylib-fn PyBytes_ConcatAndDel
-  "Create a new bytes object in *bytes containing the contents of newpart appended to
-  bytes. This version decrements the reference count of newpart.
-  Signature:
-  void (PyObject **bytes, PyObject *newpart)"
-  nil
-  [out-bytes jna/ensure-ptr-ptr]
-  [newpart ensure-pyobj])
diff --git a/src/libpython_clj/jna/concrete/cfunction.clj b/src/libpython_clj/jna/concrete/cfunction.clj
deleted file mode 100644
index 70f9057..0000000
--- a/src/libpython_clj/jna/concrete/cfunction.clj
+++ /dev/null
@@ -1,76 +0,0 @@
-(ns libpython-clj.jna.concrete.cfunction
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     as-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna CallbackReference Pointer Callback]
-           [libpython_clj.jna
-            CFunction$KeyWordFunction
-            CFunction$TupleFunction
-            PyMethodDef
-            PyObject]))
-
-
-;; #define METH_OLDARGS  0x0000   -- unsupported now
-(def METH_VARARGS  0x0001)
-(def METH_KEYWORDS 0x0002)
-
-;; METH_NOARGS and METH_O must not be combined with the flags above.
-(def METH_NOARGS   0x0004)
-(def METH_O        0x0008)
-
-;; /* METH_CLASS and METH_STATIC are a little different; these control
-;;    the construction of methods for a class.  These cannot be used for
-;;    functions in modules. */
-(def METH_CLASS    0x0010)
-(def METH_STATIC   0x0020)
-
-;; /* METH_COEXIST allows a method to be entered even though a slot has
-;;    already filled the entry.  When defined, the flag allows a separate
-;;    method, "__contains__" for example, to coexist with a defined
-;;    slot like sq_contains. */
-
-(def METH_COEXIST   0x0040)
-
-;; We aren't going there for either of these...
-;; #ifndef Py_LIMITED_API
-;; #define METH_FASTCALL  0x0080
-;; #endif
-
-;; /* This bit is preserved for Stackless Python */
-;; #ifdef STACKLESS
-;; #define METH_STACKLESS 0x0100
-;; #else
-;; #define METH_STACKLESS 0x0000
-;; #endif
-
-
-(def-pylib-fn PyCFunction_NewEx
-  "Create a new callable from an item."
-  Pointer
-  [method-def (partial jna/ensure-type PyMethodDef)]
-  [self as-pyobj]
-  [module as-pyobj])
-
-
-(def-pylib-fn PyInstanceMethod_New
-  "Return value: New reference.
-
-  Return a new instance method object, with func being any callable object func is the
-  function that will be called when the instance method is called."
-  Pointer
-  [func ensure-pyobj])
-
-
-(def-pylib-fn PyInstanceMethod_Function
-  "Return value: Borrowed reference.
-
-  Return the function object associated with the instance method im."
-  Pointer
-  [im ensure-pyobj])
diff --git a/src/libpython_clj/jna/concrete/code.clj b/src/libpython_clj/jna/concrete/code.clj
deleted file mode 100644
index d90d5d9..0000000
--- a/src/libpython_clj/jna/concrete/code.clj
+++ /dev/null
@@ -1,33 +0,0 @@
-(ns libpython-clj.jna.concrete.code
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject]))
-
-
-(def-pylib-fn PyCode_Check
-  "Return true if co is a code object."
-  Integer
-  [co ensure-pyobj])
-
-
-(def-pylib-fn PyCode_GetNumFree
-  "Return the number of free variables in co."
-  Integer
-  [co ensure-pyobj])
-
-
-
-(def-pylib-fn PyCode_NewEmpty
-  "Return a new empty code object with the specified filename, function name, and first
-  line number. It is illegal to exec or eval() the resulting code object."
-  Pointer
-  [filename str]
-  [funcname str]
-  [firstlineno int])
diff --git a/src/libpython_clj/jna/concrete/dict.clj b/src/libpython_clj/jna/concrete/dict.clj
deleted file mode 100644
index 50690d1..0000000
--- a/src/libpython_clj/jna/concrete/dict.clj
+++ /dev/null
@@ -1,235 +0,0 @@
-(ns libpython-clj.jna.concrete.dict
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject DirectMapped]))
-
-
-
-(def-pylib-fn PyDict_Check
-  "Return true if p is a dict object or an instance of a subtype of the dict type."
-  Integer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyDict_New
-  "Return value: New reference.
-
-   Return a new empty dictionary, or NULL on failure."
-  Pointer)
-
-
-(def-pylib-fn PyDictProxy_New
-  "Return value: New reference.
-
-   Return a types.MappingProxyType object for a mapping which enforces read-only
-   behavior. This is normally used to create a view to prevent modification of the
-   dictionary for non-dynamic class types."
-  Pointer
-  [mapping ensure-pyobj])
-
-
-(def-pylib-fn PyDict_Clear
-  "Empty an existing dictionary of all key-value pairs."
-  nil
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyDict_Contains
-  "Determine if dictionary p contains key. If an item in p is matches key, return 1,
-  otherwise return 0. On error, return -1. This is equivalent to the Python expression
-  key in p."
-  Integer
-  [p ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PyDict_Copy
-  "Return value: New reference.
-
-   Return a new dictionary that contains the same key-value pairs as p."
-  Pointer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyDict_SetItem
-  "Insert value into the dictionary p with a key of key. key must be hashable; if it
-  isn’t, TypeError will be raised. Return 0 on success or -1 on failure."
-  Integer
-  [p ensure-pyobj]
-  [key ensure-pyobj]
-  [val ensure-pyobj])
-
-
-(def-pylib-fn PyDict_SetItemString
-  "Insert value into the dictionary p using key as a key. key should be a const
-  char*. The key object is created using PyUnicode_FromString(key). Return 0 on success
-  or -1 on failure."
-  Integer
-  [p ensure-pyobj]
-  [key ensure-pyobj]
-  [val ensure-pyobj])
-
-
-(def-pylib-fn PyDict_DelItem
-  "Remove the entry in dictionary p with key key. key must be hashable; if it isn’t,
-  TypeError is raised. Return 0 on success or -1 on failure."
-  Integer
-  [p ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PyDict_DelItemString
-  "Remove the entry in dictionary p which has a key specified by the string key. Return
-  0 on success or -1 on failure."
-  Integer
-  [p ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PyDict_GetItem
-  "Return value: Borrowed reference.
-
-   Return the object from dictionary p which has a key key. Return NULL if the key key
-   is not present, but without setting an exception.
-
-   Note that exceptions which occur while calling __hash__() and __eq__() methods will
-   get suppressed. To get error reporting use PyDict_GetItemWithError() instead."
-  Pointer
-  [p ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PyDict_GetItemWithError
-  "Return value: Borrowed reference.
-
-   Variant of PyDict_GetItem() that does not suppress exceptions. Return NULL with an
-   exception set if an exception occurred. Return NULL without an exception set if the
-   key wasn’t present."
-  Pointer
-  [p ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PyDict_GetItemString
-  "Return value: Borrowed reference.
-
-   This is the same as PyDict_GetItem(), but key is specified as a const char*, rather
-   than a PyObject*.
-
-   Note that exceptions which occur while calling __hash__() and __eq__() methods and
-   creating a temporary string object will get suppressed. To get error reporting use
-   PyDict_GetItemWithError() instead."
-  Pointer
-  [p ensure-pyobj]
-  [key ensure-pyobj])
-
-
-
-
-(def-pylib-fn PyDict_SetDefault
-  "Return value: Borrowed reference.
-
-   This is the same as the Python-level dict.setdefault(). If present, it returns the
-   value corresponding to key from the dictionary p. If the key is not in the dict, it
-   is inserted with value defaultobj and defaultobj is returned. This function evaluates
-   the hash function of key only once, instead of evaluating it independently for the
-   lookup and the insertion.
-
-    New in version 3.4."
-  Pointer
-  [p ensure-pyobj]
-  [key ensure-pyobj]
-  [defaultobj ensure-pyobj])
-
-
-(def-pylib-fn PyDict_Items
-  "Return value: New reference.
-
-   Return a PyListObject containing all the items from the dictionary."
-  Pointer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyDict_Keys
-  "Return value: New reference.
-
-   Return a PyListObject containing all the keys from the dictionary."
-  Pointer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyDict_Values
-  "Return value: New reference.
-
-   Return a PyListObject containing all the values from the dictionary p."
-  Pointer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyDict_Size
-  "Return the number of items in the dictionary. This is equivalent to len(p) on a
-  dictionary."
-  size-t-type
-  [p ensure-pyobj])
-
-
-(defn PyDict_Next
-  "Iterate over all key-value pairs in the dictionary p. The Py_ssize_t referred to by
-  ppos must be initialized to 0 prior to the first call to this function to start the
-  iteration; the function returns true for each pair in the dictionary, and false once
-  all pairs have been reported. The parameters pkey and pvalue should either point to
-  PyObject* variables that will be filled in with each key and value, respectively, or
-  may be NULL. Any references returned through them are borrowed. ppos should not be
-  altered during iteration. Its value represents offsets within the internal dictionary
-  structure, and since the structure is sparse, the offsets are not consecutive."
-  ^long [p ppos pkey pvalue]
-  (long (DirectMapped/PyDict_Next (ensure-pyobj p)
-                                  (jna/ensure-type jna/size-t-ref-type ppos)
-                                  (jna/ensure-ptr-ptr pkey)
-                                  (jna/ensure-ptr-ptr pvalue))))
-
-
-(def-pylib-fn PyDict_Merge
-  "Iterate over mapping object b adding key-value pairs to dictionary a. b may be a
-  dictionary, or any object supporting PyMapping_Keys() and PyObject_GetItem(). If
-  override is true, existing pairs in a will be replaced if a matching key is found in
-  b, otherwise pairs will only be added if there is not a matching key in a. Return 0 on
-  success or -1 if an exception was raised."
-  Integer
-  [a ensure-pyobj]
-  [b ensure-pyobj]
-  [override int])
-
-
-(def-pylib-fn PyDict_Update
-  "This is the same as PyDict_Merge(a, b, 1) in C, and is similar to a.update(b) in
-  Python except that PyDict_Update() doesn’t fall back to the iterating over a sequence
-  of key value pairs if the second argument has no “keys” attribute. Return 0 on success
-  or -1 if an exception was raised."
-  Integer
-  [a ensure-pyobj]
-  [b ensure-pyobj])
-
-
-(def-pylib-fn PyDict_MergeFromSeq2
-  "Update or merge into dictionary a, from the key-value pairs in seq2. seq2 must be an
-  iterable object producing iterable objects of length 2, viewed as key-value pairs. In
-  case of duplicate keys, the last wins if override is true, else the first wins. Return
-  0 on success or -1 if an exception was raised. Equivalent Python (except for the
-  return value):
-def PyDict_MergeFromSeq2(a, seq2, override):
-    for key, value in seq2:
-        if override or key not in a:
-            a[key] = value"
-  Integer
-  [a ensure-pyobj]
-  [seq2 ensure-pyobj]
-  [override int])
diff --git a/src/libpython_clj/jna/concrete/err.clj b/src/libpython_clj/jna/concrete/err.clj
deleted file mode 100644
index 54b7c2b..0000000
--- a/src/libpython_clj/jna/concrete/err.clj
+++ /dev/null
@@ -1,337 +0,0 @@
-(ns libpython-clj.jna.concrete.err
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     find-pylib-symbol
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject PyTypeObject DirectMapped]))
-
-(set! *warn-on-reflection* true)
-
-
-(defn PyErr_Occurred
-  "Check if the error indicator is set.  If so, return the exception type."
-  ^Pointer []
-  (DirectMapped/PyErr_Occurred))
-
-
-(def-pylib-fn PyErr_Clear
-  "Clear the error indicator. If the error indicator is not set, there is no effect."
-  nil)
-
-
-(def-pylib-fn PyErr_PrintEx
-  "Print a standard traceback to sys.stderr and clear the error indicator. Unless the
-  error is a SystemExit. In that case the no traceback is printed and Python process
-  will exit with the error code specified by the SystemExit instance.
-
-   Call this function only when the error indicator is set. Otherwise it will cause a
-   fatal error!
-
-   If set_sys_last_vars is nonzero, the variables sys.last_type, sys.last_value and
-   sys.last_traceback will be set to the type, value and traceback of the printed
-   exception, respectively."
-  nil
-  [set-sys-last-vars int])
-
-
-(def-pylib-fn PyErr_Print
-  "Alias for PyErr_PrintEx(1)."
-  nil)
-
-
-(def-pylib-fn PyErr_WriteUnraisable
-  "This utility function prints a warning message to sys.stderr when an exception has
-   been set but it is impossible for the interpreter to actually raise the exception. It
-   is used, for example, when an exception occurs in an __del__() method.
-
-   The function is called with a single argument obj that identifies the context in
-   which the unraisable exception occurred. If possible, the repr of obj will be printed
-   in the warning message.
-
-   An exception must be set when calling this function."
-  nil
-  [obj ensure-pyobj])
-
-
-(def-pylib-fn PyErr_SetString
-  "This is the most common way to set the error indicator. The first argument specifies
-  the exception type; it is normally one of the standard exceptions,
-  e.g. PyExc_RuntimeError. You need not increment its reference count. The second
-  argument is an error message; it is decoded from 'utf-8’."
-  nil
-  [type ensure-pyobj]
-  [message str])
-
-
-(def-pylib-fn PyErr_SetObject
-  "This function is similar to PyErr_SetString() but lets you specify an arbitrary
-  Python object for the “value” of the exception."
-  nil
-  [type ensure-pyobj]
-  [value ensure-pyobj])
-
-
-(def-pylib-fn PyErr_SetNone
-  "This is a shorthand for PyErr_SetObject(type, Py_None)."
-  nil
-  [type ensure-pyobj])
-
-
-(def-pylib-fn PyErr_BadArgument
-  "This is a shorthand for PyErr_SetString(PyExc_TypeError, message), where message
-  indicates that a built-in operation was invoked with an illegal argument. It is mostly
-  for internal use."
-  Integer)
-
-
-
-(def-pylib-fn PyErr_NoMemory
-  "Return value: Always NULL.
-
-   This is a shorthand for PyErr_SetNone(PyExc_MemoryError); it returns NULL so an
-   object allocation function can write return PyErr_NoMemory(); when it runs out of
-   memory."
-  Pointer)
-
-
-(def-pylib-fn PyErr_SetFromErrno
-  "Return value: Always NULL.
-
-   This is a convenience function to raise an exception when a C library function has
-   returned an error and set the C variable errno. It constructs a tuple object whose
-   first item is the integer errno value and whose second item is the corresponding
-   error message (gotten from strerror()), and then calls PyErr_SetObject(type,
-   object). On Unix, when the errno value is EINTR, indicating an interrupted system
-   call, this calls PyErr_CheckSignals(), and if that set the error indicator, leaves it
-   set to that. The function always returns NULL, so a wrapper function around a system
-   call can write return PyErr_SetFromErrno(type); when the system call returns an
-   error."
-  Pointer
-  [type ensure-pyobj])
-
-
-(def-pylib-fn PyErr_BadInternalCall
-  "This is a shorthand for PyErr_SetString(PyExc_SystemError, message), where message
-  indicates that an internal operation (e.g. a Python/C API function) was invoked with
-  an illegal argument. It is mostly for internal use."
-  nil)
-
-
-(def-pylib-fn PyErr_WarnEx
-  "Issue a warning message. The category argument is a warning category (see below) or
-  NULL; the message argument is a UTF-8 encoded string. stack_level is a positive number
-  giving a number of stack frames; the warning will be issued from the currently
-  executing line of code in that stack frame. A stack_level of 1 is the function calling
-  PyErr_WarnEx(), 2 is the function above that, and so forth.
-
-   Warning categories must be subclasses of PyExc_Warning; PyExc_Warning is a subclass
-   of PyExc_Exception; the default warning category is PyExc_RuntimeWarning. The
-   standard Python warning categories are available as global variables whose names are
-   enumerated at Standard Warning Categories.
-
-   For information about warning control, see the documentation for the warnings module
-   and the -W option in the command line documentation. There is no C API for warning
-   control."
-  Integer
-  [category ensure-pyobj]
-  [message str]
-  [stack_level jna/size-t])
-
-
-(def-pylib-fn PyErr_WarnExplicit
-  "Similar to PyErr_WarnExplicitObject() except that message and module are UTF-8
-  encoded strings, and filename is decoded from the filesystem encoding (os.fsdecode())."
-  Integer
-  [category ensure-pyobj]
-  [message str]
-  [filename str]
-  [lineno int]
-  [module str]
-  [registry ensure-pyobj])
-
-
-(def-pylib-fn PyErr_Fetch
-  "Retrieve the error indicator into three variables whose addresses are passed. If the
-  error indicator is not set, set all three variables to NULL. If it is set, it will be
-  cleared and you own a reference to each object retrieved. The value and traceback
-  object may be NULL even when the type object is not.
-
-   Note
-
-   This function is normally only used by code that needs to catch exceptions or by code
-   that needs to save and restore the error indicator temporarily, e.g.:
-
-    {
-       PyObject *type, *value, *traceback;
-       PyErr_Fetch(&type, &value, &traceback);
-
-       /* ... code that might produce other errors ... */
-
-       PyErr_Restore(type, value, traceback);
-    }"
-  nil
-  [ptype jna/ensure-ptr-ptr]
-  [pvalue jna/ensure-ptr-ptr]
-  [ptraceback jna/ensure-ptr-ptr])
-
-
-
-(def-pylib-fn PyErr_Restore
-  "Set the error indicator from the three objects. If the error indicator is already
-   set, it is cleared first. If the objects are NULL, the error indicator is cleared. Do
-   not pass a NULL type and non-NULL value or traceback. The exception type should be a
-   class. Do not pass an invalid exception type or value. (Violating these rules will
-   cause subtle problems later.) This call takes away a reference to each object: you
-   must own a reference to each object before the call and after the call you no longer
-   own these references. (If you don’t understand this, don’t use this function. I warned
-   you.)
-
-   Note
-
-   This function is normally only used by code that needs to save and restore the error
-   indicator temporarily. Use PyErr_Fetch() to save the current error indicator."
-  nil
-  [type ensure-pyobj]
-  [value ensure-pyobj]
-  [traceback ensure-pyobj])
-
-
-(def-pylib-fn PyException_GetTraceback
-  "Return value: New reference.
-
-   Return the traceback associated with the exception as a new reference, as accessible
-   from Python through __traceback__. If there is no traceback associated, this returns
-   NULL."
-  Pointer
-  [ex ensure-pyobj])
-
-
-(def-pylib-fn PyException_SetTraceback
-  "Set the traceback associated with the exception to tb. Use Py_None to clear it."
-  Integer
-  [ex ensure-pyobj]
-  [tb ensure-pyobj])
-
-
-(def-pylib-fn PyException_GetContext
-  "Return value: New reference.
-
-   Return the context (another exception instance during whose handling ex was raised)
-   associated with the exception as a new reference, as accessible from Python through
-   __context__. If there is no context associated, this returns NULL."
-  Pointer
-  [ex ensure-pyobj])
-
-
-(def-pylib-fn PyException_SetContext
-  "Set the context associated with the exception to ctx. Use NULL to clear it. There is
-  no type check to make sure that ctx is an exception instance. This steals a reference
-  to ctx."
-  nil
-  [ex ensure-pyobj]
-  [ctx ensure-pyobj])
-
-
-(def-pylib-fn PyException_GetCause
-  "Return value: New reference.
-
-   Return the cause (either an exception instance, or None, set by raise ... from ...)
-   associated with the exception as a new reference, as accessible from Python through
-   __cause__."
-  Pointer
-  [ex ensure-pyobj])
-
-
-(def-pylib-fn PyException_SetCause
-  "Set the cause associated with the exception to cause. Use NULL to clear it. There is
-  no type check to make sure that cause is either an exception instance or None. This
-  steals a reference to cause.
-
-   __suppress_context__ is implicitly set to True by this function."
-  nil
-  [ex ensure-pyobj]
-  [cause ensure-pyobj])
-
-
-(defmacro def-err-symbol
-  [sym-name]
-  `(defn ~sym-name
-     []
-     (->
-      (find-pylib-symbol ~(name sym-name))
-      (.getPointer 0))))
-
-
-(def-err-symbol PyExc_BaseException)
-(def-err-symbol PyExc_Exception)
-(def-err-symbol PyExc_ArithmeticError)
-(def-err-symbol PyExc_AssertionError)
-(def-err-symbol PyExc_AttributeError)
-(def-err-symbol PyExc_BlockingIOError)
-(def-err-symbol PyExc_BrokenPipeError)
-(def-err-symbol PyExc_BufferError)
-(def-err-symbol PyExc_ChildProcessError)
-(def-err-symbol PyExc_ConnectionAbortedError)
-(def-err-symbol PyExc_ConnectionError)
-(def-err-symbol PyExc_ConnectionRefusedError)
-(def-err-symbol PyExc_ConnectionResetError)
-(def-err-symbol PyExc_EOFError)
-(def-err-symbol PyExc_FileExistsError)
-(def-err-symbol PyExc_FileNotFoundError)
-(def-err-symbol PyExc_FloatingPointError)
-(def-err-symbol PyExc_GeneratorExit)
-(def-err-symbol PyExc_ImportError)
-(def-err-symbol PyExc_IndentationError)
-(def-err-symbol PyExc_IndexError)
-(def-err-symbol PyExc_InterruptedError)
-(def-err-symbol PyExc_IsADirectoryError)
-(def-err-symbol PyExc_KeyError)
-(def-err-symbol PyExc_KeyboardInterrupt)
-(def-err-symbol PyExc_LookupError)
-(def-err-symbol PyExc_MemoryError)
-(def-err-symbol PyExc_ModuleNotFoundError)
-(def-err-symbol PyExc_NameError)
-(def-err-symbol PyExc_NotADirectoryError)
-(def-err-symbol PyExc_NotImplementedError)
-(def-err-symbol PyExc_OSError)
-(def-err-symbol PyExc_OverflowError)
-(def-err-symbol PyExc_PermissionError)
-(def-err-symbol PyExc_ProcessLookupError)
-(def-err-symbol PyExc_RecursionError)
-(def-err-symbol PyExc_ReferenceError)
-(def-err-symbol PyExc_RuntimeError)
-(def-err-symbol PyExc_StopAsyncIteration)
-(def-err-symbol PyExc_StopIteration)
-(def-err-symbol PyExc_SyntaxError)
-(def-err-symbol PyExc_SystemError)
-(def-err-symbol PyExc_SystemExit)
-(def-err-symbol PyExc_TabError)
-(def-err-symbol PyExc_TimeoutError)
-(def-err-symbol PyExc_TypeError)
-(def-err-symbol PyExc_UnboundLocalError)
-(def-err-symbol PyExc_UnicodeDecodeError)
-(def-err-symbol PyExc_UnicodeEncodeError)
-(def-err-symbol PyExc_UnicodeError)
-(def-err-symbol PyExc_UnicodeTranslateError)
-(def-err-symbol PyExc_ValueError)
-(def-err-symbol PyExc_ZeroDivisionError)
-
-
-(def-err-symbol PyExc_Warning)
-(def-err-symbol PyExc_BytesWarning)
-(def-err-symbol PyExc_DeprecationWarning)
-(def-err-symbol PyExc_FutureWarning)
-(def-err-symbol PyExc_ImportWarning)
-(def-err-symbol PyExc_PendingDeprecationWarning)
-(def-err-symbol PyExc_ResourceWarning)
-(def-err-symbol PyExc_RuntimeWarning)
-(def-err-symbol PyExc_SyntaxWarning)
-(def-err-symbol PyExc_UnicodeWarning)
-(def-err-symbol PyExc_UserWarning)
diff --git a/src/libpython_clj/jna/concrete/import.clj b/src/libpython_clj/jna/concrete/import.clj
deleted file mode 100644
index 34a16cf..0000000
--- a/src/libpython_clj/jna/concrete/import.clj
+++ /dev/null
@@ -1,130 +0,0 @@
-(ns libpython-clj.jna.concrete.import
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject]))
-
-
-(def-pylib-fn PyImport_ImportModule
-  "Return value: New reference.
-
-   This is a simplified interface to PyImport_ImportModuleEx() below, leaving the
-   globals and locals arguments set to NULL and level set to 0. When the name argument
-   contains a dot (when it specifies a submodule of a package), the fromlist argument is
-   set to the list ['*'] so that the return value is the named module rather than the
-   top-level package containing it as would otherwise be the case. (Unfortunately, this
-   has an additional side effect when name in fact specifies a subpackage instead of a
-   submodule: the submodules specified in the package’s __all__ variable are loaded.)
-   Return a new reference to the imported module, or NULL with an exception set on
-   failure. A failing import of a module doesn’t leave the module in sys.modules.
-
-   This function always uses absolute imports."
-  Pointer
-  [name str])
-
-
-
-(def-pylib-fn PyImport_Import
-  "Return value: New reference.
-
-   This is a higher-level interface that calls the current “import hook function” (with
-   an explicit level of 0, meaning absolute import). It invokes the __import__()
-   function from the __builtins__ of the current globals. This means that the import is
-   done using whatever import hooks are installed in the current environment.
-
-   This function always uses absolute imports."
-  Pointer
-  [name ensure-pyobj])
-
-
-
-(def-pylib-fn PyImport_AddModule
-  "Return value: Borrowed reference.
-
-   Similar to PyImport_AddModuleObject(), but the name is a UTF-8 encoded string instead
-   of a Unicode object."
-  Pointer
-  [name str])
-
-
-(def-pylib-fn PyImport_ImportModuleLevel
-  "Return value: New reference.
-
-    Similar to PyImport_ImportModuleLevelObject(), but the name is a UTF-8 encoded
-    string instead of a Unicode object.
-
-     Changed in version 3.3: Negative values for level are no longer accepted.
-
-
-(documentation from python __import__ function
- __import__(name, globals=None, locals=None, fromlist=(), level=0)
-
-
-This function is invoked by the import statement. It can be replaced (by importing the
-builtins module and assigning to builtins.__import__) in order to change semantics of
-the import statement, but doing so is strongly discouraged as it is usually simpler to
-use import hooks (see PEP 302) to attain the same goals and does not cause issues with
-code which assumes the default import implementation is in use. Direct use of
-__import__() is also discouraged in favor of importlib.import_module().
-
-The function imports the module name, potentially using the given globals and locals to
-determine how to interpret the name in a package context. The fromlist gives the names
-of objects or submodules that should be imported from the module given by name. The
-standard implementation does not use its locals argument at all, and uses its globals
-only to determine the package context of the import statement.
-
-level specifies whether to use absolute or relative imports. 0 (the default) means only
-perform absolute imports. Positive values for level indicate the number of parent
-directories to search relative to the directory of the module calling __import__() (see
-PEP 328 for the details).
-
-When the name variable is of the form package.module, normally, the top-level package
-(the name up till the first dot) is returned, not the module named by name. However,
-when a non-empty fromlist argument is given, the module named by name is returned.
-
-For example, the statement import spam results in bytecode resembling the following
-code:
-
-spam = __import__('spam', globals(), locals(), [], 0)
-
-The statement import spam.ham results in this call:
-
-spam = __import__('spam.ham', globals(), locals(), [], 0)
-
-Note how __import__() returns the toplevel module here because this is the object that
-is bound to a name by the import statement.
-
-On the other hand, the statement from spam.ham import eggs, sausage as saus results in
-
-_temp = __import__('spam.ham', globals(), locals(), ['eggs', 'sausage'], 0)
-eggs = _temp.eggs
-saus = _temp.sausage
-
-Here, the spam.ham module is returned from __import__(). From this object, the names to
-import are retrieved and assigned to their respective names.
-
-If you simply want to import a module (potentially within a package) by name, use
-importlib.import_module().
-
-Changed in version 3.3: Negative values for level are no longer supported (which also
-changes the default value to 0)."
-  Pointer
-  [name str]
-  [globals ensure-pydict]
-  [locals ensure-pydict]
-  [fromlist ensure-pyobj]
-  [level int])
-
-
-(def-pylib-fn PyImport_GetModuleDict
-  "Return value: Borrowed reference.
-
-   Return the dictionary used for the module administration (a.k.a. sys.modules). Note
-   that this is a per-interpreter variable."
-  Pointer)
diff --git a/src/libpython_clj/jna/concrete/list.clj b/src/libpython_clj/jna/concrete/list.clj
deleted file mode 100644
index 7922b72..0000000
--- a/src/libpython_clj/jna/concrete/list.clj
+++ /dev/null
@@ -1,128 +0,0 @@
-(ns libpython-clj.jna.concrete.list
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject]))
-
-(def-pylib-fn PyList_Check
-  "Return true if p is a list object or an instance of a subtype of the list type."
-  Integer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyList_New
-  "Return value: New reference.
-
-   Return a new list of length len on success, or NULL on failure.
-
-   Note
-
-   If len is greater than zero, the returned list object’s items are set to NULL. Thus
-   you cannot use abstract API functions such as PySequence_SetItem() or expose the
-   object to Python code before setting all items to a real object with
-   PyList_SetItem()."
-  Pointer
-  [len jna/size-t])
-
-
-(def-pylib-fn PyList_Size
-  "Return the length of the list object in list; this is equivalent to len(list) on a
-  list object."
-  size-t-type
-  [list ensure-pyobj])
-
-
-(def-pylib-fn PyList_GetItem
-  "Return value: Borrowed reference.
-
-   Return the object at position index in the list pointed to by list. The position must
-   be positive, indexing from the end of the list is not supported. If index is out of
-   bounds, return NULL and set an IndexError exception."
-  Pointer
-  [list ensure-pyobj]
-  [index jna/size-t])
-
-
-(def-pylib-fn PyList_SetItem
-  "Set the item at index index in list to item. Return 0 on success or -1 on failure.
-
-   Note
-
-   This function “steals” a reference to item and discards a reference to an item
-   already in the list at the affected position."
-  Integer
-  [list ensure-pyobj]
-  [index jna/size-t]
-  [item ensure-pyobj])
-
-
-(def-pylib-fn PyList_Insert
-  "Insert the item item into list list in front of index index. Return 0 if successful;
-  return -1 and set an exception if unsuccessful. Analogous to list.insert(index,
-  item)."
-  Integer
-  [list ensure-pyobj]
-  [index jna/size-t]
-  [item ensure-pyobj])
-
-
-(def-pylib-fn PyList_Append
-  "Append the object item at the end of list list. Return 0 if successful; return -1 and
-  set an exception if unsuccessful. Analogous to list.append(item)."
-  Integer
-  [list ensure-pyobj]
-  [item ensure-pyobj])
-
-
-(def-pylib-fn PyList_GetSlice
-  "Return value: New reference.
-
-   Return a list of the objects in list containing the objects between low and
-   high. Return NULL and set an exception if unsuccessful. Analogous to
-   list[low:high]. Negative indices, as when slicing from Python, are not supported."
-  Pointer
-  [list ensure-pyobj]
-  [low jna/size-t]
-  [high jna/size-t])
-
-
-(def-pylib-fn PyList_SetSlice
-  "Set the slice of list between low and high to the contents of itemlist. Analogous to
-  list[low:high] = itemlist. The itemlist may be NULL, indicating the assignment of an
-  empty list (slice deletion). Return 0 on success, -1 on failure. Negative indices, as
-  when slicing from Python, are not supported."
-  Integer
-  [list ensure-pyobj]
-  [low jna/size-t]
-  [high jna/size-t]
-  [itemlist ensure-pyobj])
-
-
-(def-pylib-fn PyList_Sort
-  "Sort the items of list in place. Return 0 on success, -1 on failure. This is
-  equivalent to list.sort()."
-  Integer
-  [list ensure-pyobj])
-
-
-(def-pylib-fn PyList_Reverse
-  "Reverse the items of list in place. Return 0 on success, -1 on failure. This is the
-  equivalent of list.reverse()."
-  Integer
-  [list ensure-pyobj])
-
-
-(def-pylib-fn PyList_AsTuple
-  "Return value: New reference.
-
-   Return a new tuple object containing the contents of list; equivalent to
-   tuple(list)."
-  Pointer
-  [list ensure-pyobj])
diff --git a/src/libpython_clj/jna/concrete/module.clj b/src/libpython_clj/jna/concrete/module.clj
deleted file mode 100644
index 10b2018..0000000
--- a/src/libpython_clj/jna/concrete/module.clj
+++ /dev/null
@@ -1,124 +0,0 @@
-(ns libpython-clj.jna.concrete.module
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyModuleDef PyMethodDef PyObject]))
-
-
-(def-pylib-fn PyModule_Check
-  "Return true if p is a module object, or a subtype of a module object."
-  Integer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyModule_New
-  "Return value: New reference.
-
-   Return a new module object with the __name__ attribute set to name. The module’s
-   __name__, __doc__, __package__, and __loader__ attributes are filled in (all but
-   __name__ are set to None); the caller is responsible for providing a __file__
-   attribute.
-
-   New in version 3.3.
-
-   Changed in version 3.4: __package__ and __loader__ are set to None."
-  Pointer
-  [name str])
-
-
-(def-pylib-fn PyModule_SetDocString
-  "Set the docstring for module to docstring. This function is called automatically when
-  creating a module from PyModuleDef, using either PyModule_Create or
-  PyModule_FromDefAndSpec.
-
-   New in version 3.5."
-  Integer
-  [module ensure-pyobj]
-  [docstring str])
-
-
-(def-pylib-fn PyModule_AddFunctions
-  "Add the functions from the NULL terminated functions array to module. Refer to the
-  PyMethodDef documentation for details on individual entries (due to the lack of a
-  shared module namespace, module level “functions” implemented in C typically receive
-  the module as their first parameter, making them similar to instance methods on Python
-  classes). This function is called automatically when creating a module from
-  PyModuleDef, using either PyModule_Create or PyModule_FromDefAndSpec."
-  Integer
-  [module ensure-pyobj]
-  [functions (partial jna/ensure-type PyMethodDef)])
-
-
-(def-pylib-fn PyModule_GetDict
-  "Return value: Borrowed reference.
-
-   Return the dictionary object that implements module’s namespace; this object is the
-   same as the __dict__ attribute of the module object. If module is not a module
-   object (or a subtype of a module object), SystemError is raised and NULL is
-   returned.
-
-   It is recommended extensions use other PyModule_*() and PyObject_*() functions
-   rather than directly manipulate a module’s __dict__."
-  Pointer
-  [module ensure-pyobj])
-
-
-(def-pylib-fn PyModule_GetNameObject
-  "Return value: New reference.
-
-   Return module’s __name__ value. If the module does not provide one, or if it is not a
-   string, SystemError is raised and NULL is returned.
-
-   New in version 3.3."
-  Pointer
-  [module ensure-pyobj])
-
-
-(def-pylib-fn PyModule_GetState
-  "Return the “state” of the module, that is, a pointer to the block of memory allocated
-  at module creation time, or NULL. See PyModuleDef.m_size."
-  Pointer
-  [module ensure-pyobj])
-
-
-(def-pylib-fn PyModule_GetDef
-  "Return a pointer to the PyModuleDef struct from which the module was created, or NULL
-  if the module wasn’t created from a definition."
-  PyModuleDef
-  [module ensure-pyobj])
-
-
-(def-pylib-fn PyModule_AddObject
-  "Add an object to module as name. This is a convenience function which can be used
-  from the module’s initialization function. This steals a reference to value. Return -1
-  on error, 0 on success."
-  Integer
-  [module ensure-pyobj]
-  [name str]
-  [value ensure-pyobj])
-
-
-(def-pylib-fn PyModule_AddIntConstant
-  "Add an integer constant to module as name. This convenience function can be used from
-  the module’s initialization function. Return -1 on error, 0 on success."
-  Integer
-  [module ensure-pyobj]
-  [name str]
-  [value int])
-
-
-(def-pylib-fn PyModule_AddStringConstant
-  "Add a string constant to module as name. This convenience function can be used from
-  the module’s initialization function. The string value must be NULL-terminated. Return
-  -1 on error, 0 on success."
-  Integer
-  [module ensure-pyobj]
-  [name str]
-  [value str])
diff --git a/src/libpython_clj/jna/concrete/numeric/boolean.clj b/src/libpython_clj/jna/concrete/numeric/boolean.clj
deleted file mode 100644
index 3350159..0000000
--- a/src/libpython_clj/jna/concrete/numeric/boolean.clj
+++ /dev/null
@@ -1,38 +0,0 @@
-(ns libpython-clj.jna.concrete.numeric.boolean
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     find-pylib-symbol]
-             :as libpy-base])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject]))
-
-
-(def-pylib-fn PyBool_Check
-  "Return true if o is of type PyBool_Type."
-  Integer
-  [p ensure-pyobj])
-
-
-(defn Py_True
-  "The Python True object. This object has no methods. It needs to be treated just like
-  any other object with respect to reference counts."
-  ^Pointer []
-  (find-pylib-symbol "_Py_TrueStruct"))
-
-
-(defn Py_False
-  "The Python False object. This object has no methods. It needs to be treated just like
-  any other object with respect to reference counts."
-  ^Pointer []
-  (find-pylib-symbol "_Py_FalseStruct"))
-
-
-(def-pylib-fn PyBool_FromLong
-  "Return value: New reference.
-
-   Return a new reference to Py_True or Py_False depending on the truth value of v."
-  Pointer
-  [v int])
diff --git a/src/libpython_clj/jna/concrete/numeric/complex.clj b/src/libpython_clj/jna/concrete/numeric/complex.clj
deleted file mode 100644
index 215e97f..0000000
--- a/src/libpython_clj/jna/concrete/numeric/complex.clj
+++ /dev/null
@@ -1,57 +0,0 @@
-(ns libpython-clj.jna.concrete.numeric.complex
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     find-pylib-symbol]
-             :as libpy-base]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyComplex PyComplex$ByValue
-            PyObject]))
-
-
-(def-pylib-fn PyComplex_Check
-  "Return true if its argument is a PyComplexObject or a subtype of PyComplexObject."
-  Integer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyComplex_FromCComplex
-  "Return value: New reference.
-
-   Create a new Python complex number object from a C Py_complex value."
-  Pointer
-  [v (partial jna/ensure-type PyComplex$ByValue)])
-
-
-(def-pylib-fn PyComplex_FromDoubles
-  "Return value: New reference.
-
-   Return a new PyComplexObject object from real and imag."
-  Pointer
-  [real double]
-  [imag double])
-
-
-(def-pylib-fn PyComplex_RealAsDouble
-  "Return the real part of op as a C double."
-  Double
-  [op ensure-pyobj])
-
-
-(def-pylib-fn PyComplex_ImagAsDouble
-  "Return the imaginary part of op as a C double."
-  Double
-  [op ensure-pyobj])
-
-
-(def-pylib-fn PyComplex_AsCComplex
-  "Return the Py_complex value of the complex number op.
-
-   If op is not a Python complex number object but has a __complex__() method, this
-   method will first be called to convert op to a Python complex number object. Upon
-   failure, this method returns -1.0 as a real value."
-  PyComplex$ByValue
-  [op ensure-pyobj])
diff --git a/src/libpython_clj/jna/concrete/numeric/float.clj b/src/libpython_clj/jna/concrete/numeric/float.clj
deleted file mode 100644
index 14ed289..0000000
--- a/src/libpython_clj/jna/concrete/numeric/float.clj
+++ /dev/null
@@ -1,65 +0,0 @@
-(ns libpython-clj.jna.concrete.numeric.float
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     *python-library*]
-             :as libpy-base])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject DirectMapped]))
-
-
-(def-pylib-fn PyFloat_Check
-  "Return true if its argument is a PyFloatObject or a subtype of PyFloatObject."
-  Integer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyFloat_CheckExact
-  "Return true if its argument is a PyFloatObject, but not a subtype of PyFloatObject."
-  Integer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyFloat_FromString
-  "Return value: New reference.
-
-   Create a PyFloatObject object based on the string value in str, or NULL on failure."
-  Pointer
-  [str ensure-pyobj])
-
-
-(defn PyFloat_FromDouble
-  "Return value: New reference.
-
-   Create a PyFloatObject object from v, or NULL on failure."
-  ^Pointer [v]
-  (DirectMapped/PyFloat_FromDouble (double v)))
-
-
-(defn PyFloat_AsDouble
-  "Return a C double representation of the contents of pyfloat. If pyfloat is not a
-  Python floating point object but has a __float__() method, this method will first be
-  called to convert pyfloat into a float. This method returns -1.0 upon failure, so one
-  should call PyErr_Occurred() to check for errors."
-  ^double [v]
-  (DirectMapped/PyFloat_AsDouble (ensure-pyobj v)))
-
-
-(def-pylib-fn PyFloat_GetInfo
-  "Return value: New reference.
-
-   Return a structseq instance which contains information about the precision, minimum
-   and maximum values of a float. It’s a thin wrapper around the header file float.h."
-  Pointer)
-
-
-(def-pylib-fn PyFloat_GetMax
-  "Return the maximum representable finite float DBL_MAX as C double."
-  Double)
-
-
-(def-pylib-fn PyFloat_GetMin
-  "Return the minimum normalized positive float DBL_MIN as C double."
-  Double)
diff --git a/src/libpython_clj/jna/concrete/numeric/integer.clj b/src/libpython_clj/jna/concrete/numeric/integer.clj
deleted file mode 100644
index 2b80db4..0000000
--- a/src/libpython_clj/jna/concrete/numeric/integer.clj
+++ /dev/null
@@ -1,100 +0,0 @@
-(ns libpython-clj.jna.concrete.numeric.integer
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject DirectMapped]))
-
-
-(def-pylib-fn PyLong_Check
-  "Return true if its argument is a PyLongObject or a subtype of PyLongObject."
-  Integer
-  [p ensure-pyobj])
-
-
-
-(def-pylib-fn PyLong_CheckExact
-  "Return true if its argument is a PyLongObject, but not a subtype of PyLongObject."
-  Integer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyLong_FromLong
-  "Return value: New reference.
-
-   Return a new PyLongObject object from v, or NULL on failure.
-
-   The current implementation keeps an array of integer objects for all integers between
-   -5 and 256, when you create an int in that range you actually just get back a
-   reference to the existing object. So it should be possible to change the value of
-   1. I suspect the behaviour of Python in this case is undefined. :-)"
-  Pointer
-  [v int])
-
-
-(def-pylib-fn PyLong_FromUnsignedLong
-  "Return value: New reference.
-
-   Return a new PyLongObject object from a C unsigned long, or NULL on failure."
-  Pointer
-  [v unchecked-int])
-
-
-(def-pylib-fn PyLong_FromSsize_t
-  "Return value: New reference.
-
-   Return a new PyLongObject object from a C Py_ssize_t, or NULL on failure."
-  Pointer
-  [v jna/size-t])
-
-
-(defn PyLong_FromLongLong
-  "Return value: New reference.
-
-   Return a new PyLongObject object from a C long long, or NULL on failure."
-  ^Pointer [v]
-  (DirectMapped/PyLong_FromLongLong (long v)))
-
-
-(def-pylib-fn PyLong_FromUnsignedLongLong
-  "Return value: New reference.
-
-   Return a new PyLongObject object from a C unsigned long long, or NULL on failure."
-  Pointer
-  [v unchecked-long])
-
-
-(def-pylib-fn PyLong_FromDouble
-  "Return value: New reference.
-
-   Return a new PyLongObject object from the integer part of v, or NULL on failure."
-  Pointer
-  [v double])
-
-
-(def-pylib-fn PyLong_AsLong
-  "Return a C long representation of obj. If obj is not an instance of PyLongObject,
-  first call its __int__() method (if present) to convert it to a PyLongObject.
-
-   Raise OverflowError if the value of obj is out of range for a long.
-
-   Returns -1 on error. Use PyErr_Occurred() to disambiguate."
-  Integer
-  [obj ensure-pyobj])
-
-
-(defn PyLong_AsLongLong
-  "Return a C long long representation of obj. If obj is not an instance of
-  PyLongObject, first call its __int__() method (if present) to convert it to a
-  PyLongObject.
-
-   Raise OverflowError if the value of obj is out of range for a long.
-
-   Returns -1 on error. Use PyErr_Occurred() to disambiguate."
-  ^long [obj]
-  (DirectMapped/PyLong_AsLongLong (ensure-pyobj obj)))
diff --git a/src/libpython_clj/jna/concrete/set.clj b/src/libpython_clj/jna/concrete/set.clj
deleted file mode 100644
index 1370c9f..0000000
--- a/src/libpython_clj/jna/concrete/set.clj
+++ /dev/null
@@ -1,92 +0,0 @@
-(ns libpython-clj.jna.concrete.set
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject]))
-
-
-(def-pylib-fn PySet_Check
-  "Return true if p is a set object or an instance of a subtype."
-  Integer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PyFrozenSet_Check
-  "Return true if p is a frozenset object or an instance of a subtype."
-  Integer
-  [p ensure-pyobj])
-
-
-(def-pylib-fn PySet_New
-  "Return value: New reference.
-
-   Return a new set containing objects returned by the iterable. The iterable may be
-   NULL to create a new empty set. Return the new set on success or NULL on
-   failure. Raise TypeError if iterable is not actually iterable. The constructor is
-   also useful for copying a set (c=set(s))."
-  Pointer
-  [iterable ensure-pyobj])
-
-
-(def-pylib-fn PyFrozenSet_New
-  "Return value: New reference.
-
-   Return a new frozenset containing objects returned by the iterable. The iterable may
-   be NULL to create a new empty frozenset. Return the new set on success or NULL on
-   failure. Raise TypeError if iterable is not actually iterable."
-  Pointer
-  [iterable ensure-pyobj])
-
-
-(def-pylib-fn PySet_Contains
-  "Return 1 if found, 0 if not found, and -1 if an error is encountered. Unlike the
-  Python __contains__() method, this function does not automatically convert unhashable
-  sets into temporary frozensets. Raise a TypeError if the key is unhashable. Raise
-  PyExc_SystemError if anyset is not a set, frozenset, or an instance of a subtype."
-  Integer
-  [anyset ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PySet_Add
-  "Add key to a set instance. Also works with frozenset instances (like
-  PyTuple_SetItem() it can be used to fill-in the values of brand new frozensets before
-  they are exposed to other code). Return 0 on success or -1 on failure. Raise a
-  TypeError if the key is unhashable. Raise a MemoryError if there is no room to
-  grow. Raise a SystemError if set is not an instance of set or its subtype."
-  Integer
-  [set ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PySet_Discard
-  "Return 1 if found and removed, 0 if not found (no action taken), and -1 if an error
-  is encountered. Does not raise KeyError for missing keys. Raise a TypeError if the key
-  is unhashable. Unlike the Python discard() method, this function does not
-  automatically convert unhashable sets into temporary frozensets. Raise
-  PyExc_SystemError if set is not an instance of set or its subtype."
-  Integer
-  [set ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PySet_Pop
-  "Return value: New reference.
-
-   Return a new reference to an arbitrary object in the set, and removes the object from
-   the set. Return NULL on failure. Raise KeyError if the set is empty. Raise a
-   SystemError if set is not an instance of set or its subtype."
-  Pointer
-  [set ensure-pyobj])
-
-
-(def-pylib-fn PySet_Clear
-  "Empty an existing set of all elements."
-  Integer
-  [set ensure-pyobj])
diff --git a/src/libpython_clj/jna/concrete/tuple.clj b/src/libpython_clj/jna/concrete/tuple.clj
deleted file mode 100644
index 5b44e04..0000000
--- a/src/libpython_clj/jna/concrete/tuple.clj
+++ /dev/null
@@ -1,59 +0,0 @@
-(ns libpython-clj.jna.concrete.tuple
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject DirectMapped]))
-
-
-(def-pylib-fn PyTuple_Check
-  "Return true if p is a tuple object or an instance of a subtype of the tuple type."
-  Integer
-  [p ensure-pyobj])
-
-
-(defn PyTuple_New
-  "Return value: New reference.
-
-   Return a new tuple object of size len, or NULL on failure."
-  ^Pointer [^long len]
-  (DirectMapped/PyTuple_New (long len)))
-
-
-(defn PyTuple_GetItem
-  "Return value: Borrowed reference.
-
-   Return the object at position pos in the tuple pointed to by p. If pos is out of
-   bounds, return NULL and sets an IndexError exception."
-  ^Pointer [p pos]
-  (DirectMapped/PyTuple_GetItem (ensure-pyobj p) (long pos)))
-
-
-(def-pylib-fn PyTuple_GetSlice
-  "Return value: New reference.
-
-   Take a slice of the tuple pointed to by p from low to high and return it as a new
-   tuple."
-  Pointer
-  [p ensure-pyobj]
-  [low jna/size-t]
-  [high jna/size-t])
-
-
-(defn PyTuple_SetItem
-  "Insert a reference to object o at position pos of the tuple pointed to by p. Return 0
-  on success.
-
-   Note
-
-   This function “steals” a reference to o"
-  ^long [p pos o]
-  (long (DirectMapped/PyTuple_SetItem (ensure-pyobj p)
-                                      (long pos)
-                                      (ensure-pyobj o))))
diff --git a/src/libpython_clj/jna/concrete/type.clj b/src/libpython_clj/jna/concrete/type.clj
deleted file mode 100644
index 52db927..0000000
--- a/src/libpython_clj/jna/concrete/type.clj
+++ /dev/null
@@ -1,137 +0,0 @@
-(ns libpython-clj.jna.concrete.type
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyMethodDef PyTypeObject PyObject]))
-
-
-(def Py_TPFLAGS_HEAPTYPE (bit-shift-left 1 9))
-
-;; /* Set if the type allows subclassing */
-(def Py_TPFLAGS_BASETYPE (bit-shift-left 1 10))
-
-;; /* Set if the type is 'ready' -- fully initialized */
-(def Py_TPFLAGS_READY (bit-shift-left 1 12))
-
-;; /* Set while the type is being 'readied', to prevent recursive ready calls */
-(def Py_TPFLAGS_READYING (bit-shift-left 1 13))
-
-;; /* Objects support garbage collection (see objimp.h) */
-(def Py_TPFLAGS_HAVE_GC (bit-shift-left 1 14))
-
-;; /* These two bits are preserved for Stackless Python, next after this is 17 */
-;;We aren't going there
-;; #ifdef STACKLESS
-;; #define Py_TPFLAGS_HAVE_STACKLESS_EXTENSION (3UL << 15)
-;; #else
-(def Py_TPFLAGS_HAVE_STACKLESS_EXTENSION 0)
-;; #endif
-
-;; /* Objects support type attribute cache */
-(def Py_TPFLAGS_HAVE_VERSION_TAG (bit-shift-left 1 18))
-(def Py_TPFLAGS_VALID_VERSION_TAG (bit-shift-left 1 19))
-
-;; /* Type is abstract and cannot be instantiated */
-(def Py_TPFLAGS_IS_ABSTRACT (bit-shift-left 1 20))
-
-;; /* These flags are used to determine if a type is a subclass. */
-;; #define Py_TPFLAGS_LONG_SUBCLASS        (1UL << 24)
-;; #define Py_TPFLAGS_LIST_SUBCLASS        (1UL << 25)
-;; #define Py_TPFLAGS_TUPLE_SUBCLASS       (1UL << 26)
-;; #define Py_TPFLAGS_BYTES_SUBCLASS       (1UL << 27)
-;; #define Py_TPFLAGS_UNICODE_SUBCLASS     (1UL << 28)
-;; #define Py_TPFLAGS_DICT_SUBCLASS        (1UL << 29)
-;; #define Py_TPFLAGS_BASE_EXC_SUBCLASS    (1UL << 30)
-;; #define Py_TPFLAGS_TYPE_SUBCLASS        (1UL << 31)
-
-(def Py_TPFLAGS_DEFAULT  (bit-or Py_TPFLAGS_HAVE_STACKLESS_EXTENSION
-                                 Py_TPFLAGS_HAVE_VERSION_TAG))
-
-;;structmember
-
-;; #define T_SHORT     0
-;; #define T_INT       1
-;; #define T_LONG      2
-;; #define T_FLOAT     3
-;; #define T_DOUBLE    4
-;; #define T_STRING    5
-;; #define T_OBJECT    6
-;; /* XXX the ordering here is weird for binary compatibility */
-;; #define T_CHAR      7   /* 1-character string */
-;; #define T_BYTE      8   /* 8-bit signed int */
-;; /* unsigned variants: */
-;; #define T_UBYTE     9
-;; #define T_USHORT    10
-;; #define T_UINT      11
-;; #define T_ULONG     12
-
-;; /* Added by Jack: strings contained in the structure */
-;; #define T_STRING_INPLACE    13
-
-;; /* Added by Lillo: bools contained in the structure (assumed char) */
-;; #define T_BOOL      14
-
-;; #define T_OBJECT_EX 16  /* Like T_OBJECT, but raises AttributeError
-;;                            when the value is NULL, instead of
-;;                            converting to None. */
-;; #define T_LONGLONG      17
-;; #define T_ULONGLONG     18
-
-;; #define T_PYSSIZET      19      /* Py_ssize_t */
-;; #define T_NONE          20      /* Value is always None */
-
-
-;; /* Flags */
-;; #define READONLY            1
-;; #define READ_RESTRICTED     2
-;; #define PY_WRITE_RESTRICTED 4
-;; #define RESTRICTED          (READ_RESTRICTED | PY_WRITE_RESTRICTED)
-
-
-
-(def-pylib-fn PyType_Check
-  "Return true if the object o is a type object, including instances of types derived
-  from the standard type object. Return false in all other cases"
-  Integer
-  [o ensure-pyobj])
-
-
-(def-pylib-fn PyType_GenericNew
-  "Return value: New reference.
-
-   Generic handler for the tp_new slot of a type object. Create a new instance using the
-   type’s tp_alloc slot."
-  Pointer
-  [type (partial jna/ensure-type PyTypeObject)]
-  [args #(when % (ensure-pyobj %))]
-  [kwds #(when % (ensure-pyobj %))])
-
-
-(def-pylib-fn PyType_Ready
-  "Finalize a type object. This should be called on all type objects to finish their
-  initialization. This function is responsible for adding inherited slots from a type’s
-  base class. Return 0 on success, or return -1 and sets an exception on error."
-  Integer
-  [type (partial jna/ensure-type PyTypeObject)])
-
-
-(def-pylib-fn _PyObject_New
-  "Return value: New reference."
-  Pointer
-  [type (partial jna/ensure-type PyTypeObject)])
-
-
-(def-pylib-fn PyObject_Del
-  "Releases memory allocated to an object using PyObject_New() or
-  PyObject_NewVar(). This is normally called from the tp_dealloc handler specified in
-  the object’s type. The fields of the object should not be accessed after this call as
-  the memory is no longer a valid Python object."
-  Pointer
-  [op ensure-pyobj])
diff --git a/src/libpython_clj/jna/concrete/unicode.clj b/src/libpython_clj/jna/concrete/unicode.clj
deleted file mode 100644
index e44000c..0000000
--- a/src/libpython_clj/jna/concrete/unicode.clj
+++ /dev/null
@@ -1,87 +0,0 @@
-(ns libpython-clj.jna.concrete.unicode
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     find-pylib-symbol
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna]
-            [tech.v3.datatype :as dtype]
-            [tech.v3.datatype.nio-buffer :as nio-buffer])
-  (:import [com.sun.jna Pointer]
-           [com.sun.jna.ptr PointerByReference
-            LongByReference IntByReference]
-           [libpython_clj.jna PyObject DirectMapped]))
-
-
-
-(defn PyUnicode_Decode
-  "Return value: New reference.
-
-   Create a Unicode object by decoding size bytes of the encoded string s. encoding and
-   errors have the same meaning as the parameters of the same name in the str() built-in
-   function. The codec to be used is looked up using the Python codec registry. Return
-   NULL if an exception was raised by the codec.
-
-  Signature:
-  PyObject* (const char *s, Py_ssize_t size, const char *encoding, const char *errors)"
-  ^Pointer [s size encoding errors]
-  (DirectMapped/PyUnicode_Decode (nio-buffer/->nio-buffer s)
-                                 (jna/size-t size)
-                                 (str encoding)
-                                 (str errors)))
-
-
-(def-pylib-fn PyUnicode_AsEncodedString
-  "Return value: New reference.
-
-   Encode a Unicode object and return the result as Python bytes object. encoding and
-   errors have the same meaning as the parameters of the same name in the Unicode
-   encode() method. The codec to be used is looked up using the Python codec
-   registry. Return NULL if an exception was raised by the codec.
-   Signature:
-   PyObject* (PyObject *unicode, const char *encoding, const char *errors)"
-  Pointer
-  [s ensure-pyobj]
-  [encoding str]
-  [errors str])
-
-
-(defn size-t-by-reference-type
-  []
-  (if (instance? Long (jna/size-t 0))
-    LongByReference
-    IntByReference))
-
-
-(defn PyUnicode_AsUTF8AndSize
-  "Return a pointer to the UTF-8 encoding of the Unicode object, and store the size of
-   the encoded representation (in bytes) in size. The size argument can be NULL; in this
-   case no size will be stored. The returned buffer always has an extra null byte
-   appended (not included in size), regardless of whether there are any other null code
-   points.
-
-   In the case of an error, NULL is returned with an exception set and no size is stored.
-
-   This caches the UTF-8 representation of the string in the Unicode object, and
-   subsequent calls will return a pointer to the same buffer. The caller is not
-   responsible for deallocating the buffer.
-
-   New in version 3.3.
-
-   Changed in version 3.7: The return type is now const char * rather of char *."
-  ^Pointer [py-obj size-ptr]
-  (DirectMapped/PyUnicode_AsUTF8AndSize (ensure-pyobj py-obj)
-                                        ^LongByReference size-ptr))
-
-
-(def-pylib-fn PyUnicode_AsUTF8
-  "As PyUnicode_AsUTF8AndSize(), but does not store the size.
-
-   New in version 3.3.
-
-   Changed in version 3.7: The return type is now const char * rather of char *."
-  Pointer
-  [py-obj ensure-pyobj])
diff --git a/src/libpython_clj/jna/interpreter.clj b/src/libpython_clj/jna/interpreter.clj
deleted file mode 100644
index 7cdb26b..0000000
--- a/src/libpython_clj/jna/interpreter.clj
+++ /dev/null
@@ -1,400 +0,0 @@
-(ns libpython-clj.jna.interpreter
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     def-no-gil-pylib-fn
-                     as-pyobj
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     find-pylib-symbol
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna]
-            [libpython-clj.jna.protocols.object :as pyobj]
-            [libpython-clj.jna.concrete.unicode :as pyuni]
-            [camel-snake-kebab.core :refer [->kebab-case]]
-            [clojure.tools.logging :as log])
-  (:import [com.sun.jna Pointer Native NativeLibrary]
-           [com.sun.jna.ptr PointerByReference]
-           [libpython_clj.jna PyObject DirectMapped]))
-
-
-
-(def-no-gil-pylib-fn Py_SetProgramName
-  "This function should be called before Py_Initialize() is called for the first time,
-  if it is called at all. It tells the interpreter the value of the argv[0] argument to
-  the main() function of the program (converted to wide characters). This is used by
-  Py_GetPath() and some other functions below to find the Python run-time libraries
-  relative to the interpreter executable. The default value is 'python'. The argument
-  should point to a zero-terminated wide character string in static storage whose
-  contents will not change for the duration of the program’s execution. No code in the
-  Python interpreter will change the contents of this storage.
-
-  Use Py_DecodeLocale() to decode a bytes string to get a wchar_* string."
-  nil
-  [name jna/ensure-ptr])
-
-
-
-
-(def-no-gil-pylib-fn Py_SetPythonHome
-  "Set the default “home” directory, that is, the location of the standard Python
-  libraries. See PYTHONHOME for the meaning of the argument string.
-
-  The argument should point to a zero-terminated character string in static storage
-  whose contents will not change for the duration of the program’s execution. No code
-  in the Python interpreter will change the contents of this storage.
-
-  Use Py_DecodeLocale() to decode a bytes string to get a wchar_* string."
-  nil
-  [home jna/ensure-ptr])
-
-
-
-;; Bugs and caveats: The destruction of modules and objects in modules is done in random
-;; order; this may cause destructors (__del__() methods) to fail when they depend on
-;; other objects (even functions) or modules. Dynamically loaded extension modules
-;; loaded by Python are not unloaded. Small amounts of memory allocated by the Python
-;; interpreter may not be freed (if you find a leak, please report it). Memory tied up
-;; in circular references between objects is not freed. Some memory allocated by
-;; extension modules may not be freed. Some extensions may not work properly if their
-;; initialization routine is called more than once; this can happen if an application
-;; calls Py_Initialize() and Py_Finalize() more than once.
-
-
-(def-no-gil-pylib-fn Py_InitializeEx
-  "This function works like Py_Initialize() if initsigs is 1. If initsigs is 0, it skips
-  initialization registration of signal handlers, which might be useful when Python is
-  embedded."
-  nil
-  [initsigs int])
-
-
-
-(def-no-gil-pylib-fn Py_IsInitialized
-  "Return true (nonzero) when the Python interpreter has been initialized, false (zero)
-  if not. After Py_Finalize() is called, this returns false until Py_Initialize() is
-  called again."
-  Integer)
-
-
-(def-no-gil-pylib-fn Py_FinalizeEx
-  "Undo all initializations made by Py_Initialize() and subsequent use of Python/C API
-  functions, and destroy all sub-interpreters (see Py_NewInterpreter() below) that were
-  created and not yet destroyed since the last call to Py_Initialize(). Ideally, this
-  frees all memory allocated by the Python interpreter. This is a no-op when called for
-  a second time (without calling Py_Initialize() again first). There is no return value;
-  errors during finalization are ignored."
-  Integer)
-
-
-(def-pylib-fn PyRun_SimpleString
-  "This is a simplified interface to PyRun_SimpleStringFlags() below, leaving the
-  PyCompilerFlags* argument set to NULL."
-  Integer
-  [command str])
-
-;; System Functionality
-
-(def-no-gil-pylib-fn PySys_SetArgv
-  "This function works like PySys_SetArgvEx() with updatepath set to 1 unless the python
-  interpreter was started with the -I.
-
-    Use Py_DecodeLocale() to decode a bytes string to get a wchar_* string.
-
-    Changed in version 3.4: The updatepath value depends on -I."
-  nil
-  [argc int]
-  [argv ensure-pyobj])
-
-
-;; chrisn@chrisn-dt:~/dev/cnuernber/libpython-clj$ nm -D /usr/lib/x86_64-linux-gnu/libpython3.7m.so | grep -i _type
-;; Then a few more transformations.
-(def type-symbol-names
-  ["PyAsyncGen_Type"
-   "PyBaseObject_Type"
-   "PyBool_Type"
-   "PyBufferedIOBase_Type"
-   "PyBufferedRandom_Type"
-   "PyBufferedReader_Type"
-   "PyBufferedRWPair_Type"
-   "PyBufferedWriter_Type"
-   "PyByteArrayIter_Type"
-   "PyByteArray_Type"
-   "PyBytesIO_Type"
-   "PyBytesIter_Type"
-   "PyBytes_Type"
-   "PyCallIter_Type"
-   "PyCapsule_Type"
-   "PyCell_Type"
-   "PyCFunction_Type"
-   "PyClassMethodDescr_Type"
-   "PyClassMethod_Type"
-   "PyCode_Type"
-   "PyComplex_Type"
-   "PyContextTokenMissing_Type"
-   "PyContextToken_Type"
-   "PyContext_Type"
-   "PyContextVar_Type"
-   "PyCoro_Type"
-   "PyDictItems_Type"
-   "PyDictIterItem_Type"
-   "PyDictIterKey_Type"
-   "PyDictIterValue_Type"
-   "PyDictKeys_Type"
-   "PyDictProxy_Type"
-   "PyDict_Type"
-   "PyDictValues_Type"
-   "PyEllipsis_Type"
-   "PyEnum_Type"
-   "PyFileIO_Type"
-   "PyFilter_Type"
-   "PyFloat_Type"
-   "PyFrame_Type"
-   "PyFrozenSet_Type"
-   "PyFunction_Type"
-   "PyGen_Type"
-   "PyGetSetDescr_Type"
-   "PyIncrementalNewlineDecoder_Type"
-   "PyInstanceMethod_Type"
-   "PyIOBase_Type"
-   "PyListIter_Type"
-   "PyListRevIter_Type"
-   "PyList_Type"
-   "PyLongRangeIter_Type"
-   "PyLong_Type"
-   "PyMap_Type"
-   "PyMemberDescr_Type"
-   "PyMemoryView_Type"
-   "PyMethodDescr_Type"
-   "PyMethod_Type"
-   "PyModuleDef_Type"
-   "PyModule_Type"
-   "PyODictItems_Type"
-   "PyODictIter_Type"
-   "PyODictKeys_Type"
-   "PyODict_Type"
-   "PyODictValues_Type"
-   "PyProperty_Type"
-   "PyRangeIter_Type"
-   "PyRange_Type"
-   "PyRawIOBase_Type"
-   "PyReversed_Type"
-   "PySeqIter_Type"
-   "PySetIter_Type"
-   "PySet_Type"
-   "PySlice_Type"
-   "PyStaticMethod_Type"
-   "PyStdPrinter_Type"
-   "PySTEntry_Type"
-   "PyStringIO_Type"
-   "PySuper_Type"
-   "PyTextIOBase_Type"
-   "PyTextIOWrapper_Type"
-   "PyTraceBack_Type"
-   "PyTupleIter_Type"
-   "PyTuple_Type"
-   "PyType_Type"
-   "PyUnicodeIter_Type"
-   "PyUnicode_Type"
-   "PyWrapperDescr_Type"
-   "PyZip_Type"])
-
-
-(def type-symbol-table
-  (->> type-symbol-names
-       (map (juxt (comp keyword ->kebab-case) identity))
-       (into {})))
-
-
-;; (-> (pyobj/PyObject_GetAttrString symbol-addr "__name__")
-;;                                   (pyuni/PyUnicode_AsUTF8)
-;;                                   (jna/variable-byte-ptr->string)
-;;                                   keyword)
-
-
-(defn get-type-name
-  [type-pyobj]
-  (if-let [obj-name (pyobj/PyObject_GetAttrString type-pyobj "__name__")]
-    (-> (pyuni/PyUnicode_AsUTF8 obj-name)
-        (jna/variable-byte-ptr->string)
-        ->kebab-case
-        keyword)
-    (do
-      (log/warn "Failed to get typename for object")
-      :typename-lookup-failure)))
-
-
-(defn lookup-type-symbols
-  "Transform the static type-symbol-table map into a map of actual long pointer addresses to
-  {:typename :type-symbol-name}"
-  []
-  (->> type-symbol-table
-       (map (fn [[typename type-symbol-name]]
-              (try
-                (when-let [symbol-addr (find-pylib-symbol type-symbol-name)]
-                  [(Pointer/nativeValue symbol-addr)
-                   {:typename (get-type-name symbol-addr)
-                    :type-symbol-name type-symbol-name}])
-                (catch Throwable e nil))))
-       (remove nil?)
-       (into {})))
-
-
-(defn Py_None
-  []
-  (find-pylib-symbol "_Py_NoneStruct"))
-
-
-(defn Py_NotImplemented
-  []
-  (find-pylib-symbol "_Py_NotImplementedStruct"))
-
-;; Interpreter level protocols
-
-(def-pylib-fn PyMem_Free
-  "Frees the memory block pointed to by p, which must have been returned by a previous
-  call to PyMem_Malloc(), PyMem_Realloc() or PyMem_Calloc(). Otherwise, or if
-  PyMem_Free(p) has been called before, undefined behavior occurs.
-
-   If p is NULL, no operation is performed."
-  nil
-  [data-ptr jna/as-ptr])
-
-
-(defn PyGILState_Ensure
-  "Ensure that the current thread is ready to call the Python C API regardless of
-   the current state of Python, or of the global interpreter lock. This may be called
-   as many times as desired by a thread as long as each call is matched with a call
-   to PyGILState_Release(). In general, other thread-related APIs may be used between
-   PyGILState_Ensure() and PyGILState_Release() calls as long as the thread state is
-   restored to its previous state before the Release(). For example, normal usage of
-   the Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS macros is acceptable.
-
-   The return value is an opaque “handle” to the thread state when PyGILState_Ensure()
-   was called, and must be passed to PyGILState_Release() to ensure Python is left in
-   the same state. Even though recursive calls are allowed, these handles cannot be shared -
-   each unique call to PyGILState_Ensure() must save the handle for its call to PyGILState_Release().
-
-   When the function returns, the current thread will hold the GIL and be able to call
-   arbitrary Python code. Failure is a fatal error."
-  ^long []
-  (DirectMapped/PyGILState_Ensure))
-
-(defn PyGILState_Release
-  "Release any resources previously acquired. After this call, Python’s state will be
-   the same as it was prior to the corresponding PyGILState_Ensure() call (but generally
-   this state will be unknown to the caller, hence the use of the GILState API).
-
-   Every call to PyGILState_Ensure() must be matched by a call to PyGILState_Release()
-   on the same thread."
-  [^long s]
-  (DirectMapped/PyGILState_Release s))
-
-(defn PyGILState_Check
-  "Return 1 if the current thread is holding the GIL and 0 otherwise. This function
-  can be called from any thread at any time. Only if it has had its Python thread
-  state initialized and currently is holding the GIL will it return 1. This is
-  mainly a helper/diagnostic function. It can be useful for example in callback
-  contexts or memory allocation functions when knowing that the GIL is locked can
-  allow the caller to perform sensitive actions or otherwise behave differently."
-  ^long []
-  (DirectMapped/PyGILState_Check))
-
-
-;;Acquire the GIL of the given thread state
-(defn PyEval_RestoreThread
-  "Acquire the global interpreter lock (if it has been created and thread support is
-  enabled) and set the thread state to tstate, which must not be NULL. If the lock has
-  been created, the current thread must not have acquired it, otherwise deadlock ensues.
-
-  Note
-
-  Calling this function from a thread when the runtime is finalizing will terminate the
-  thread, even if the thread was not created by Python. You can use _Py_IsFinalizing()
-  or sys.is_finalizing() to check if the interpreter is in process of being finalized
-  before calling this function to avoid unwanted termination."
-  [tstate]
-  (DirectMapped/PyEval_RestoreThread (ensure-pyobj tstate)))
-
-
-;;Release the GIL, return thread state
-(defn PyEval_SaveThread
-  "Release the global interpreter lock (if it has been created and thread support is
-  enabled) and reset the thread state to NULL, returning the previous thread state
-  (which is not NULL). If the lock has been created, the current thread must have
-  acquired it."
-  ^Pointer []
-  (DirectMapped/PyEval_SaveThread))
-
-
-;;Get current thread state for interpreter
-(def-pylib-fn PyThreadState_Get
-  "Return the current thread state. The global interpreter lock must be held. When the
-  current thread state is NULL, this issues a fatal error (so that the caller needn’t
-  check for NULL)."
-  Pointer)
-
-
-;;Swap threadstate for new interpreter without releasing gil
-(def-pylib-fn PyThreadState_Swap
-  "Swap the current thread state with the thread state given by the argument tstate,
-  which may be NULL. The global interpreter lock must be held and is not released."
-  Pointer
-  [tstate ensure-pyobj])
-
-
-(def start-symbol-table
-  {:py-single-input 256
-   :py-file-input 257
-   :py-eval-input 258})
-
-
-(defn start-symbol
-  [item]
-  (let [value (cond
-               (number? item)
-               (long item)
-               (keyword? item)
-               (get start-symbol-table item 0))
-        valid-values (set (vals start-symbol-table))]
-    (when-not (contains? valid-values value)
-      (throw (ex-info (format "%s is not a start symbol" item) {})))
-    (int value)))
-
-
-(def-pylib-fn PyRun_String
-  "Return value: New reference.
-
-   Execute Python source code from str in the context specified by the objects globals
-   and locals with the compiler flags specified by flags. globals must be a dictionary;
-   locals can be any object that implements the mapping protocol. The parameter start
-   specifies the start token that should be used to parse the source code.
-
-   Returns the result of executing the code as a Python object, or NULL if an exception
-   was raised."
-  Pointer
-  [program str]
-  [start-sym start-symbol]
-  [globals ensure-pydict]
-  ;;Any object that implements the mapping protocol
-  [locals ensure-pydict])
-
-
-(def-pylib-fn PyRun_StringFlags
-  "Return value: New reference.
-
-   Execute Python source code from str in the context specified by the objects globals
-   and locals with the compiler flags specified by flags. globals must be a dictionary;
-   locals can be any object that implements the mapping protocol. The parameter start
-   specifies the start token that should be used to parse the source code.
-
-   Returns the result of executing the code as a Python object, or NULL if an exception
-   was raised."
-  Pointer
-  [program str]
-  [start-sym start-symbol]
-  [globals ensure-pydict]
-  ;;Any object that implements the mapping protocol
-  [locals ensure-pydict]
-  [compilerflags identity])
diff --git a/src/libpython_clj/jna/protocols/buffer.clj b/src/libpython_clj/jna/protocols/buffer.clj
deleted file mode 100644
index 66d94f3..0000000
--- a/src/libpython_clj/jna/protocols/buffer.clj
+++ /dev/null
@@ -1,108 +0,0 @@
-(ns libpython-clj.jna.protocols.buffer
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna]
-            [tech.v3.datatype.nio-buffer :as nio-buffer])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyBuffer]))
-
-
-(def-pylib-fn PyObject_CheckBuffer
-  "Return 1 if obj supports the buffer interface otherwise 0. When 1 is returned, it
-  doesn’t guarantee that PyObject_GetBuffer() will succeed. This function always
-  succeeds."
-  Integer
-  [obj ensure-pyobj])
-
-
-;; /* Maximum number of dimensions */
-(def PyBUF_MAX_NDIM 64)
-
-;; /* Flags for getting buffers */
-(def PyBUF_SIMPLE 0)
-(def PyBUF_WRITABLE 0x0001)
-;; /*  we used to include an E, backwards compatible alias  */
-(def PyBUF_WRITEABLE PyBUF_WRITABLE)
-(def PyBUF_FORMAT 0x0004)
-(def PyBUF_ND 0x0008)
-(def PyBUF_STRIDES (bit-or 0x0010 PyBUF_ND))
-(def PyBUF_C_CONTIGUOUS (bit-or 0x0020 PyBUF_STRIDES))
-(def PyBUF_F_CONTIGUOUS (bit-or 0x0040 PyBUF_STRIDES))
-(def PyBUF_ANY_CONTIGUOUS (bit-or 0x0080 PyBUF_STRIDES))
-(def PyBUF_INDIRECT (bit-or 0x0100 PyBUF_STRIDES))
-
-(def PyBUF_CONTIG (bit-or PyBUF_ND PyBUF_WRITABLE))
-(def PyBUF_CONTIG_RO PyBUF_ND)
-
-(def PyBUF_STRIDED (bit-or PyBUF_STRIDES PyBUF_WRITABLE))
-(def PyBUF_STRIDED_RO PyBUF_STRIDES)
-
-(def PyBUF_RECORDS (bit-or PyBUF_STRIDES PyBUF_WRITABLE PyBUF_FORMAT))
-(def PyBUF_RECORDS_RO (bit-or PyBUF_STRIDES PyBUF_FORMAT))
-
-(def PyBUF_FULL (bit-or PyBUF_INDIRECT PyBUF_WRITABLE PyBUF_FORMAT))
-(def PyBUF_FULL_RO (bit-or PyBUF_INDIRECT PyBUF_FORMAT))
-
-
-(def PyBUF_READ  0x100)
-(def PyBUF_WRITE 0x200)
-
-
-
-(def-pylib-fn PyObject_GetBuffer
-  "Send a request to exporter to fill in view as specified by flags. If the exporter
-  cannot provide a buffer of the exact type, it MUST raise PyExc_BufferError, set
-  view->obj to NULL and return -1.
-
-   On success, fill in view, set view->obj to a new reference to exporter and return
-   0. In the case of chained buffer providers that redirect requests to a single object,
-   view->obj MAY refer to this object instead of exporter (See Buffer Object
-   Structures).
-
-   Successful calls to PyObject_GetBuffer() must be paired with calls to
-   PyBuffer_Release(), similar to malloc() and free(). Thus, after the consumer is done
-   with the buffer, PyBuffer_Release() must be called exactly once."
-  Integer
-  [exporter ensure-pyobj]
-  [view (partial jna/ensure-type PyBuffer)]
-  [flags int])
-
-
-
-(def-pylib-fn PyBuffer_Release
-  "Release the buffer view and decrement the reference count for view->obj. This
-  function MUST be called when the buffer is no longer being used, otherwise reference
-  leaks may occur.
-
-   It is an error to call this function on a buffer that was not obtained via
-   PyObject_GetBuffer()."
-  nil
-  [view (partial jna/ensure-type PyBuffer)])
-
-
-(def-pylib-fn PyBuffer_IsContiguous
-  "Return 1 if the memory defined by the view is C-style (order is 'C') or Fortran-style
-  (order is 'F') contiguous or either one (order is 'A'). Return 0 otherwise. This
-  function always succeeds."
-  Integer
-  [view (partial jna/ensure-type PyBuffer)]
-  [order byte])
-
-
-
-(def-pylib-fn PyBuffer_ToContiguous
-  "Copy len bytes from src to its contiguous representation in buf. order can be 'C' or
-  'F' (for C-style or Fortran-style ordering). 0 is returned on success, -1 on error.
-
-   This function fails if len != src->len."
-  Integer
-  [buf nio-buffer/as-nio-buffer]
-  [src (partial jna/ensure-type PyBuffer)]
-  [len jna/size-t]
-  [order byte])
diff --git a/src/libpython_clj/jna/protocols/iterator.clj b/src/libpython_clj/jna/protocols/iterator.clj
deleted file mode 100644
index c28c8a7..0000000
--- a/src/libpython_clj/jna/protocols/iterator.clj
+++ /dev/null
@@ -1,31 +0,0 @@
-(ns libpython-clj.jna.protocols.iterator
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject]))
-
-
-
-
-(def-pylib-fn PyIter_Check
-  "Return true if the object o supports the iterator protocol."
-  Integer
-  [o ensure-pyobj])
-
-
-
-(def-pylib-fn PyIter_Next
-  "Return value: New reference.
-
-   Return the next value from the iteration o. The object must be an iterator (it is up
-   to the caller to check this). If there are no remaining values, returns NULL with no
-   exception set. If an error occurs while retrieving the item, returns NULL and passes
-   along the exception."
-  Pointer
-  [o ensure-pyobj])
diff --git a/src/libpython_clj/jna/protocols/mapping.clj b/src/libpython_clj/jna/protocols/mapping.clj
deleted file mode 100644
index ff9ebcc..0000000
--- a/src/libpython_clj/jna/protocols/mapping.clj
+++ /dev/null
@@ -1,118 +0,0 @@
-(ns libpython-clj.jna.protocols.mapping
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject]))
-
-
-
-(def-pylib-fn PyMapping_Check
-  "Return 1 if the object provides mapping protocol or supports slicing, and 0
-  otherwise. Note that it returns 1 for Python classes with a __getitem__() method since
-  in general case it is impossible to determine what the type of keys it supports. This
-  function always succeeds."
-  Integer
-  [o ensure-pyobj])
-
-
-(def-pylib-fn PyMapping_Length
-  "Returns the number of keys in object o on success, and -1 on failure. This is
-  equivalent to the Python expression len(o)."
-  size-t-type
-  [o ensure-pyobj])
-
-
-(def-pylib-fn PyMapping_GetItemString
-  "Return value: New reference.
-
-   Return element of o corresponding to the string key or NULL on failure. This is the
-   equivalent of the Python expression o[key]. See also PyObject_GetItem()."
-  Pointer
-  [o ensure-pyobj]
-  [key str])
-
-
-(def-pylib-fn PyMapping_SetItemString
-  "Map the string key to the value v in object o. Returns -1 on failure. This is the
-  equivalent of the Python statement o[key] = v. See also PyObject_SetItem()."
-  Integer
-  [o ensure-pyobj]
-  [key str]
-  [v ensure-pyobj])
-
-
-(def-pylib-fn PyMapping_DelItem
-  "Remove the mapping for the object key from the object o. Return -1 on failure. This
-  is equivalent to the Python statement del o[key]. This is an alias of
-  PyObject_DelItem()."
-  Integer
-  [o ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PyMapping_DelItemString
-  "Remove the mapping for the string key from the object o. Return -1 on failure. This
-  is equivalent to the Python statement del o[key]."
-  Integer
-  [o ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PyMapping_HasKey
-  "Return 1 if the mapping object has the key key and 0 otherwise. This is equivalent to
-  the Python expression key in o. This function always succeeds.
-
-   Note that exceptions which occur while calling the __getitem__() method will get
-   suppressed. To get error reporting use PyObject_GetItem() instead."
-  Integer
-  [o ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PyMapping_HasKeyString
-  "Return 1 if the mapping object has the key key and 0 otherwise. This is equivalent to
-  the Python expression key in o. This function always succeeds.
-
-   Note that exceptions which occur while calling the __getitem__() method and creating
-   a temporary string object will get suppressed. To get error reporting use
-   PyMapping_GetItemString() instead."
-  Integer
-  [o ensure-pyobj]
-  [key str])
-
-
-(def-pylib-fn PyMapping_Keys
-  "Return value: New reference.
-
-   On success, return a list of the keys in object o. On failure, return NULL.
-
-   Changed in version 3.7: Previously, the function returned a list or a tuple."
-  Pointer
-  [o ensure-pyobj])
-
-
-(def-pylib-fn PyMapping_Values
-  "Return value: New reference.
-
-   On success, return a list of the values in object o. On failure, return NULL.
-
-   Changed in version 3.7: Previously, the function returned a list or a tuple."
-  Pointer
-  [o ensure-pyobj])
-
-
-(def-pylib-fn PyMapping_Items
-  "Return value: New reference.
-
-   On success, return a list of the items in object o, where each item is a tuple
-   containing a key-value pair. On failure, return NULL.
-
-   Changed in version 3.7: Previously, the function returned a list or a tuple."
-  Pointer
-  [o ensure-pyobj])
diff --git a/src/libpython_clj/jna/protocols/object.clj b/src/libpython_clj/jna/protocols/object.clj
deleted file mode 100644
index 3ad78aa..0000000
--- a/src/libpython_clj/jna/protocols/object.clj
+++ /dev/null
@@ -1,409 +0,0 @@
-(ns libpython-clj.jna.protocols.object
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     as-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base]
-            [camel-snake-kebab.core :refer [->kebab-case]])
-  (:import [com.sun.jna Pointer Native NativeLibrary]
-           [com.sun.jna.ptr PointerByReference]
-           [libpython_clj.jna PyObject DirectMapped]))
-
-
-;; Object Protocol
-
-(defn Py_DecRef
-  "Decrement the refference count on an object"
-  [py-obj]
-  (DirectMapped/Py_DecRef (ensure-pyobj py-obj)))
-
-
-(defn Py_IncRef
-  "Increment the reference count on an object"
-  [py-obj]
-  (DirectMapped/Py_IncRef (ensure-pyobj py-obj)))
-
-
-;; object.h:937
-(def bool-fn-table
-  (->> {"Py_LT" 0
-        "Py_LE" 1
-        "Py_EQ" 2
-        "Py_NE" 3
-        "Py_GT" 4
-        "Py_GE" 5}
-       (map (fn [[k v]]
-              [(-> (->kebab-case k)
-                   keyword)
-               v]))
-       (into {})))
-(def bool-fn-value-set (set (vals bool-fn-table)))
-
-
-(defn bool-fn-constant
-  [item]
-  (let [value (cond
-                (number? item)
-                (long item)
-                (keyword? item)
-                (get bool-fn-table item))
-        value-set bool-fn-value-set]
-    (when-not (contains? value-set value)
-      (throw (ex-info (format "Unrecognized bool fn %s" item) {})))
-    (int value)))
-
-
-;;There is a good reason to use this function; then you aren't dependent upon the
-;;compile time representation of the pyobject item itself which changes if, for instance,
-;;tracing is enabled.
-(def-pylib-fn PyObject_Type
-  "Return value: New reference.
-
-   When o is non-NULL, returns a type object corresponding to the object type of object
-   o. On failure, raises SystemError and returns NULL. This is equivalent to the Python
-   expression type(o). This function increments the reference count of the return
-   value. There’s really no reason to use this function instead of the common
-   expression o->ob_type, which returns a pointer of type PyTypeObject*, except when
-   the incremented reference count is needed."
-  Pointer
-  [py-obj ensure-pyobj])
-
-
-
-(def-pylib-fn PyObject_Repr
-  "Return value: New reference.
-
-   Compute a string representation of object o. Returns the string representation on
-   success, NULL on failure. This is the equivalent of the Python expression
-   repr(o). Called by the repr() built-in function.
-
-   Changed in version 3.4: This function now includes a debug assertion to help ensure
-   that it does not silently discard an active exception."
-  Pointer
-  [py_obj ensure-pyobj])
-
-
-(def-pylib-fn PyObject_Str
-  "Return value: New reference.
-
-   Compute a string representation of object o. Returns the string representation on
-   success, NULL on failure. This is the equivalent of the Python expression str(o). Called
-   by the str() built-in function and by the print statement."
-  Pointer
-  [py-obj ensure-pyobj])
-
-
-(def-pylib-fn PyObject_HasAttr
-  "Returns 1 if o has the attribute attr_name, and 0 otherwise. This is equivalent to
-  the Python expression hasattr(o, attr_name). This function always succeeds.
-
-   Note that exceptions which occur while calling __getattr__() and __getattribute__()
-   methods will get suppressed. To get error reporting use PyObject_GetAttr() instead."
-  Integer
-  [pyobj ensure-pyobj]
-  [attr-name ensure-pyobj])
-
-
-(defn PyObject_HasAttrString
-  "Returns 1 if o has the attribute attr_name, and 0 otherwise. This is equivalent to
-  the Python expression hasattr(o, attr_name). This function always succeeds.
-
-   Note that exceptions which occur while calling __getattr__() and __getattribute__()
-   methods and creating a temporary string object will get suppressed. To get error
-   reporting use Object_GetAttrString() instead."
-  ^long [pyobj attr-name]
-  (long
-   (DirectMapped/PyObject_HasAttrString (ensure-pyobj pyobj)
-                                        (str attr-name))))
-
-
-(def-pylib-fn PyObject_GetAttr
-  "Return value: New reference.
-
-   Retrieve an attribute named attr_name from object o. Returns the attribute value on
-   success, or NULL on failure. This is the equivalent of the Python expression
-   o.attr_name."
-  Pointer
-  [pyobj ensure-pyobj]
-  [attr-name ensure-pyobj])
-
-
-(defn PyObject_GetAttrString
-  "Return value: New reference.
-
-   Retrieve an attribute named attr_name from object o. Returns the attribute value on
-   success, or NULL on failure. This is the equivalent of the Python expression
-   o.attr_name."
-  ^Pointer [pyobj attr-name]
-  (DirectMapped/PyObject_GetAttrString (ensure-pyobj pyobj)
-                                       (str attr-name)))
-
-
-(def-pylib-fn PyObject_GenericGetAttr
-  "Return value: New reference.
-
-   Generic attribute getter function that is meant to be put into a type object’s
-   tp_getattro slot. It looks for a descriptor in the dictionary of classes in the
-   object’s MRO as well as an attribute in the object’s __dict__ (if present). As
-   outlined in Implementing Descriptors, data descriptors take preference over instance
-   attributes, while non-data descriptors don’t. Otherwise, an AttributeError is
-   raised."
-  Pointer
-  [pyobj ensure-pyobj]
-  [attr-name ensure-pyobj])
-
-
-(def-pylib-fn PyObject_SetAttr
-  "Set the value of the attribute named attr_name, for object o, to the value v. Raise
-   an exception and return -1 on failure; return 0 on success. This is the equivalent of
-   the Python statement o.attr_name = v.
-
-   If v is NULL, the attribute is deleted, however this feature is deprecated in favour
-   of using PyObject_DelAttr()."
-  Integer
-  [pyobj ensure-pyobj]
-  [attr-name ensure-pyobj]
-  [v ensure-pyobj])
-
-
-
-(def-pylib-fn PyObject_SetAttrString
-  "Set the value of the attribute named attr_name, for object o, to the value v. Raise
-   an exception and return -1 on failure; return 0 on success. This is the equivalent of
-   the Python statement o.attr_name = v.
-
-   If v is NULL, the attribute is deleted, however this feature is deprecated in favour
-   of using PyObject_DelAttrString()."
-  Integer
-  [pyobj ensure-pyobj]
-  [attr-name str]
-  [v ensure-pyobj])
-
-
-(def-pylib-fn PyObject_GenericSetAttr
-  "Generic attribute setter and deleter function that is meant to be put into a type
-  object’s tp_setattro slot. It looks for a data descriptor in the dictionary of classes
-  in the object’s MRO, and if found it takes preference over setting or deleting the
-  attribute in the instance dictionary. Otherwise, the attribute is set or deleted in
-  the object’s __dict__ (if present). On success, 0 is returned, otherwise an
-  AttributeError is raised and -1 is returned."
-  Integer
-  [pyobj ensure-pyobj]
-  [attr-name ensure-pyobj]
-  [v ensure-pyobj])
-
-
-(def-pylib-fn PyObject_DelAttr
-  "Delete attribute named attr_name, for object o. Returns -1 on failure. This is the
-  equivalent of the Python statement del o.attr_name."
-  Integer
-  [pyobj ensure-pyobj]
-  [attr-name ensure-pyobj])
-
-
-(def-pylib-fn PyObject_DelAttrString
-  "Delete attribute named attr_name, for object o. Returns -1 on failure. This is the
-  equivalent of the Python statement del o.attr_name."
-  Integer
-  [pyobj ensure-pyobj]
-  [attr-name str])
-
-
-(def-pylib-fn PyObject_GenericGetDict
-  "Return value: New reference.
-
-   A generic implementation for the getter of a __dict__ descriptor. It creates the
-   dictionary if necessary.
-
-   New in version 3.3."
-  Pointer
-  [pyobj ensure-pyobj]
-  [context ensure-pyobj])
-
-
-(def-pylib-fn PyObject_GenericSetDict
-  "A generic implementation for the setter of a __dict__ descriptor. This implementation
-   does not allow the dictionary to be deleted.
-
-   New in version 3.3."
-  Integer
-  [pyobj ensure-pyobj]
-  [context ensure-pyobj])
-
-
-(def-pylib-fn PyObject_RichCompare
-  "Return value: New reference.
-
-   Compare the values of o1 and o2 using the operation specified by opid, which must be
-   one of Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, or Py_GE, corresponding to <, <=, ==, !=,
-   >, or >= respectively. This is the equivalent of the Python expression o1 op o2,
-   where op is the operator corresponding to opid. Returns the value of the comparison
-   on success, or NULL on failure."
-  Pointer
-  [o1 ensure-pyobj]
-  [o2 ensure-pyobj]
-  [opid bool-fn-constant])
-
-
-(def-pylib-fn PyObject_RichCompareBool
-  "Compare the values of o1 and o2 using the operation specified by opid, which must be
-  one of Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, or Py_GE, corresponding to <, <=, ==, !=, >,
-  or >= respectively. Returns -1 on error, 0 if the result is false, 1 otherwise. This
-  is the equivalent of the Python expression o1 op o2, where op is the operator
-  corresponding to opid.
-
-   Note
-
-   If o1 and o2 are the same object, PyObject_RichCompareBool() will always return 1 for
-   Py_EQ and 0 for Py_NE."
-  Integer
-  [o1 ensure-pyobj]
-  [o2 ensure-pyobj]
-  [opid bool-fn-constant])
-
-
-(defn PyCallable_Check
-  "Determine if the object o is callable. Return 1 if the object is callable and 0
-  otherwise. This function always succeeds."
-  ^long [pyobj]
-  (long (DirectMapped/PyCallable_Check (ensure-pyobj pyobj))))
-
-
-(defn PyObject_Call
-  "Return value: New reference.
-
-   Call a callable Python object callable, with arguments given by the tuple args, and
-   named arguments given by the dictionary kwargs.
-
-   args must not be NULL, use an empty tuple if no arguments are needed. If no named
-   arguments are needed, kwargs can be NULL.
-
-   Returns the result of the call on success, or NULL on failure.
-
-   This is the equivalent of the Python expression: callable(*args, **kwargs)."
-  ^Pointer [callable args kwargs]
-  (DirectMapped/PyObject_Call (ensure-pyobj callable)
-                              (ensure-pytuple args)
-                              (as-pyobj kwargs)))
-
-(defn PyObject_CallObject
-  "Return value: New reference.
-
-   Call a callable Python object callable, with arguments given by the tuple args. If no
-   arguments are needed, then args can be NULL.
-
-   Returns the result of the call on success, or NULL on failure.
-
-   This is the equivalent of the Python expression: callable(*args)."
-  ^Pointer [callable args]
-  (DirectMapped/PyObject_CallObject (ensure-pyobj callable)
-                                    (as-pyobj args)))
-
-
-(def-pylib-fn PyObject_Hash
-  "Compute and return the hash value of an object o. On failure, return -1. This is the
-  equivalent of the Python expression hash(o).
-
-   Changed in version 3.2: The return type is now Py_hash_t. This is a signed integer
-   the same size as Py_ssize_t."
-  size-t-type
-  [o ensure-pyobj])
-
-
-(def-pylib-fn PyObject_IsInstance
-  "Return 1 if inst is an instance of the class cls or a subclass of cls, or 0 if
-   not. On error, returns -1 and sets an exception.
-
-   If cls is a tuple, the check will be done against every entry in cls. The result
-   will be 1 when at least one of the checks returns 1, otherwise it will be 0.
-
-   If cls has a __instancecheck__() method, it will be called to determine the
-   subclass status as described in PEP 3119. Otherwise, inst is an instance of cls
-   if its class is a subclass of cls.
-
-   An instance inst can override what is considered its class by having a __class__
-   attribute.
-
-   An object cls can override if it is considered a class, and what its base
-   classes are, by having a __bases__ attribute (which must be a tuple of base
-   classes)."
-  Integer
-  [inst ensure-pyobj]
-  [cls ensure-pyobj])
-
-
-
-(def-pylib-fn PyObject_IsTrue
-  "Returns 1 if the object o is considered to be true, and 0 otherwise. This is
-  equivalent to the Python expression not not o. On failure, return -1."
-  Integer
-  [py-obj ensure-pyobj])
-
-
-(def-pylib-fn PyObject_Not
-  "Returns 0 if the object o is considered to be true, and 1 otherwise. This is
-  equivalent to the Python expression not o. On failure, return -1."
-  Integer
-  [py-obj ensure-pyobj])
-
-
-(def-pylib-fn PyObject_Length
-  "Return the length of object o. If the object o provides either the sequence and
-  mapping protocols, the sequence length is returned. On error, -1 is returned. This is
-  the equivalent to the Python expression len(o)."
-  Integer
-  [py-obj ensure-pyobj])
-
-
-(def-pylib-fn PyObject_GetItem
-  "Return value: New reference.
-
-   Return element of o corresponding to the object key or NULL on failure. This is the
-   equivalent of the Python expression o[key]."
-  Pointer
-  [o ensure-pyobj]
-  [key ensure-pyobj])
-
-
-(def-pylib-fn PyObject_SetItem
-  "Map the object key to the value v. Raise an exception and return -1 on failure;
-   return 0 on success. This is the equivalent of the Python statement o[key] = v."
-  Integer
-  [o ensure-pyobj]
-  [key ensure-pyobj]
-  [v ensure-pyobj])
-
-
-(def-pylib-fn PyObject_DelItem
-  "Remove the mapping for the object key from the object o. Return -1 on failure. This
-  is equivalent to the Python statement del o[key]."
-  Integer
-  [o ensure-pyobj]
-  [key ensure-pyobj])
-
-
-
-(def-pylib-fn PyObject_Dir
-  "Return value: New reference.
-
-   This is equivalent to the Python expression dir(o), returning a (possibly empty)
-   list of strings appropriate for the object argument, or NULL if there was an
-   error. If the argument is NULL, this is like the Python dir(), returning the names
-   of the current locals; in this case, if no execution frame is active then NULL is
-   returned but PyErr_Occurred() will return false."
-  Pointer
-  [o ensure-pyobj])
-
-
-(def-pylib-fn PyObject_GetIter
-  "Return value: New reference.
-
-   This is equivalent to the Python expression iter(o). It returns a new iterator for
-   the object argument, or the object itself if the object is already an
-   iterator. Raises TypeError and returns NULL if the object cannot be iterated."
-  Pointer
-  [o ensure-pyobj])
diff --git a/src/libpython_clj/jna/protocols/sequence.clj b/src/libpython_clj/jna/protocols/sequence.clj
deleted file mode 100644
index 8a77b8f..0000000
--- a/src/libpython_clj/jna/protocols/sequence.clj
+++ /dev/null
@@ -1,178 +0,0 @@
-(ns libpython-clj.jna.protocols.sequence
-  (:require [libpython-clj.jna.base
-             :refer [def-pylib-fn
-                     ensure-pyobj
-                     ensure-pytuple
-                     ensure-pydict
-                     size-t-type
-                     *python-library*]
-             :as libpy-base]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna Pointer]
-           [libpython_clj.jna PyObject DirectMapped]))
-
-
-
-(defn PySequence_Check
-  "Return 1 if the object provides sequence protocol, and 0 otherwise. Note that it
-  returns 1 for Python classes with a __getitem__() method unless they are dict
-  subclasses since in general case it is impossible to determine what the type of keys
-  it supports. This function always succeeds."
-  ^long [o]
-  (long (DirectMapped/PySequence_Check (ensure-pyobj o))))
-
-
-(defn PySequence_Length
-  "Returns the number of objects in sequence o on success, and -1 on failure. This is
-  equivalent to the Python expression len(o)."
-  ^long [o]
-  (long (DirectMapped/PySequence_Length (ensure-pyobj o))))
-
-
-(def-pylib-fn PySequence_Concat
-  "Return value: New reference.
-
-   Return the concatenation of o1 and o2 on success, and NULL on failure. This is the
-   equivalent of the Python expression o1 + o2."
-  Pointer
-  [o1 ensure-pyobj]
-  [o2 ensure-pyobj])
-
-
-(def-pylib-fn PySequence_Repeat
-  "Return value: New reference.
-
-   Return the result of repeating sequence object o count times, or NULL on
-   failure. This is the equivalent of the Python expression o * count."
-  Pointer
-  [o ensure-pyobj]
-  [count jna/size-t])
-
-
-(def-pylib-fn PySequence_InPlaceConcat
-  "Return value: New reference.
-
-   Return the concatenation of o1 and o2 on success, and NULL on failure. The operation
-   is done in-place when o1 supports it. This is the equivalent of the Python expression
-   o1 += o2."
-  Pointer
-  [o1 ensure-pyobj]
-  [o2 ensure-pyobj])
-
-
-(def-pylib-fn PySequence_InPlaceRepeat
-  "Return value: New reference.
-
-   Return the result of repeating sequence object o count times, or NULL on failure. The
-   operation is done in-place when o supports it. This is the equivalent of the Python
-   expression o *= count."
-  Pointer
-  [o ensure-pyobj]
-  [count jna/size-t])
-
-
-(defn PySequence_GetItem
-  "Return value: New reference.
-
-   Return the ith element of o, or NULL on failure. This is the equivalent of the Python
-   expression o[i]."
-  ^Pointer [o i]
-  (DirectMapped/PySequence_GetItem (ensure-pyobj o) (jna/size-t i)))
-
-
-(def-pylib-fn PySequence_GetSlice
-  "Return value: New reference.
-
-   Return the slice of sequence object o between i1 and i2, or NULL on failure. This is
-   the equivalent of the Python expression o[i1:i2]."
-  Pointer
-  [o ensure-pyobj]
-  [i1 jna/size-t]
-  [i2 jna/size-t])
-
-
-(def-pylib-fn PySequence_SetItem
-  "Assign object v to the ith element of o. Raise an exception and return -1 on failure;
-   return 0 on success. This is the equivalent of the Python statement o[i] = v. This
-   function does not steal a reference to v.
-
-   If v is NULL, the element is deleted, however this feature is deprecated in favour
-   of using PySequence_DelItem()."
-  Integer
-  [o ensure-pyobj]
-  [i jna/size-t]
-  [v ensure-pyobj])
-
-
-(def-pylib-fn PySequence_DelItem
-  "Delete the ith element of object o. Returns -1 on failure. This is the equivalent of
-  the Python statement del o[i]."
-  Integer
-  [o ensure-pyobj]
-  [i jna/size-t])
-
-
-(def-pylib-fn PySequence_SetSlice
-  "Assign the sequence object v to the slice in sequence object o from i1 to i2. This is
-  the equivalent of the Python statement o[i1:i2] = v."
-  Integer
-  [o ensure-pyobj]
-  [i1 jna/size-t]
-  [i2 jna/size-t]
-  [v ensure-pyobj])
-
-
-(def-pylib-fn PySequence_DelSlice
-  "Delete the slice in sequence object o from i1 to i2. Returns -1 on failure. This is
-  the equivalent of the Python statement del o[i1:i2]."
-  Integer
-  [o ensure-pyobj]
-  [i1 jna/size-t]
-  [i2 jna/size-t])
-
-
-(def-pylib-fn PySequence_Count
-  "Return the number of occurrences of value in o, that is, return the number of keys
-  for which o[key] == value. On failure, return -1. This is equivalent to the Python
-  expression o.count(value)."
-  size-t-type
-  [o ensure-pyobj]
-  [value ensure-pyobj])
-
-
-(def-pylib-fn PySequence_Contains
-  "Determine if o contains value. If an item in o is equal to value, return 1, otherwise
-  return 0. On error, return -1. This is equivalent to the Python expression value in
-  o."
-  Integer
-  [o ensure-pyobj]
-  [value ensure-pyobj])
-
-
-(def-pylib-fn PySequence_Index
-  "Return the first index i for which o[i] == value. On error, return -1. This is
-  equivalent to the Python expression o.index(value)."
-  size-t-type
-  [o ensure-pyobj]
-  [value ensure-pyobj])
-
-
-(def-pylib-fn PySequence_List
-  "Return value: New reference.
-
-   Return a list object with the same contents as the sequence or iterable o, or NULL on
-   failure. The returned list is guaranteed to be new. This is equivalent to the Python
-   expression list(o)."
-  Pointer
-  [o ensure-pyobj])
-
-
-(def-pylib-fn PySequence_Tuple
-  "Return value: New reference.
-
-   Return a tuple object with the same contents as the sequence or iterable o, or NULL
-   on failure. If o is a tuple, a new reference will be returned, otherwise a tuple will
-   be constructed with the appropriate contents. This is equivalent to the Python
-   expression tuple(o)."
-  Pointer
-  [o ensure-pyobj])
diff --git a/src/libpython_clj/py_modules/numpy.clj b/src/libpython_clj/py_modules/numpy.clj
deleted file mode 100644
index 81bbf46..0000000
--- a/src/libpython_clj/py_modules/numpy.clj
+++ /dev/null
@@ -1,15 +0,0 @@
-(ns libpython-clj.py-modules.numpy
-  (:require [libpython-clj.python :as py]
-            [libpython-clj.require :refer [require-python]])
-  (:refer-clojure :exclude [test take str sort short require repeat partition
-                            mod min max long load int identity float empty double
-                            conj char cast byte]))
-
-
-(require-python '[numpy :refer :* :reload])
-
-
-(comment
-  (linspace 2 3)
-  (py/call-kw linspace [2 3] {:num 5})
-  )
diff --git a/src/libpython_clj/python.clj b/src/libpython_clj/python.clj
deleted file mode 100644
index c5b21f1..0000000
--- a/src/libpython_clj/python.clj
+++ /dev/null
@@ -1,539 +0,0 @@
-(ns libpython-clj.python
-  "Base low-level namespace for accessing Python via Clojure.  Higher level interfaces
-   are found in libpython-clj.require specifically require-python and import-python."
-  (:require [tech.v3.datatype.export-symbols :refer [export-symbols]]
-            [libpython-clj.python.interop :as pyinterop]
-            [libpython-clj.python.interpreter :as pyinterp]
-            [libpython-clj.python.object :as pyobj]
-            [libpython-clj.python.bridge :as pybridge]
-            [libpython-clj.python.windows :as win]
-            [libpython-clj.jna :as libpy]
-            ;;Protocol implementations purely for nd-ness
-            [libpython-clj.python.np-array]
-            [tech.v3.jna :as jna])
-  (:import [com.sun.jna Pointer]
-           [com.sun.jna.ptr PointerByReference]
-           [java.lang.reflect Field]
-           [java.io Writer]
-           [libpython_clj.jna PyObject DirectMapped
-            CFunction$KeyWordFunction
-            CFunction$TupleFunction
-            CFunction$NoArgFunction]))
-
-
-(set! *warn-on-reflection* true)
-
-
-(export-symbols libpython-clj.python.protocols
-                python-type
-                dir
-                att-type-map
-                get-attr
-                has-attr?
-                set-attr!
-                callable?
-                has-item?
-                get-item
-                set-item!
-                call
-                call-kw
-                call-attr
-                call-attr-kw
-                len
-                as-map
-                as-list)
-
-
-(export-symbols libpython-clj.python.object
-                ->py-float
-                ->py-long
-                ->py-string
-                ->python
-                ;;Used when you are returning a value from a function.
-                ->python-incref
-                ->jvm
-                make-tuple-fn
-                make-tuple-instance-fn
-                create-class
-                is-instance?
-                hash-code
-                equals?)
-
-
-(defmacro stack-resource-context
-  "Create a stack-based resource context.  All python objects allocated within this
-  context will be released at the termination of this context.
-  !!This means that no python objects can escape from this context!!
-  You must use copy semantics (->jvm) for anything escaping this context.
-  Furthermore, if you are returning generic python objects you may need
-  to call (into {}) or something like that just to ensure that absolutely
-  everything is copied into the jvm."
-  [& body]
-  `(pyobj/stack-resource-context
-    ~@body))
-
-
-(defmacro with-gil
-  "Capture the gil for an extended amount of time.  This can greatly speed up
-  operations as the mutex is captured and held once as opposed to find grained
-  grabbing/releasing of the mutex."
-  [& body]
-  `(pyinterp/with-gil
-     ~@body))
-
-
-(defmacro with-gil-stack-rc-context
-  "Capture the gil, open a resource context.  The resource context is released
-  before the gil is leading to much faster resource collection.  See documentation
-  on `stack-resource-context` for multiple warnings; the most important one being
-  that if a python object escapes this context your program will eventually, at
-  some undefined point in the future crash.  That being said, this is the recommended
-  pathway to use in production contexts where you want defined behavior and timings
-  related to use of python."
-  [& body]
-  `(with-gil
-     (stack-resource-context
-      ~@body)))
-
-
-(defn set-attrs!
-  "Set a sequence of [name value] attributes.
-  Returns item"
-  [item att-seq]
-  (with-gil
-    (doseq [[k v] att-seq]
-      (set-attr! item k v)))
-  item)
-
-
-(defn set-items!
-  "Set a sequence of [name value].
-  Returns item"
-  [item item-seq]
-  (with-gil
-    (doseq [[k v] item-seq]
-      (set-item! item k v)))
-  item)
-
-
-(export-symbols libpython-clj.python.interop
-                libpython-clj-module-name
-                create-bridge-from-att-map)
-
-
-(export-symbols libpython-clj.python.bridge
-                args->pos-kw-args
-                cfn
-                afn
-                as-jvm
-                as-python
-                as-python-incref ;; Used when returning a value from a function to python.
-                ->numpy
-                as-numpy)
-
-
-(defn ->py-dict
-  "Create a python dictionary"
-  [item]
-  (-> (pyobj/->py-dict item)
-      (as-jvm)))
-
-
-(defn ->py-list
-  "Create a python list"
-  [item]
-  (-> (pyobj/->py-list item)
-      (as-jvm)))
-
-
-(defn ->py-tuple
-  "Create a python tuple"
-  [item]
-  (-> (pyobj/->py-tuple item)
-      (as-jvm)))
-
-
-(defn ->py-fn
-  "Make a python function.  If clojure function is passed in the arguments are
-  marshalled from python to clojure, the function called, and the return value will be
-  marshalled back."
-  [item]
-  (-> (pyobj/->py-fn item)
-      (as-jvm)))
-
-
-(defn run-simple-string
-  "Run a string expression returning a map of
-  {:globals :locals}.
-  This uses the global __main__ dict under the covers so it matches the behavior
-  of the cpython implementation with the exception of returning the various maps
-  used.
-
-  Note this will never return the result of the expression:
-  https://mail.python.org/pipermail/python-list/1999-April/018011.html
-
-  Globals, locals may be provided but are not necessary.
-
-  Implemented in cpython as:
-
-    PyObject *m, *d, *v;
-    m = PyImport_AddModule(\"__main__\");
-    if (m == NULL)
-        return -1;
-    d = PyModule_GetDict(m);
-    v = PyRun_StringFlags(command, Py_file_input, d, d, flags);
-    if (v == NULL) {
-        PyErr_Print();
-        return -1;
-    }
-    Py_DECREF(v);
-    return 0;"
-  [program & {:keys [globals locals]}]
-  (->> (pyinterop/run-simple-string program :globals globals :locals locals)
-       (map (fn [[k v]]
-              [k (as-jvm v)]))
-       (into {})))
-
-
-(defn run-string
-  "Wrapper around the python c runtime PyRun_String method.  This requires you to
-  understand what needs to be in the globals and locals dict in order for everything
-  to work out right and for this reason we recommend run-simple-string."
-  [program & {:keys [globals locals]}]
-  (->> (pyinterop/run-string program :globals globals :locals locals)
-       (map (fn [[k v]]
-              [k (as-jvm v)]))
-       (into {})))
-
-
-(defn import-module
-  "Import a python module.  Returns a bridge"
-  [modname]
-  (-> (pyinterop/import-module modname)
-      (as-jvm)))
-
-
-(defn add-module
-  "Add a python module.  Returns a bridge"
-  [modname]
-  (-> (pyinterop/add-module modname)
-      (as-jvm)))
-
-
-(defn module-dict
-  "Get the module dictionary.  Returns bridge."
-  [module]
-  (-> (pyinterop/module-dict module)
-      as-jvm))
-
-
-(defn initialize!
-  "Initialize the python library.  If library path is provided, then the python
-  :library-path Library path of the python library to use.
-  :program-name - optional but will show up in error messages from python.
-  :no-io-redirect - there if you don't want python stdout and stderr redirection
-     to *out* and *err*."
-  [& {:keys [program-name
-             library-path
-             python-home
-             no-io-redirect?
-             python-executable
-             windows-anaconda-activate-bat]}]
-  (when-not @pyinterp/main-interpreter*
-    (pyinterp/initialize! :program-name program-name
-                          :library-path library-path
-                          :python-home python-home
-                          :python-executable python-executable)
-    ;;setup bridge mechansim and io redirection
-    (pyinterop/register-bridge-type!)
-    (when-not no-io-redirect?
-      (pyinterop/setup-std-writer #'*err* "stderr")
-      (pyinterop/setup-std-writer #'*out* "stdout"))
-    (if-not (nil? windows-anaconda-activate-bat)
-      (win/setup-windows-conda! windows-anaconda-activate-bat)))
-  :ok)
-
-
-(defn ptr-refcnt
-  [item]
-  (-> (libpy/as-pyobj item)
-      (libpython_clj.jna.PyObject.)
-      (.ob_refcnt)))
-
-
-(defn finalize!
-  "Finalize the interpreter.  You probably shouldn't call this as it destroys the
-  global interpreter and reinitialization is unsupported cpython."
-  []
-  (pyinterp/finalize!))
-
-
-(defn python-pyerr-fetch-error-handler
-  "Utility code used in with macro"
-  []
-  (let [ptype# (PointerByReference.)
-        pvalue# (PointerByReference.)
-        ptraceback# (PointerByReference.)
-        _# (libpy/PyErr_Fetch ptype# pvalue# ptraceback#)
-        ptype# (-> (jna/->ptr-backing-store ptype#)
-                   (pyobj/wrap-pyobject true))
-        pvalue# (-> (jna/->ptr-backing-store pvalue#)
-                    (pyobj/wrap-pyobject true))
-        ptraceback# (-> (jna/->ptr-backing-store ptraceback#)
-                        (pyobj/wrap-pyobject true))]
-    ;;We own the references so they have to be released.
-    (throw (ex-info "python error in flight"
-                    {:ptype ptype#
-                     :pvalue pvalue#
-                     :ptraceback ptraceback#}))))
-
-
-(defn with-exit-error-handler
-  "Utility code used in with macro"
-  [with-var error]
-  (let [einfo (ex-data error)]
-    (if (every? #(contains? einfo %) [:ptype :pvalue :ptraceback])
-      (let [{ptype :ptype
-             pvalue :pvalue
-             ptraceback :ptraceback} einfo
-            suppress-error? (call-attr with-var "__exit__"
-                                       ptype
-                                       pvalue
-                                       ptraceback)]
-        (when (and ptype pvalue ptraceback
-                   (not suppress-error?))
-          (do
-            ;;Manual incref here because we cannot detach the object
-            ;;from our gc decref hook added during earlier pyerr-fetch handler.
-            (libpy/Py_IncRef ptype)
-            (libpy/Py_IncRef pvalue)
-            (libpy/Py_IncRef ptraceback)
-            (libpy/PyErr_Restore ptype pvalue ptraceback)
-            (pyinterp/check-error-throw))))
-      (do
-        (call-attr with-var "__exit__" nil nil nil)
-        (throw error)))))
-
-
-(defmacro with
-  "Support for the 'with' statement in python:
-  (py/with [item (py/call-attr testcode-module \"WithObjClass\" true fn-list)]
-                    (py/call-attr item \"doit_err\"))"
-  [bind-vec & body]
-  (when-not (= 2 (count bind-vec))
-    (throw (Exception. "Bind vector must have 2 items")))
-  (let [varname (first bind-vec)]
-    `(with-gil
-       (let [~@bind-vec]
-         (with-bindings
-           {#'libpython-clj.python.interpreter/*python-error-handler*
-            python-pyerr-fetch-error-handler}
-           (call-attr ~varname "__enter__")
-           (try
-             (let [retval#
-                   (do
-                     ~@body)]
-               (call-attr ~varname "__exit__" nil nil nil)
-               retval#)
-             (catch Throwable e#
-               (with-exit-error-handler ~varname e#))))))))
-
-
-(defn gc!
-  "Run the system garbage collection facility and then call the cooperative
-  'cleanup python objects' queue"
-  []
-  (System/gc)
-  (with-gil))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-
-(defmacro a$
-  "Call an attribute of an object.  Similar calling conventions to afn except:
-  Keywords must be compile time constants.  So this won't work with 'apply'.  On the
-  other hand, building the positional and kw argmaps happens at compile time as
-  opposed to at runtime.  The attr name can be a symbol.
-
-  DEPRECATION POSSIBLE - use $a."
-  [item attr & args]
-  (let [[pos-args kw-args] (args->pos-kw-args args)]
-    `(call-attr-kw ~item ~(pybridge/key-sym-str->str attr)
-                   ~pos-args ~kw-args)))
-
-
-(defmacro c$
-  "Call an object.  Similar calling conventions to cfn except:
-  Keywords must be compile time constants.  So this won't work with 'apply'.  On the
-  other hand, building the positional and kw argmaps happens at compile time as
-  opposed to at runtime.
-
-  DEPRECATION POSSIBLE - use $c."
-  [item & args]
-  (let [[pos-args kw-args] (args->pos-kw-args args)]
-    `(call-kw ~item ~pos-args ~kw-args)))
-
-
-
-(defmacro $a
-  "Call an attribute of an object.  Similar calling conventions to afn except:
-  Keywords must be compile time constants.  So this won't work with 'apply'.  On the
-  other hand, building the positional and kw argmaps happens at compile time as
-  opposed to at runtime.  The attr name can be a symbol."
-  [item attr & args]
-  `(a$ ~item ~attr ~@args))
-
-
-(defmacro $c
-  "Call an object.  Similar calling conventions to cfn except:
-  Keywords must be compile time constants.  So this won't work with 'apply'.  On the
-  other hand, building the positional and kw argmaps happens at compile time as
-  opposed to at runtime."
-  [item & args]
-  `(c$ ~item ~@args))
-
-
-(defmacro $.
-  "Get the attribute of an object."
-  [item attname]
-  `(get-attr ~item ~(pybridge/key-sym-str->str attname)))
-
-
-(defmacro $..
-  "Get the attribute of an object.  If there are extra args, apply successive
-  get-attribute calls to the arguments."
-  [item attname & args]
-  `(-> (get-attr ~item ~(pybridge/key-sym-str->str attname))
-       ~@(->> args
-              (map (fn [arg]
-                     `(get-attr ~(pybridge/key-sym-str->str arg)))))))
-
-
-(defmacro import-as
-  "Import a module and assign it to a var.  Documentation is included."
-  [module-path varname]
-  `(let [~'mod-data (import-module ~(name module-path))]
-     (def ~varname (import-module ~(name module-path)))
-     (alter-meta! #'~varname assoc :doc (get-attr ~'mod-data "__doc__"))
-     #'~varname))
-
-
-(defmacro from-import
-  "Support for the from a import b,c style of importing modules and symbols in python.
-  Documentation is included."
-  [module-path item & args]
-  `(do
-     (let [~'mod-data (import-module ~(name module-path))]
-       ~@(map (fn [varname]
-                `(let [~'var-data (get-attr ~'mod-data ~(name varname))]
-                   (def ~varname ~'var-data)
-                   (alter-meta! #'~varname assoc :doc (get-attr ~'var-data "__doc__"))
-                   #'~varname))
-              (concat [item] args)))))
-
-
-(defmacro py.-
-  "Class/object getter syntax.  (py.- obj attr) is equivalent to
-  Python's obj.attr syntax."
-  [x arg]
-  (list #'$. x arg))
-
-
-(defmacro py.
-  "Class/object method syntax.  (py. obj method arg1 arg2 ... argN)
-  is equivalent to Python's obj.method(arg1, arg2, ..., argN) syntax."
-  [x & args]
-  (list* (into (vector #'$a x) args)))
-
-(defmacro py*
-  "Special syntax for passing along *args and **kwargs style arguments
-  to methods.
-
-  Usage:
-
-  (py* obj method args kwargs)
-
-  Example:
-
-  (def d (python/dict))
-  d ;;=> {}
-  (def iterable [[:a 1] [:b 2]])
-  (def kwargs {:cat \"dog\" :name \"taco\"})
-  (py* d  update [iterable] kwargs)
-  d ;;=> {\"a\": 1, \"b\": 2, \"cat\": \"dog\", \"name\": \"taco\"}"
-  ([x method args]
-   (list #'call-attr-kw x (str method) args nil))
-  ([x method args kwargs]
-   (list #'call-attr-kw x (str method) args kwargs)))
-
-(defmacro py**
-  "Like py*, but it is assumed that the LAST argument is kwargs."
-  ([x method kwargs]
-   (list #'call-attr-kw x (str method) nil kwargs))
-  ([x method arg & args]
-   (let [args   (into [arg] args)
-         kwargs (last args)
-         args   (vec (pop args))]
-     (list #'call-attr-kw x (str method) args kwargs))))
-
-
-(defn ^:private handle-pydotdot
-  ([x form]
-   (if (list? form)
-     (let [form-data (vec form)
-           [instance-member & args] form-data
-           symbol-str (str instance-member)]
-       (cond
-         (clojure.string/starts-with? symbol-str "-")
-         (list #'py.- x (symbol (subs symbol-str 1 (count symbol-str))))
-
-         (clojure.string/starts-with? symbol-str "**")
-         (list* #'py** x (symbol (subs symbol-str 2 (count symbol-str))) args)
-
-         (clojure.string/starts-with? symbol-str "*")
-         (list* #'py* x (symbol (subs symbol-str 1 (count symbol-str))) args)
-
-         :else ;; assumed to be method invocation
-
-         (list* (into (vector #'py. x instance-member) args))))
-     (handle-pydotdot x (list form))))
-  ([x form & more]
-   (apply handle-pydotdot (handle-pydotdot x form) more)))
-
-
-(defmacro py..
-  "Extended accessor notation, similar to the `..` macro in Clojure.
-
-  (require-python 'sys)
-  (py.. sys -path (append \"/home/user/bin\"))
-
-  is equivalent to Python's
-
-  import sys
-  sys.path.append('/home/user/bin')
-
-  SPECIAL SYNTAX for programmatic *args and **kwargs
-
-  Special syntax is provided to meet the needs required by
-  Python's *args and **kwargs syntax programmatically.
-
-
-  (= (py.. obj (*method args))
-     (py* obj methods args))
-
-  (= (py.. obj (*methods args kwargs))
-     (py* obj method args kwargs))
-
-  (= (py.. obj (**method kwargs))
-     (py** obj kwargs))
-
-  (= (py.. obj (**method arg1 arg2 arg3 ... argN kwargs))
-     (py** obj method arg1 arg2 arg3 ... argN kwargs)
-     (py*  obj method [arg1 arg2 arg3 ... argN] kwargs))
-
-
-  These forms exist for when you need to pass in a map of options
-  in the same way you would use the f(*args, **kwargs) forms in
-  Python."
-  [x & args]
-  (apply handle-pydotdot x args))
diff --git a/src/libpython_clj/python/bridge.clj b/src/libpython_clj/python/bridge.clj
deleted file mode 100644
index fecf9c1..0000000
--- a/src/libpython_clj/python/bridge.clj
+++ /dev/null
@@ -1,1129 +0,0 @@
-(ns libpython-clj.python.bridge
-  "Bridging classes to allow python and java to intermix."
-  (:require [libpython-clj.jna :as libpy]
-            [libpython-clj.jna.concrete.err :as err]
-            [libpython-clj.jna.base :as libpy-base]
-            [libpython-clj.python.protocols
-             :refer [python-type
-                     has-attr? get-attr call-attr
-                     dir att-type-map
-                     pyobject-as-jvm
-                     as-list]
-             :as py-proto]
-            [libpython-clj.python.interpreter
-             :refer
-             [with-gil
-              ensure-bound-interpreter
-              check-error-throw
-              initialize!]
-             :as pyinterp]
-            [libpython-clj.python.object
-             :refer
-             [->jvm
-              ->python
-              stringable?
-              stringable
-              incref
-              incref-wrap-pyobject
-              wrap-pyobject
-              python->jvm-iterable
-              python->jvm-iterator
-              py-none
-              ->py-list
-              ->py-tuple
-              ->py-dict
-              ->py-string
-              ->py-fn]
-             :as pyobj]
-            [libpython-clj.python.interop :as pyinterop
-             :refer
-             [expose-bridge-to-python!
-              pybridge->bridge
-              create-bridge-from-att-map]]
-            [libpython-clj.python.gc :as pygc]
-            [clojure.stacktrace :as st]
-            [tech.v3.jna :as jna]
-            [tech.v3.tensor :as dtt]
-            [tech.v3.datatype.protocols :as dtype-proto]
-            [tech.v3.datatype.casting :as casting]
-            [tech.v3.datatype.argops :as argops]
-            [tech.v3.datatype :as dtype]
-            [clojure.set :as c-set]
-            [clojure.tools.logging :as log])
-  (:import [java.util Map RandomAccess List Map$Entry Iterator UUID]
-           [java.util.concurrent ConcurrentHashMap ConcurrentLinkedQueue]
-           [clojure.lang IFn Symbol Keyword Seqable
-            Fn MapEntry Range LongRange]
-           [tech.v3.datatype NDBuffer ObjectBuffer]
-           [tech.v3.datatype.array_buffer ArrayBuffer]
-           [tech.v3.datatype.native_buffer NativeBuffer]
-           [com.sun.jna Pointer]
-           [tech.resource GCReference]
-           [java.io Writer]
-           [libpython_clj.jna JVMBridge
-            CFunction$KeyWordFunction
-            CFunction$TupleFunction
-            CFunction$NoArgFunction
-            PyFunction
-            PyObject]))
-
-
-(set! *warn-on-reflection* true)
-
-
-(def bridgeable-python-type-set
-  #{:list :dict :tuple :string :int :float})
-
-
-(defn bridgeable-python-type?
-  [pyobj]
-  (with-gil
-    (or (-> (python-type pyobj)
-            bridgeable-python-type-set
-            boolean)
-        (has-attr? pyobj "__iter__")
-        (has-attr? pyobj "__getitem__"))))
-
-
-(defn bridgeable-jvm-type?
-  [jvm-obj]
-  (or (number? jvm-obj)
-      (stringable? jvm-obj)
-      (instance? jvm-obj RandomAccess)
-      (instance? jvm-obj Map)
-      (instance? jvm-obj Iterable)
-      (fn? jvm-obj)))
-
-
-(defn check-py-method-return
-  [^long retval]
-  (when-not (= 0 retval)
-    (check-error-throw)))
-
-
-(defn as-jvm
-  "Bridge a python object into the jvm.  Attempts to build a jvm bridge that 'hides'
-  the python type.  This bridge is lazy and noncaching so use it wisely; it may be
-  better to just copy the type once into the JVM.  Bridging is recursive so any
-  subtypes are also bridged if possible or represented by a hashmap of {:type
-  :value} if not."
-  [item & [options]]
-  (if (or (not item)
-          (= :none-type (python-type item)))
-    nil
-    (py-proto/as-jvm item options)))
-
-
-(defn as-python
-  "Bridge a jvm object into python"
-  [item & [options]]
-  (if (nil? item)
-    (py-none)
-    (py-proto/as-python item options)))
-
-
-(extend-protocol py-proto/PBridgeToJVM
-  Pointer
-  (as-jvm [item options]
-    (pyobject-as-jvm item))
-  PyObject
-  (as-jvm [item options]
-    (pyobject-as-jvm (.getPointer item))))
-
-
-;; These cannot be bridged.  Why would you, anyway?
-
-(defmethod pyobject-as-jvm :int
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :float
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :int-8
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :uint-8
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :int-16
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :uint-16
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :int-32
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :uint-32
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :int-64
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :uint-64
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :float-64
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :float-32
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :str
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :bool
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defmethod pyobject-as-jvm :range
-  [pyobj]
-  (->jvm pyobj))
-
-
-
-(defn mostly-copy-arg
-  "This is the dirty boundary between the languages.  Copying as often faster
-  for simple things but we have to be careful not to attempt a copy of things that
-  are only iterable (and not random access)."
-  [arg]
-  (cond
-    (libpy/as-pyobj arg)
-    (libpy/as-pyobj arg)
-    (dtype/reader? arg)
-    (->python arg)
-    (instance? Map arg)
-    (->python arg)
-    (instance? Iterable arg)
-    (as-python arg)
-    :else
-    (as-python arg)))
-
-
-(defmacro bridge-pyobject
-  [pyobj interpreter & body]
-  `(let [pyobj# ~pyobj
-         interpreter# ~interpreter]
-     (with-meta
-       (reify
-         libpy-base/PToPyObjectPtr
-         (convertible-to-pyobject-ptr? [item#] true)
-         (->py-object-ptr [item#] (libpy/as-pyobj pyobj#))
-         py-proto/PPythonType
-         (get-python-type [item]
-           (with-gil
-             (py-proto/get-python-type pyobj#)))
-         py-proto/PCopyToPython
-         (->python [item# options#] pyobj#)
-         py-proto/PBridgeToPython
-         (as-python [item# options#] pyobj#)
-         py-proto/PBridgeToJVM
-         (as-jvm [item# options#] item#)
-         py-proto/PCopyToJVM
-         (->jvm [item# options#]
-           (with-gil
-             (->jvm pyobj# options#)))
-         py-proto/PPyObject
-         (dir [item#]
-           (with-gil
-             (py-proto/dir pyobj#)))
-         (has-attr? [item# item-name#]
-           (with-gil
-             (py-proto/has-attr? pyobj# item-name#)))
-         (get-attr [item# item-name#]
-           (with-gil
-             (-> (py-proto/get-attr pyobj# item-name#)
-                 as-jvm)))
-         (set-attr! [item# item-name# item-value#]
-           (with-gil
-             (py-proto/set-attr! pyobj# item-name#
-                                 (as-python item-value#))))
-         (callable? [item#]
-           (with-gil
-             (py-proto/callable? pyobj#)))
-         (has-item? [item# item-name#]
-           (with-gil
-             (py-proto/has-item? pyobj# item-name#)))
-         (get-item [item# item-name#]
-           (with-gil
-             (-> (py-proto/get-item pyobj# item-name#)
-                 as-jvm)))
-         (set-item! [item# item-name# item-value#]
-           (with-gil
-             (py-proto/set-item! pyobj# item-name# (as-python item-value#))))
-         py-proto/PPyAttMap
-         (att-type-map [item#]
-           (with-gil
-             (py-proto/att-type-map pyobj#)))
-         py-proto/PyCall
-         (do-call-fn [callable# arglist# kw-arg-map#]
-           (with-gil
-             (let [arglist# (mapv mostly-copy-arg arglist#)
-                   kw-arg-map# (->> kw-arg-map#
-                                    (map (fn [[k# v#]]
-                                           [k# (mostly-copy-arg v#)]))
-                                    (into {}))]
-               (-> (py-proto/do-call-fn pyobj# arglist# kw-arg-map#)
-                   (as-jvm)))))
-         Object
-         (toString [this#]
-           (with-gil
-             (if (= 1 (libpy/PyObject_IsInstance pyobj# (libpy/PyType_Type)))
-               (format "%s.%s"
-                       (->jvm (py-proto/get-attr pyobj# "__module__"))
-                       (->jvm (py-proto/get-attr pyobj# "__name__")))
-               (->jvm (py-proto/call-attr pyobj# "__str__")))))
-         (equals [this# other#]
-           (pyobj/equals? this# other#))
-         (hashCode [this#]
-           (.hashCode ^Object (pyobj/hash-code this#)))
-         ~@body)
-       {:type :pyobject})))
-
-
-(defmethod print-method :pyobject
-  [pyobj w]
-  (.write ^Writer w ^String (.toString ^Object pyobj)))
-
-
-(defn as-tuple
-  "Create a python tuple from a sequence of things."
-  [item-seq]
-  (->> (map as-python item-seq)
-       (->py-tuple)))
-
-
-(defn as-dict
-  "Create a python dict from a sequence of things."
-  [map-data]
-  (->> map-data
-       (map (fn [[k v]]
-              [(as-python k) (as-python v)]))
-       (->py-dict)))
-
-
-
-(defn- py-impl-call-raw
-  [att-name att-map arglist]
-  (if-let [py-fn (get att-map att-name)]
-    (py-proto/do-call-fn py-fn (map as-python arglist) nil)
-    (throw (UnsupportedOperationException.
-            (format "Python object has no attribute: %s"
-                    att-name)))))
-
-
-(defn- py-impl-call-as
-  [att-name att-map arglist]
-  (-> (py-impl-call-raw att-name att-map arglist)
-      as-jvm))
-
-(defn- raw-python-iterator
-  [att-map]
-  (when-not (get att-map "__iter__")
-    (throw (ex-info "Object is not iterable!" {})))
-  (let [py-iter (python->jvm-iterator (get att-map "__iter__") identity)]
-    py-iter))
-
-(defn generic-python-as-map
-  [pyobj]
-  (with-gil
-    (let [interpreter (ensure-bound-interpreter)
-          dict-atts #{"__len__" "__getitem__" "__setitem__" "__iter__" "__contains__"
-                      "__eq__" "__hash__" "clear" "keys" "values"
-                      "__delitem__"}
-          dict-att-map (->> (py-proto/dir pyobj)
-                            (filter dict-atts)
-                            (map (juxt identity (partial py-proto/get-attr pyobj)))
-                            (into {}))
-          py-call (fn [fn-name & args]
-                    (with-gil
-                      (py-impl-call-as fn-name dict-att-map args)))]
-
-      (bridge-pyobject
-       pyobj
-       interpreter
-       Map
-       (clear [item] (py-call "clear"))
-       (containsKey [item k] (boolean (py-call "__contains__" k)))
-       (entrySet
-        [this]
-        (->> (.iterator this)
-             iterator-seq
-             set))
-       (get [this obj-key]
-            (py-call "__getitem__" obj-key))
-       (getOrDefault [item obj-key obj-default-value]
-                     (if (.containsKey item obj-key)
-                       (.get item obj-key)
-                       obj-default-value))
-       (isEmpty [this] (= 0 (.size this)))
-       (keySet [this] (->> (py-call "keys")
-                           set))
-       (put [this k v]
-            (py-call "__setitem__" k v))
-
-       (remove [this k]
-               (py-call "__delitem__" k))
-
-       (size [this]
-             (int (py-call "__len__")))
-
-
-       (values [this]
-               (py-call "values"))
-       Iterable
-       (iterator
-        [this]
-        (let [mapentry-seq
-              (->> (raw-python-iterator dict-att-map)
-                   iterator-seq
-                   (map (fn [pyobj-key]
-                          (with-gil
-                            (let [k (as-jvm pyobj-key)
-                                  v (.get this pyobj-key)
-                                  tuple [k v]]
-                              (MapEntry. k v))))))]
-          (.iterator ^Iterable mapentry-seq)))
-       IFn
-       (invoke [this arg] (.getOrDefault this arg nil))
-       (invoke [this k v] (.put this k v))
-       (applyTo [this arglist]
-                (let [arglist (vec arglist)]
-                  (case (count arglist)
-                    1 (.get this (first arglist))
-                    2 (.put this (first arglist) (second arglist)))))
-       py-proto/PPyObjectBridgeToMap
-       (as-map [item] item)))))
-
-
-(defn generic-python-as-list
-  [pyobj]
-  (with-gil
-    (let [interpreter (ensure-bound-interpreter)
-          dict-atts #{"__len__" "__getitem__" "__setitem__" "__iter__" "__contains__"
-                      "__eq__" "__hash__" "clear" "insert" "pop" "append"
-                      "__delitem__" "sort"}
-          dict-att-map (->> (py-proto/dir pyobj)
-                            (filter dict-atts)
-                            (map (juxt identity (partial py-proto/get-attr pyobj)))
-                            (into {}))
-          py-call (fn [fn-name & args]
-                    (with-gil
-                      (py-impl-call-as fn-name dict-att-map args)))]
-      (bridge-pyobject
-       pyobj
-       interpreter
-       ObjectBuffer
-       (lsize [reader]
-              (long (py-call "__len__")))
-       (readObject [reader idx]
-             (py-call "__getitem__" idx))
-       (sort [reader obj-com]
-             (when-not (= nil obj-com)
-               (throw (ex-info "Python lists do not support comparators" {})))
-             (py-call "sort"))
-       (writeObject [writer idx value]
-              (py-call "__setitem__" idx value))
-       (remove [writer ^int idx]
-               (py-call "__delitem__" idx))
-       (add [mutable idx value]
-            (py-call "insert" idx value))
-       (add [mutable value]
-            (.add mutable (.size mutable) value))))))
-
-
-
-(defmethod pyobject-as-jvm :list
-  [pyobj]
-  (generic-python-as-list pyobj))
-
-
-(defmethod pyobject-as-jvm :tuple
-  [pyobj]
-  (generic-python-as-list pyobj))
-
-
-(defn check-pybool-return
-  [^long retval]
-  (cond
-    (> retval 0) true
-    (= retval 0) false
-    :else
-    (check-error-throw)))
-
-
-(defmethod pyobject-as-jvm :dict
-  [pyobj]
-  (generic-python-as-map pyobj))
-
-
-(defn python-iterable-as-jvm
-  [pyobj]
-  (python->jvm-iterable pyobj ->jvm))
-
-
-(def py-dtype->dtype-map
-  (->> (concat (for [bit-width [8 16 32 64]
-                     unsigned? [true false]]
-                 (str (if unsigned?
-                        "uint"
-                        "int")
-                      bit-width))
-               ["float32" "float64"])
-       (map (juxt identity keyword))
-       (into {})))
-
-
-(def dtype->py-dtype-map
-  (c-set/map-invert py-dtype->dtype-map))
-
-
-(defn obj-dtype->dtype
-  [py-dtype]
-  (when-let [fields (get-attr py-dtype "fields")]
-    (throw (ex-info (format "Cannot convert numpy object with fields: %s"
-                            (call-attr fields "__str__"))
-                    {})))
-  (if-let [retval (->> (py-proto/get-attr py-dtype "name")
-                       (get py-dtype->dtype-map))]
-    retval
-    (throw (ex-info (format "Unable to find datatype: %s"
-                            (py-proto/get-attr py-dtype "name"))
-                    {}))))
-
-
-(defn numpy->desc
-  [np-obj]
-  (with-gil
-    (let [np (pyinterop/import-module "numpy")
-          ctypes (py-proto/as-jvm (get-attr np-obj "ctypes") {})
-          np-dtype (-> (py-proto/as-jvm (get-attr np-obj "dtype") {})
-                       (obj-dtype->dtype))
-          shape (-> (get-attr ctypes "shape")
-                    (as-list)
-                    vec)
-          strides (-> (get-attr ctypes "strides")
-                      (as-list)
-                      vec)
-          long-addr (get-attr ctypes "data")
-          hash-ary {:ctypes-map ctypes}
-          ptr-val long-addr]
-      (-> {:ptr ptr-val
-           :elemwise-datatype np-dtype
-           :shape shape
-           :strides strides}
-          (pygc/track #(get hash-ary :ctypes-map))))))
-
-
-(defmethod py-proto/python-obj-iterator :default
-  [pyobj interpreter]
-  (with-gil
-    (let [iter-fn (py-proto/get-attr pyobj "__iter__")]
-      (python->jvm-iterator iter-fn as-jvm))))
-
-
-(defn args->pos-kw-args
-  "Utility function that, given a list of arguments, separates them
-  into positional and keyword arguments.  Throws an exception if the
-  keyword argument is not followed by any more arguments."
-  [arglist]
-  (loop [args arglist
-         pos-args []
-         kw-args nil
-         found-kw? false]
-    (if-not (seq args)
-      [pos-args kw-args]
-      (let [arg (first args)
-            [pos-args kw-args args found-kw?]
-            (if (keyword? arg)
-              (if-not (seq (rest args))
-                (throw (Exception.
-                        (format "Keyword arguments must be followed by another arg: %s"
-                                (str arglist))))
-                [pos-args (assoc kw-args arg (first (rest args)))
-                 (drop 2 args) true])
-              (if found-kw?
-                (throw (Exception.
-                        (format "Positional arguments are not allowed after keyword arguments: %s"
-                                arglist)))
-                [(conj pos-args (first args))
-                 kw-args
-                 (rest args) found-kw?]))]
-        (recur args pos-args kw-args found-kw?)))))
-
-
-(defn cfn
-  "Call an object.
-  Arguments are passed in positionally.  Any keyword
-  arguments are paired with the next arg, gathered, and passed into the
-  system as *kwargs.
-
-  Not having an argument after a keyword argument is an error."
-  [item & args]
-  (let [[pos-args kw-args] (args->pos-kw-args args)]
-    (py-proto/call-kw item pos-args kw-args)))
-
-
-(defn key-sym-str->str
-  [attr-name]
-  (cond
-    (or (keyword? attr-name)
-        (symbol? attr-name))
-    (name attr-name)
-    (string? attr-name)
-    attr-name
-    :else
-    (throw (Exception.
-            "Only keywords, symbols, or strings can be used to access attributes."))))
-
-
-(defn afn
-  "Call an attribute of an object.
-  Arguments are passed in positionally.  Any keyword
-  arguments are paired with the next arg, gathered, and passed into the
-  system as *kwargs.
-
-  Not having an argument after a keyword argument is an error."
-  [item attr & args]
-  (let [[pos-args kw-args] (args->pos-kw-args args)]
-    (py-proto/call-attr-kw item (key-sym-str->str attr)
-                           pos-args kw-args)))
-
-;;utility fn to generate IFn arities
-(defn- emit-args
-  [bodyf varf]
-   (let [argify (fn [n argfn bodyf]
-                  (let [raw  `[~'this ~@(map #(symbol (str "arg" %))
-                                             (range n))]]
-                    `~(bodyf (argfn raw))))]
-     (concat (for [i (range 21)]
-               (argify i identity bodyf))
-             [(argify 21 (fn [xs]
-                          `[~@(butlast xs) ~'arg20-obj-array])
-                     varf)])))
-
-;;Python specific interop wrapper for IFn invocations.
-(defn- emit-py-args []
-  (emit-args    (fn [args] `(~'invoke [~@args]
-                             (~'with-gil
-                              (~'cfn ~@args))))
-                (fn [args]
-                  `(~'invoke [~@args]
-                    (~'with-gil
-                     (~'apply ~'cfn ~@(butlast args) ~(last args)))))))
-
-(defmacro bridge-callable-pyobject
-  "Like bridge-pyobject, except it populates the implementation of IFn
-   for us, where all arg permutations are supplied, as well as applyTo,
-   and the function invocation is of the form
-   (invoke [this arg] (with-gil (cfn this arg))).
-   If caller supplies an implementation for clojure.lang.IFn or aliased
-   Fn, the macro will use that instead (allowing more control but
-   requiring caller to specify implementations for all desired arities)."
-  [pyobj interpreter & body]
-  (let [fn-specs (when-not (some #{'IFn 'clojure.lang.IFn} body)
-                   `(~'IFn
-                     ~@(emit-py-args)
-                     (~'applyTo [~'this ~'arglist]
-                      (~'with-gil
-                       (~'apply ~'cfn ~'this ~'arglist)))))]
-    `(bridge-pyobject ~pyobj ~interpreter
-                      ~@fn-specs
-                      ~@body)))
-
-(defn generic-python-as-jvm
-  "Given a generic pyobject, wrap it in a read-only map interface
-  where the keys are the attributes."
-  [pyobj]
-  (with-gil
-    (if (= :none-type (python-type pyobj))
-      nil
-      (let [interpreter (ensure-bound-interpreter)]
-        (if (py-proto/callable? pyobj)
-          (bridge-callable-pyobject
-           pyobj
-           interpreter
-           Iterable
-           (iterator [this]
-                     (py-proto/python-obj-iterator pyobj interpreter))
-           py-proto/PPyObjectBridgeToMap
-           (as-map [item]
-                   (generic-python-as-map pyobj))
-           py-proto/PPyObjectBridgeToList
-           (as-list [item]
-                    (generic-python-as-list pyobj))
-           ;;IFn is now supplied for us by the macro.
-           ;;Mark this as executable
-           Fn
-           PyFunction
-           (invokeKeyWords [this tuple-args keyword-args]
-                           (-> (libpy/PyObject_Call pyobj
-                                                    (as-tuple tuple-args)
-                                                    (->py-dict keyword-args))
-                               wrap-pyobject
-                               as-jvm)))
-          (bridge-pyobject
-           pyobj
-           interpreter
-           Iterable
-           (iterator [this]
-                     (py-proto/python-obj-iterator pyobj interpreter))
-           py-proto/PPyObjectBridgeToMap
-           (as-map [item]
-                   (generic-python-as-map pyobj))
-           py-proto/PPyObjectBridgeToList
-           (as-list [item]
-                    (generic-python-as-list pyobj))
-           dtype-proto/PToTensor
-           (as-tensor [item]
-                      (-> (numpy->desc item)
-                          dtt/nd-buffer-descriptor->tensor))))))))
-
-
-(defmethod pyobject-as-jvm :default
-  [pyobj]
-  (generic-python-as-jvm pyobj))
-
-(defmacro wrap-jvm-context
-  [& body]
-  `(try
-     ~@body
-     (catch Throwable e#
-       (libpy/PyErr_SetString
-        libpy/PyExc_Exception
-        (format "%s:%s" (str e#)
-                (with-out-str
-                  (st/print-stack-trace e#)))))))
-
-
-(defn as-python-incref
-  "Convert to python and add a reference.  Necessary for return values from
-  functions as python expects a new reference and the as-python pathway
-  ensures the jvm garbage collector also sees the reference."
-  [item]
-  (-> (as-python item)
-      (pyobj/incref)))
-
-
-(defn- as-py-fn
-  ^Pointer [obj-fn]
-  (pyobj/make-tuple-fn obj-fn
-                       :arg-converter as-jvm
-                       :result-converter as-python-incref))
-
-
-(defonce ^:private jvm-handle-map (ConcurrentHashMap.))
-
-(defn- make-jvm-object-handle
-  ^long [item]
-  (let [^ConcurrentHashMap hash-map jvm-handle-map]
-    (loop [handle (pyinterp/get-object-handle item)]
-      (let [handle (long handle)]
-        (if (not (.containsKey hash-map handle))
-          (do
-            (.put hash-map handle item)
-            handle)
-          (recur (.hashCode (UUID/randomUUID))))))))
-
-(defn- get-jvm-object
-  [handle]
-  (.get ^ConcurrentHashMap jvm-handle-map (long handle)))
-
-(defn- remove-jvm-object
-  [handle]
-  (.remove ^ConcurrentHashMap jvm-handle-map (long handle))
-  nil)
-
-(defn- py-self->jvm-handle
-  [self]
-  (->jvm (py-proto/get-attr self "jvm_handle")))
-
-(defn- py-self->jvm-obj
-  ^Object [self]
-  (-> (py-self->jvm-handle self)
-      get-jvm-object))
-
-(defn- as-tuple-instance-fn
-  [fn-obj]
-  (pyobj/make-tuple-instance-fn fn-obj :result-converter as-python-incref))
-
-
-(defn- self->map
-  ^Map [self]
-  (py-self->jvm-obj self))
-
-
-(defmacro pydelay
-  "Create a delay object that uses only gc reference semantics.  If stack reference
-  semantics happen to be in effect when this delay executes the object may still be
-  reachable by your program when it's reference counts are released leading to
-  bad/crashy behavior.  This ensures that can't happen at the cost of possibly an object
-  sticking around."
-  [& body]
-  `(delay
-     (with-gil
-       (with-bindings {#'pygc/*stack-gc-context* nil}
-         ~@body))))
-
-
-(defonce mapping-type
-  (pydelay
-    (with-gil
-      (let [mod (pyinterop/import-module "collections.abc")
-            map-type (py-proto/get-attr mod "MutableMapping")]
-        ;;In order to make things work ingeneral
-        (pyobj/create-class
-         "jvm-map-as-python"
-         [map-type]
-         {"__init__" (as-tuple-instance-fn
-                      (fn [self jvm-handle]
-                        (py-proto/set-attr! self "jvm_handle" jvm-handle)
-                        nil))
-          "__del__" (as-tuple-instance-fn
-                     #(try
-                        (remove-jvm-object (py-self->jvm-handle %))
-                        (catch Throwable e
-                          (log/warnf e "Error removing object"))))
-          "__contains__" (as-tuple-instance-fn #(.containsKey (self->map %1) (as-jvm %2)))
-          "__eq__" (as-tuple-instance-fn #(.equals (self->map %1) (as-jvm %2)))
-          "__getitem__" (as-tuple-instance-fn
-                         #(do (println "getting" (as-jvm %2))
-                              (.get (self->map %1) (as-jvm %2))))
-          "__setitem__" (as-tuple-instance-fn #(.put (self->map %1) (as-jvm %2) %3))
-          "__delitem__" (as-tuple-instance-fn #(.remove (self->map %1) (as-jvm %2)))
-          "__hash__" (as-tuple-instance-fn #(.hashCode (self->map %1)))
-          "__iter__" (as-tuple-instance-fn #(.iterator ^Iterable (keys (self->map %1))))
-          "__len__" (as-tuple-instance-fn #(.size (self->map %1)))
-          "__str__" (as-tuple-instance-fn #(.toString (self->map %1)))
-          "clear" (as-tuple-instance-fn #(.clear (self->map %1)))
-          "keys" (as-tuple-instance-fn #(seq (.keySet (self->map %1))))
-          "values" (as-tuple-instance-fn #(seq (.values (self->map %1))))
-          "pop" (as-tuple-instance-fn #(.remove (self->map %1) (as-jvm %2)))})))))
-
-
-(defn jvm-map-as-python
-  [^Map jvm-data]
-  (with-gil
-    (py-proto/call (libpy/as-pyobj (deref mapping-type)) (make-jvm-object-handle jvm-data))))
-
-
-(defmethod py-proto/pyobject->jvm :jvm-map-as-python
-  [pyobj]
-  (py-self->jvm-obj pyobj))
-
-
-(defmethod pyobject-as-jvm :jvm-map-as-python
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defn- self->list
-  ^List [self]
-  (py-self->jvm-obj self))
-
-(defonce sequence-type
-  (pydelay
-    (let [mod (pyinterop/import-module "collections.abc")
-          seq-type (py-proto/get-attr mod "MutableSequence")]
-      (pyobj/create-class
-       "jvm-list-as-python"
-       [seq-type]
-       {"__init__" (as-tuple-instance-fn
-                      (fn [self jvm-handle]
-                        (py-proto/set-attr! self "jvm_handle" jvm-handle)
-                        nil))
-        "__del__" (as-tuple-instance-fn
-                   #(try
-                      (remove-jvm-object (py-self->jvm-handle %))
-                      (catch Throwable e
-                        (log/warnf e "Error removing object"))))
-        "__contains__" (as-tuple-instance-fn #(.contains (self->list %1) %2))
-        "__eq__" (as-tuple-instance-fn #(.equals (self->list %1) (->jvm %2)))
-        "__getitem__" (as-tuple-instance-fn #(.get (self->list %1) (int (->jvm %2))))
-        "__setitem__" (as-tuple-instance-fn #(.set (self->list %1)
-                                                   (int (->jvm %2))
-                                                   (as-jvm %3)))
-        "__delitem__" (as-tuple-instance-fn #(.remove (self->list %1)
-                                                      (int (->jvm %2))))
-        "__hash__" (as-tuple-instance-fn #(.hashCode (self->list %1)))
-        "__iter__" (as-tuple-instance-fn #(.iterator (self->list %1)))
-        "__len__" (as-tuple-instance-fn #(.size (self->list %1)))
-        "__str__" (as-tuple-instance-fn #(.toString (self->list %1)))
-        "clear" (as-tuple-instance-fn #(.clear (self->list %1)))
-        "sort" (as-tuple-instance-fn #(.sort (self->list %1) nil))
-        "append" (as-tuple-instance-fn #(.add (self->list %1) %2))
-        "insert" (as-tuple-instance-fn #(.add (self->list %1) (int (->jvm %2)) %3))
-        "pop" (as-tuple-instance-fn
-               (fn [self & args]
-                 (let [jvm-data (self->list self)
-                       args (map ->jvm args)
-                       index (int (if (first args)
-                                     (first args)
-                                     -1))
-                       index (if (< index 0)
-                               (- (.size jvm-data) index)
-                               index)]
-                   #(.remove jvm-data index))))}))))
-
-
-(defn jvm-list-as-python
-  [^List jvm-data]
-  (with-gil
-    (py-proto/call (libpy/as-pyobj (deref sequence-type)) (make-jvm-object-handle jvm-data))))
-
-
-(defmethod py-proto/pyobject->jvm :jvm-list-as-python
-  [pyobj]
-  (py-self->jvm-obj pyobj))
-
-
-(defmethod pyobject-as-jvm :jvm-list-as-python
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defonce iterable-type
-  (pydelay
-    (with-gil
-      (let [mod (pyinterop/import-module "collections.abc")
-            iter-base-cls (py-proto/get-attr mod "Iterable")]
-        (pyobj/create-class
-         "jvm-iterable-as-python"
-         [iter-base-cls]
-         {"__init__" (as-tuple-instance-fn
-                      (fn [self jvm-handle]
-                        (py-proto/set-attr! self "jvm_handle" jvm-handle)
-                        nil))
-          "__del__" (as-tuple-instance-fn
-                     #(try
-                        (remove-jvm-object (py-self->jvm-handle %))
-                        (catch Throwable e
-                          (log/warnf e "Error removing object"))))
-          "__iter__" (as-tuple-instance-fn
-                      #(.iterator ^Iterable (py-self->jvm-obj %)))
-          "__eq__" (as-tuple-instance-fn #(.equals (py-self->jvm-obj %1)
-                                                   (as-jvm %2)))
-          "__hash__" (as-tuple-instance-fn
-                      #(.hashCode (py-self->jvm-obj %)))
-          "__str__" (as-tuple-instance-fn
-                     #(.toString (py-self->jvm-obj %)))})))))
-
-
-(defn jvm-iterable-as-python
-  [^Iterable jvm-data]
-  (with-gil
-    (py-proto/call (libpy/as-pyobj (deref iterable-type)) (make-jvm-object-handle jvm-data))))
-
-
-(defmethod py-proto/pyobject->jvm :jvm-iterable-as-python
-  [pyobj]
-  (py-self->jvm-obj pyobj))
-
-
-(defmethod pyobject-as-jvm :jvm-iterable-as-python
-  [pyobj]
-  (->jvm pyobj))
-
-
-(defonce iterator-type
-  (pydelay
-    (with-gil
-      (let [mod (pyinterop/import-module "collections.abc")
-            iter-base-cls (py-proto/get-attr mod "Iterator")
-            next-fn (fn [self]
-                      (let [^Iterator item (py-self->jvm-obj self)]
-                        (if (.hasNext item)
-                          (-> (.next item)
-                              ;;As python tracks the object in a jvm context
-                              (as-python)
-                              (jna/->ptr-backing-store)
-                              ;;But we are handing the object back to python which is expecting
-                              ;;a new reference.
-                              (pyobj/incref))
-                          (do
-                            (libpy/PyErr_SetNone
-                             (err/PyExc_StopIteration))
-                            nil))))]
-        (pyobj/create-class
-         "jvm-iterator-as-python"
-         [iter-base-cls]
-         {"__init__" (as-tuple-instance-fn
-                      (fn [self jvm-handle]
-                        (py-proto/set-attr! self "jvm_handle" jvm-handle)
-                        nil))
-          "__del__" (as-tuple-instance-fn
-                     #(try
-                        (remove-jvm-object (py-self->jvm-handle %))
-                        (catch Throwable e
-                          (log/warnf e "Error removing object"))))
-          "__next__" (pyobj/make-tuple-instance-fn
-                      next-fn
-                      ;;In this case we are explicitly taking care of all conversions
-                      ;;to python and back so we do not ask for any converters.
-                      :arg-converter nil
-                      :result-converter nil)})))))
-
-
-(defn jvm-iterator-as-python
-  [^Iterator jvm-data]
-  (with-gil
-    (py-proto/call (libpy/as-pyobj (deref iterator-type)) (make-jvm-object-handle jvm-data))))
-
-
-(defmethod py-proto/pyobject->jvm :jvm-iterator-as-python
-  [pyobj]
-  (py-self->jvm-obj pyobj))
-
-
-(defmethod pyobject-as-jvm :jvm-iterator-as-python
-  [pyobj]
-  (->jvm pyobj))
-
-(extend-protocol py-proto/PBridgeToPython
-  Number
-  (as-python [item options] (->python item))
-  String
-  (as-python [item options] (->python item))
-  Character
-  (as-python [item options] (->python item))
-  Symbol
-  (as-python [item options] (->python item))
-  Keyword
-  (as-python [item options] (->python item))
-  Boolean
-  (as-python [item options] (->python item))
-  Range
-  (as-python [item options]
-    (if (casting/integer-type? (dtype/get-datatype item))
-      (->python item options)
-      (jvm-iterable-as-python item)))
-  LongRange
-  (as-python [item options]
-    (->python item options))
-  Iterable
-  (as-python [item options]
-    (cond
-      (instance? Map item)
-      (jvm-map-as-python item)
-      (instance? RandomAccess item)
-      (jvm-list-as-python item)
-      :else
-      (jvm-iterable-as-python item)))
-  NDBuffer
-  (as-python [item options] (py-proto/as-numpy item options))
-  ArrayBuffer
-  (as-python [item options] (py-proto/as-numpy item options))
-  NativeBuffer
-  (as-python [item options] (py-proto/as-numpy item options))
-  Iterator
-  (as-python [item options]
-    (jvm-iterator-as-python item))
-  Object
-  (as-python [item options]
-    (cond
-      (fn? item)
-      (as-py-fn item)
-      :else
-      (throw (Exception. (format "Unable to convert objects of type: %s"
-                                 (type item)))))))
-
-
-(defn datatype->ptr-type-name
-  [dtype]
-  (case dtype
-    :int8 "c_byte"
-    :uint8 "c_ubyte"
-    :int16 "c_short"
-    :uint16 "c_ushort"
-    :int32 "c_long"
-    :uint32 "c_ulong"
-    :int64 "c_longlong"
-    :uint64 "c_ulonglong"
-    :float32 "c_float"
-    :float64 "c_double"))
-
-
-(defn descriptor->numpy
-  [{:keys [ptr shape strides elemwise-datatype] :as buffer-desc}]
-  (with-gil
-    (let [stride-tricks (-> (pyinterop/import-module "numpy.lib.stride_tricks")
-                            as-jvm)
-          ctypes (-> (pyinterop/import-module "ctypes")
-                     as-jvm)
-          np-ctypes (-> (pyinterop/import-module "numpy.ctypeslib")
-                        as-jvm)
-          dtype-size (casting/numeric-byte-width elemwise-datatype)
-          max-stride-idx (argops/argmax strides)
-          buffer-len (* (long (dtype/get-value shape max-stride-idx))
-                        (long (dtype/get-value strides max-stride-idx)))
-          n-elems (quot buffer-len dtype-size)
-          lvalue (long ptr)
-          void-p (call-attr ctypes "c_void_p" lvalue)
-          actual-ptr (call-attr
-                      ctypes "cast" void-p
-                      (call-attr
-                       ctypes "POINTER"
-                       (py-proto/get-attr ctypes
-                                          (datatype->ptr-type-name elemwise-datatype))))
-
-          initial-buffer (call-attr
-                          np-ctypes "as_array"
-                          actual-ptr (->py-tuple [n-elems]))
-
-          retval (call-attr stride-tricks "as_strided"
-                            initial-buffer
-                            (->py-tuple shape)
-                            (->py-tuple strides))]
-      (pygc/track retval #(get buffer-desc :ptr)))))
-
-
-(extend-type Object
-  py-proto/PJvmToNumpy
-  (->numpy [item options]
-    (-> (dtt/ensure-nd-buffer-descriptor item)
-        descriptor->numpy))
-  py-proto/PJvmToNumpyBridge
-  (as-numpy [item options]
-    (when (dtype-proto/convertible-to-native-buffer? item)
-      (when-let [desc (dtt/ensure-nd-buffer-descriptor item)]
-        (descriptor->numpy desc)))))
-
-
-(defn ->numpy
-  "Convert an object to numpy throwing an error if this isn't possible."
-  [item & [options]]
-  (py-proto/->numpy item options))
-
-
-(defn as-numpy
-  "Bridge an object into numpy sharing the backing store.  If it is not possible to
-  do this without copying data then return nil."
-  [item & [options]]
-  (py-proto/as-numpy item options))
diff --git a/src/libpython_clj/python/gc.clj b/src/libpython_clj/python/gc.clj
deleted file mode 100644
index 23b47af..0000000
--- a/src/libpython_clj/python/gc.clj
+++ /dev/null
@@ -1,67 +0,0 @@
-(ns libpython-clj.python.gc
-  "Binding of various sort of gc semantics optimized specifically for
-  libpython-clj.  For general bindings, see tech.resource"
-  (:import [java.util.concurrent ConcurrentHashMap ConcurrentLinkedDeque]
-           [java.lang.ref ReferenceQueue]
-           [tech.resource GCReference]))
-
-
-(set! *warn-on-reflection* true)
-
-
-(defonce ^:dynamic *stack-gc-context* nil)
-(defn stack-context
-  ^ConcurrentLinkedDeque []
-  *stack-gc-context*)
-
-
-(defonce reference-queue-var (ReferenceQueue.))
-(defn reference-queue
-  ^ReferenceQueue []
-  reference-queue-var)
-
-
-(defonce ptr-set-var (ConcurrentHashMap/newKeySet))
-(defn ptr-set
-  ^java.util.Set []
-  ptr-set-var)
-
-
-(defn track
-  [item dispose-fn]
-  (let [ptr-val (GCReference. item (reference-queue) (fn [ptr-val]
-                                                       (.remove (ptr-set) ptr-val)
-                                                       (dispose-fn)))
-        ^ConcurrentLinkedDeque stack-context (stack-context)]
-    ;;We have to keep track of the pointer.  If we do not the pointer gets gc'd then
-    ;;it will not be put on the reference queue when the object itself is gc'd.
-    ;;Nice little gotcha there.
-    (if stack-context
-      (.add stack-context ptr-val)
-      ;;Ensure we don't lose track of the weak reference.  If it gets cleaned up
-      ;;the gc system will fail.
-      (.add (ptr-set) ptr-val))
-    item))
-
-
-(defn clear-reference-queue
-  []
-  (when-let [next-ref (.poll (reference-queue))]
-    (.run ^Runnable next-ref)
-    (recur)))
-
-
-(defn clear-stack-context
-  []
-  (when-let [next-ref (.pollLast (stack-context))]
-    (.run ^Runnable next-ref)
-    (recur)))
-
-
-(defmacro with-stack-context
-  [& body]
-  `(with-bindings {#'*stack-gc-context* (ConcurrentLinkedDeque.)}
-     (try
-       ~@body
-       (finally
-         (clear-stack-context)))))
diff --git a/src/libpython_clj/python/interop.clj b/src/libpython_clj/python/interop.clj
deleted file mode 100644
index 9e9990a..0000000
--- a/src/libpython_clj/python/interop.clj
+++ /dev/null
@@ -1,444 +0,0 @@
-(ns libpython-clj.python.interop
-  "The messy details of actual embedding python in the jvm, aside from interpreter
-  state, go here.  Don't expect a pleasant ride.  If you want to create a new python
-  type or function, something that requires knowledge of the C structures behind
-  everything that knowledge should be encoded in this file."
-  (:require [libpython-clj.jna :as libpy]
-            [libpython-clj.python.logging
-             :refer [log-error log-warn log-info]]
-            [libpython-clj.python.interpreter
-             :refer
-             [get-object-handle
-              with-gil
-              ensure-bound-interpreter
-              ensure-interpreter
-              find-jvm-bridge-entry
-              get-jvm-bridge
-              unregister-bridge!
-              register-bridge!
-              conj-forever!]
-             :as pyinterp]
-            [clojure.stacktrace :as st]
-            [tech.v3.jna :as jna]
-            ;;need memset
-            [tech.v3.datatype.native-buffer :as native-buffer]
-            [tech.v3.resource :as resource]
-            [libpython-clj.python.object
-             :refer [wrap-pyobject incref-wrap-pyobject
-                     incref
-                     ->jvm ->python
-                     ->py-dict
-                     get-attr
-                     py-none
-                     ->py-string
-                     ->py-list
-                     ->py-tuple
-                     ->py-fn
-                     apply-method-def-data!]
-             :as pyobject]
-            [libpython-clj.python.protocols
-             :refer [python-type]
-             :as py-proto])
-  (:import [java.lang.reflect Field Method]
-           [com.sun.jna Pointer Structure CallbackReference]
-           [libpython_clj.jna
-            CFunction$KeyWordFunction
-            CFunction$TupleFunction
-            CFunction$NoArgFunction
-            CFunction$tp_new
-            CFunction$tp_free
-            CFunction$tp_dealloc
-            CFunction$tp_att_getter
-            CFunction$tp_att_setter
-            CFunction$tp_getattr
-            CFunction$tp_getattro
-            CFunction$tp_setattr
-            CFunction$tp_setattro
-            CFunction$tp_hash
-            PyMethodDef PyObject
-            PyMethodDef$ByReference
-            PyTypeObject
-            JVMBridge
-            JVMBridgeType]
-           [java.io Writer]))
-
-
-(set! *warn-on-reflection* true)
-
-
-(def libpython-clj-module-name
-  "Module name of the libpython-clj python model.  Used to find binding-level objects
-  such as the type used for actual jvm bridging objects."
-  "libpython_clj")
-
-
-(defn import-module
-  [modname]
-  (with-gil
-    (-> (libpy/PyImport_ImportModule modname)
-        wrap-pyobject)))
-
-
-(defn add-module
-  [modname]
-  (with-gil
-    (-> (libpy/PyImport_AddModule modname)
-        (incref-wrap-pyobject))))
-
-
-(defn module-dict
-  [module]
-  (with-gil
-    (-> (libpy/PyModule_GetDict module)
-        incref-wrap-pyobject)))
-
-
-(defn run-simple-string
-  "Run a simple string returning boolean 1 or 0.  Note this will never
-  return the result of the expression:
-
-  https://mail.python.org/pipermail/python-list/1999-April/018011.html
-
-  Implemented in cpython as:
-
-    PyObject *m, *d, *v;
-    m = PyImport_AddModule(\"__main__\");
-    if (m == NULL)
-        return -1;
-    d = PyModule_GetDict(m);
-    v = PyRun_StringFlags(command, Py_file_input, d, d, flags);
-    if (v == NULL) {
-        PyErr_Print();
-        return -1;
-    }
-    Py_DECREF(v);
-    return 0;"
-  [program & {:keys [globals locals]}]
-  (with-gil
-    (let [main-mod (libpy/PyImport_AddModule "__main__")
-          globals (or globals (incref-wrap-pyobject
-                               (libpy/PyModule_GetDict main-mod)))
-          locals (or locals globals)
-          retval (-> (libpy/PyRun_String (str program)
-                                         :py-file-input
-                                         globals locals)
-                     wrap-pyobject)]
-      {:globals globals
-       :locals locals})))
-
-
-(defn run-string
-  "If you don't know what you are doing, this will sink you.  See documentation for
-  run-simple-string."
-  [^String program & {:keys [globals locals]}]
-  (with-gil
-    (let [globals (or globals (->py-dict {}))
-          locals (or locals (->py-dict {}))]
-      {:result
-       (wrap-pyobject
-        (libpy/PyRun_String (str program) :py-file-input globals locals))
-       :globals globals
-       :locals locals})))
-
-
-(defn test-cpp-example
-  []
-  (with-gil
-    (let [python-script "result = multiplicand * multiplier\n"
-          local-dict (->py-dict {"multiplicand" 2
-                                 "multiplier" 5})]
-      (run-string python-script :locals local-dict))))
-
-
-(def fieldOffsetMethod
-  (memoize
-   (fn []
-     (doto (.getDeclaredMethod Structure "fieldOffset"
-                               (into-array Class [String]))
-       (.setAccessible true)))))
-
-
-(defn offsetof
-  [structure-instance fieldname]
-  (.invoke ^Method (fieldOffsetMethod)
-           structure-instance
-           (into-array Object [(str fieldname)])))
-
-
-(defn method-def-data-seq->method-def-ref
-  ^PyMethodDef$ByReference [method-def-data-seq]
-  (when (seq method-def-data-seq)
-    (let [n-elems (count method-def-data-seq)
-          method-def-ary (-> (PyMethodDef.)
-                             (.toArray (inc n-elems)))]
-      (->> method-def-data-seq
-           (map-indexed (fn [idx def-data]
-                          (apply-method-def-data!
-                           (aget method-def-ary idx)
-                           def-data)))
-           dorun)
-      (PyMethodDef$ByReference. (.getPointer ^PyMethodDef
-                                             (aget method-def-ary 0))))))
-
-
-(def type-obj-size (-> (PyTypeObject.)
-                       (.size)))
-
-
-(defn register-type!
-  "Register a new type.  Please refer to python documentation for meaning
-  of the variables."
-  [module {:keys [type-name
-                  docstring
-                  method-definitions
-                  tp_flags ;;may be nil
-                  tp_basicsize ;;size of binary type
-                  tp_init ;;init fn, may be nil
-                  tp_new ;;may be nil, will use generic
-                  tp_dealloc ;;may be nil, will use generic
-                  tp_getattr ;;may *not* be nil
-                  tp_setattr ;;may *not* be nil
-                  tp_iter ;;may be nil
-                  tp_iternext ;;may be nil
-                  ]
-           :as type-definition}]
-  (resource/stack-resource-context
-   (when (or (not type-name)
-             (= 0 (count type-name)))
-     (throw (ex-info "Cannot create unnamed type." {})))
-   (let [tp_new (or tp_new
-                    (reify CFunction$tp_new
-                      (pyinvoke [this self varargs kw_args]
-                        (let [retval (libpy/PyType_GenericNew self varargs kw_args)]
-                          retval)
-                        )))
-         module-name (get-attr module "__name__")
-         ;;These get leaked.  Really, types are global objects that cannot be released.
-         ;;Until we can destroy interpreters, it isn't worth the effort to track the
-         ;;type and memory related to the type.
-         docstring-ptr (when docstring (jna/string->ptr-untracked docstring))
-         type-name-ptr (jna/string->ptr-untracked (str module-name "." type-name))
-         tp_flags (long (or tp_flags
-                            (bit-or libpy/Py_TPFLAGS_DEFAULT
-                                    libpy/Py_TPFLAGS_BASETYPE)))
-         ;;We allocate our memory manually here else the system will gc the
-         ;;type object memory when the type goes out of scope.
-         new-mem (jna/malloc-untracked type-obj-size)
-         _ (.setMemory (native-buffer/unsafe) (Pointer/nativeValue new-mem) type-obj-size (unchecked-byte 0))
-         new-type (PyTypeObject. new-mem)]
-     (set! (.tp_name new-type) type-name-ptr)
-     (set! (.tp_init new-type) tp_init)
-     (set! (.tp_doc new-type) docstring-ptr)
-     (set! (.tp_basicsize new-type) tp_basicsize)
-     (set! (.tp_flags new-type) tp_flags)
-     (set! (.tp_new new-type) tp_new)
-     (set! (.tp_dealloc new-type) tp_dealloc)
-     (set! (.tp_getattr new-type) tp_getattr)
-     (set! (.tp_setattr new-type) tp_setattr)
-     (set! (.tp_methods new-type) (method-def-data-seq->method-def-ref
-                                   method-definitions))
-     (when tp_iter
-       (set! (.tp_iter new-type) tp_iter))
-     (when tp_iternext
-       (set! (.tp_iternext new-type) tp_iternext))
-     (let [type-ready (libpy/PyType_Ready new-type)]
-       (if (>= 0 type-ready)
-         (do
-           (libpy/Py_IncRef new-type)
-           (.read new-type)
-           (libpy/PyModule_AddObject module (str type-name "_type") new-type)
-           ;;We are careful to keep track of the static data we give to python.
-           ;;the GC cannot now remove any of this stuff pretty much
-           ;;forever now.
-           (conj-forever! (assoc type-definition
-                                 :tp_name type-name-ptr
-                                 :tp_doc docstring-ptr
-                                 :tp_new tp_new
-                                 :tp_init tp_init
-                                 :tp_dealloc tp_dealloc
-                                 :tp_getattr tp_getattr
-                                 :tp_setattr tp_setattr
-                                 :tp_iter tp_iter
-                                 :tp_iternext tp_iternext))
-           (libpy/Py_IncRef new-type)
-           new-type)
-         (throw (ex-info (format "Type failed to register: %d" type-ready)
-                         {})))))))
-
-(defn pybridge->bridge
-  ^JVMBridge [^Pointer pybridge]
-  (let [bridge-type (JVMBridgeType. pybridge)]
-    (get-jvm-bridge (Pointer/nativeValue pybridge)
-                    (.jvm_interpreter_handle bridge-type))))
-
-
-(defn register-bridge-type!
-  "Register the bridge type and return newly created type."
-  [& {:keys [type-name module]
-      :or {type-name "jvm_bridge"}}]
-  (with-gil
-    (let [module (or module (add-module
-                             libpython-clj-module-name))]
-      (register-type!
-       module
-       {:type-name type-name
-        :docstring "Type used to create the jvmbridge python objects"
-        :method-definitions [{:name "__dir__"
-                              :doc "Custom jvm bridge  __dir__ method"
-                              :function (reify CFunction$NoArgFunction
-                                          (pyinvoke [this self]
-                                            (try
-                                              (-> (pybridge->bridge self)
-                                                  (.dir)
-                                                  (->py-list))
-                                              (catch Throwable e
-                                                (log-error "error calling __dir__:" e )
-                                                (->py-list [])))))}]
-        :tp_basicsize (.size (JVMBridgeType.))
-        :tp_dealloc (reify CFunction$tp_dealloc
-                      (pyinvoke [this self]
-                        (try
-                          (let [bridge (pybridge->bridge self)]
-                            (try (.close bridge)
-                                 (catch Throwable e
-                                   (log-error (format "%s:%s"
-                                               e
-                                               (with-out-str
-                                                 (st/print-stack-trace e))))))
-                            (unregister-bridge! bridge self))
-                          (catch Throwable e
-                            (log-error e)))
-                        (try
-                          (let [^CFunction$tp_free free-func
-                                (.tp_free (PyTypeObject.
-                                           (libpy/PyObject_Type self)))]
-                            (when free-func
-                              (.pyinvoke free-func self)))
-                          (catch Throwable e
-                            (log-error (format "%s:%s"
-                                               e
-                                               (with-out-str
-                                                 (st/print-stack-trace e))))))))
-        :tp_getattr (reify CFunction$tp_getattr
-                      (pyinvoke [this self att-name]
-                        (try
-                          (-> (pybridge->bridge self)
-                              (.getAttr att-name))
-                          (catch Throwable e
-                            (log-error (format "getattr: %s: %s" att-name e))
-                            nil))))
-        :tp_setattr (reify CFunction$tp_setattr
-                      (pyinvoke [this self att-name att-value]
-                        (try
-                          (-> (pybridge->bridge self)
-                              (.setAttr att-name att-value))
-                          (catch Throwable e
-                            (log-error (format "setattr: %s: %s" att-name e))
-                            0))))
-        :tp_iter (reify CFunction$NoArgFunction
-                   (pyinvoke [this self]
-                     (if-let [attr (-> (pybridge->bridge self)
-                                         (.getAttr "__iter__"))]
-                       (libpy/PyObject_CallObject (libpy/as-pyobj attr) nil)
-                       (do
-                         (libpy/PyErr_SetNone (libpy/PyExc_Exception))
-                         nil))))
-        :tp_iternext (reify CFunction$NoArgFunction
-                       (pyinvoke [this self]
-                         (if-let [next (-> (pybridge->bridge self)
-                                           (.getAttr "__next__"))]
-                           (libpy/PyObject_CallObject (libpy/as-pyobj next) nil)
-                           (do
-                             (libpy/PyErr_SetNone (libpy/PyExc_Exception))
-                             nil))))}))))
-
-
-(defn expose-bridge-to-python!
-  "Create a python object for this bridge."
-  [^JVMBridge bridge & [libpython-module]]
-  (with-gil
-    (let [libpython-module (or libpython-module
-                               (add-module libpython-clj-module-name))
-          ^Pointer bridge-type-ptr (get-attr libpython-module
-                                             "jvm_bridge_type")
-          _ (when-not bridge-type-ptr
-              (throw (ex-info "Failed to find bridge type" {})))
-          bridge-type (PyTypeObject. bridge-type-ptr)
-          ^Pointer new-py-obj (libpy/_PyObject_New bridge-type)
-          pybridge (JVMBridgeType. new-py-obj)]
-      (set! (.jvm_interpreter_handle pybridge) (get-object-handle
-                                                (.interpreter bridge)))
-      (set! (.jvm_handle pybridge) (get-object-handle (.wrappedObject bridge)))
-      (.write pybridge)
-      (register-bridge! bridge new-py-obj)
-      (wrap-pyobject new-py-obj))))
-
-
-(defn create-bridge-from-att-map
-  [src-item att-map]
-  (with-gil
-    (let [interpreter (ensure-bound-interpreter)
-          dir-data (->> (keys att-map)
-                        sort
-                        (into-array String))
-          bridge
-          (reify
-            JVMBridge
-            (getAttr [bridge att-name]
-              (if-let [retval (get att-map att-name)]
-                (incref retval)
-                (incref (libpy/Py_None))))
-            (setAttr [bridge att-name att-value]
-              (throw (ex-info "Cannot set attributes" {})))
-            (dir [bridge] dir-data)
-            (interpreter [bridge] interpreter)
-            (wrappedObject [bridge] src-item)
-            py-proto/PCopyToJVM
-            (->jvm [item options] src-item)
-            py-proto/PBridgeToJVM
-            (as-jvm [item options] src-item))]
-      (expose-bridge-to-python! bridge))))
-
-
-(defmethod py-proto/pyobject->jvm :jvm-bridge
-  [pyobj]
-  (-> (pybridge->bridge pyobj)
-      (.wrappedObject)))
-
-
-(defmethod py-proto/pyobject-as-jvm :jvm-bridge
-  [pyobj]
-  (-> (pybridge->bridge pyobj)
-      (.wrappedObject)))
-
-
-(defn create-var-writer
-  "Returns an unregistered bridge"
-  ^Pointer [writer-var varname]
-  (with-gil
-    (create-bridge-from-att-map
-     writer-var
-     {"write" (->python (fn [& args]
-                          (.write ^Writer @writer-var (str (first args)))
-                          ))
-      "flush" (->python (fn [& args]))
-      "isatty" (->python (fn [& args]
-                           (libpy/Py_False)))
-      })))
-
-
-(defn get-or-create-var-writer
-  [writer-var varname]
-  (if-let [existing-writer (find-jvm-bridge-entry (get-object-handle writer-var)
-                                                  (ensure-interpreter))]
-    (:pyobject existing-writer)
-    (create-var-writer writer-var varname)))
-
-
-(defn setup-std-writer
-  [writer-var sys-mod-attname]
-  (with-gil
-    (let [sys-module (import-module "sys")
-          std-out-writer (get-or-create-var-writer writer-var sys-mod-attname)]
-      (py-proto/set-attr! sys-module sys-mod-attname std-out-writer)
-      :ok)))
diff --git a/src/libpython_clj/python/interpreter.clj b/src/libpython_clj/python/interpreter.clj
deleted file mode 100644
index 3155d2f..0000000
--- a/src/libpython_clj/python/interpreter.clj
+++ /dev/null
@@ -1,532 +0,0 @@
-(ns libpython-clj.python.interpreter
-  (:require [libpython-clj.jna :as libpy]
-            [libpython-clj.jna.base :as libpy-base]
-            [libpython-clj.python.gc :as pygc]
-            [libpython-clj.python.logging
-             :refer [log-error log-warn log-info]]
-            [tech.v3.jna :as jna]
-            [clojure.java.shell :refer [sh]]
-            [clojure.java.io :as io]
-            [clojure.string :as s]
-            [clojure.tools.logging :as log]
-            [clojure.data.json :as json]
-            [clojure.pprint :as pp])
-  (:import [libpython_clj.jna JVMBridge PyObject DirectMapped]
-           [java.util.concurrent.atomic AtomicLong]
-           [com.sun.jna Pointer]
-           [com.sun.jna.ptr PointerByReference]
-           [java.io StringWriter]
-           [java.nio.file Paths Path]))
-
-
-(set! *warn-on-reflection* true)
-
-
-(defn get-object-handle
-  [obj]
-  (System/identityHashCode obj))
-
-
-(defn python-system-data [executable]
-  (let [{:keys [out err exit]}
-        (sh executable "-c" "import sys, json;
-print(json.dumps(
-{'platform':          sys.platform,
-  'prefix':           sys.prefix,
-  'base_prefix':      sys.base_prefix,
-  'executable':       sys.executable,
-  'base_exec_prefix': sys.base_exec_prefix,
-  'exec_prefix':      sys.exec_prefix,
-  'version':          list(sys.version_info)[:3]}))")]
-    (when (= 0 exit)
-      (json/read-str out :key-fn keyword))))
-
-(defn python-system-info
-  "An information map about the Python system information provided
-  by a Python executable (string).
-
-  :platform (operating system information)
-  :prefix
-  A string giving the site-specific directory prefix where the platform independent Python files are installed; by default, this is the string '/usr/local'. This can be set at build time with the --prefix argument to the configure script. The main collection of Python library modules is installed in the directory prefix/lib/pythonX.Y while the platform independent header files (all except pyconfig.h) are stored in prefix/include/pythonX.Y, where X.Y is the version number of Python, for example 3.2.
-  Note If a virtual environment is in effect, this value will be changed in site.py to point to the virtual environment. The value for the Python installation will still be available, via base_prefix.
-
-  :base-prefix
-  Set during Python startup, before site.py is run, to the same value as prefix. If not running in a virtual environment, the values will stay the same; if site.py finds that a virtual environment is in use, the values of prefix and exec_prefix will be changed to point to the virtual environment, whereas base_prefix and base_exec_prefix will remain pointing to the base Python installation (the one which the virtual environment was created from).
-
-  :executable
-  A string giving the absolute path of the executable binary for the Python interpreter, on systems where this makes sense. If Python is unable to retrieve the real path to its executable, sys.executable will be an empty string or None.
-
-  :exec-prefix
-  A string giving the site-specific directory prefix where the platform-dependent Python files are installed; by default, this is also '/usr/local'. This can be set at build time with the --exec-prefix argument to the configure script. Specifically, all configuration files (e.g. the pyconfig.h header file) are installed in the directory exec_prefix/lib/pythonX.Y/config, and shared library modules are installed in exec_prefix/lib/pythonX.Y/lib-dynload, where X.Y is the version number of Python, for example 3.2.
-  Note If a virtual environment is in effect, this value will be changed in site.py to point to the virtual environment. The value for the Python installation will still be available, via base_exec_prefix.
-
-  :base-exec-prefix
-  Set during Python startup, before site.py is run, to the same value as exec_prefix. If not running in a virtual environment, the values will stay the same; if site.py finds that a virtual environment is in use, the values of prefix and exec_prefix will be changed to point to the virtual environment, whereas base_prefix and base_exec_prefix will remain pointing to the base Python installation (the one which the virtual environment was created from).
-
-  :version
-  (list python-major python-minor python-micro)"
-  [executable]
-  (let [{platform         :platform
-         prefix           :prefix
-         base-prefix      :base_prefix
-         executable       :executable
-         exec-prefix      :exec_prefix
-         base-exec-prefix :base_exec_prefix
-         version          :version}
-        (python-system-data executable)]
-    {:platform         platform
-     :prefix           prefix
-     :base-prefix      base-prefix
-     :executable       executable
-     :exec-prefix      exec-prefix
-     :base-exec-prefix base-exec-prefix
-     :version          version}))
-
-
-(defn python-library-regex [system-info]
-  (let [{version  :version
-         platform :platform} system-info
-        [major minor micro]  (vec version)]
-    (re-pattern
-     (format
-      (condp (partial =) (keyword platform)
-        ;; TODO: not sure what the strings returned by
-        ;;   ..: mac and windows are for sys.platform
-        :linux   "libpython%s.%sm?.so$"
-        :mac     "libpython%s.%sm?.dylib$"
-        :darwin  "libpython%s.%sm?.dylib$"
-        :win32   "python%s%s.dll$")
-      major minor))))
-
-
-(defn python-library-paths
-  "Returns vector of matching python libraries in order of:
-  - virtual-env (library)
-  - installation prefix (library)
-  - default installation location
-  - virtual-env (executable)
-  - installation prefix (executable)
-  - default executable location"
-  [system-info python-regex]
-  (println system-info)
-  (transduce
-   (comp
-    (map io/file)
-    (map file-seq)
-    cat
-    (map str)
-    (filter #(re-find python-regex %)))
-   (fn
-     ([[seen results]] results)
-     ([[seen? results] input]
-      (if (not (seen? input))
-        [(conj seen? input) (conj results input)]
-        [seen? results])))
-   ;; [seen? results]
-   [#{} []]
-   ((comp
-     vec
-     (juxt :base-prefix :prefix :base-exec-prefix :exec-prefix))
-    system-info)))
-
-(comment
-  ;; library paths workflow
-
-  (let [executable "python3.6"
-        system-info (python-system-info executable)
-        pyregex (python-library-regex system-info)]
-    (python-library-paths system-info pyregex)))
-  ;;=> ["/usr/lib/x86_64-linux-gnu/libpython3.7m.so" "/usr/lib/python3.7/config-3.7m-x86_64-linux-gnu/libpython3.7m.so"]
-
-
-(defn- ignore-shell-errors
-  [& args]
-  (try
-    (apply sh args)
-    (catch Throwable e nil)))
-
-
-(def default-python-executables
-  ["python3" "python3.6" "python3.7" "python3.8" "python3.9" "python"])
-
-
-(defn detect-startup-info
-  [{:keys [library-path python-home python-executable]}]
-  (log-info
-   (str "Detecting startup-info for Python executable: "
-        python-executable))
-  (let [executable-seq                (concat
-                                       (when python-executable
-                                         [python-executable])
-                                       default-python-executables)
-        system-info                (->> executable-seq
-                                        (map #(try (python-system-info %)
-                                                   (catch Throwable e
-                                                     nil)))
-                                        (remove nil?)
-                                        (first))
-        _ (when-not system-info
-            (throw (Exception. (format "Python executable was not found.  Tried: %s"
-                                       (vec executable-seq)))))
-        python-home                (cond
-                                     python-home
-                                     python-home
-                                     (seq (System/getenv "PYTHONHOME"))
-                                     (System/getenv "PYTHONHOME")
-                                     :else
-                                     (:prefix system-info))
-        java-library-path-addendum (when python-home
-                                     (-> (Paths/get python-home
-                                                    (into-array String ["lib"]))
-                                         (.toString)))
-        [ver-maj ver-med _ver-min] (:version system-info)
-        lib-version                (format "%s.%s" ver-maj ver-med)
-        libname                    (or library-path
-                                       (when (seq lib-version)
-                                         (str "python" lib-version "m")))
-        libnames                   (concat [libname]
-                                           ;;Make sure we try without the 'm' suffix
-                                           (when lib-version
-                                             [(str "python" lib-version)]))
-        retval
-        (merge
-         system-info
-         {:python-home                python-home
-          :lib-version                lib-version
-          :libname                    libname
-          :libnames                   libnames
-          :java-library-path-addendum java-library-path-addendum})]
-    (log/infof "Startup info detected:\n%s"
-               (with-out-str
-                 (pp/pprint (dissoc retval :libname))))
-    retval))
-
-
-;;All interpreters share the same type symbol table as types are uniform
-;;across initializations.  So given an unknown item, we can in constant time
-;;get the type of that item if we have seen it before.
-(defrecord Interpreter [
-                        interpreter-state* ;;Thread state, per interpreter
-                        shared-state*]) ;;state shared among all interpreters
-
-
-
-;; Main interpreter booted up during initialize!
-;; * in the right to indicate atom
-(defonce main-interpreter* (atom nil))
-
-(defn main-interpreter
-  ^Interpreter []
-  @main-interpreter*)
-
-
-
-(defn handle-or-interpreter->interpreter
-  [hdl-or-interp]
-  (if (number? hdl-or-interp)
-    (throw (Exception. "Interpreters are no long handles"))
-    hdl-or-interp))
-
-
-;;Bridge objects are generically created objects that can bridge between
-;;python and java.  At the very least, they implement JVMBridge
-(defn find-jvm-bridge-entry
-  ^JVMBridge [handle interpreter]
-  (when-let [interpreter (handle-or-interpreter->interpreter interpreter)]
-    (when-let [^JVMBridge bridge-object (get-in @(:interpreter-state* interpreter)
-                                                [:bridge-objects handle])]
-      bridge-object)))
-
-
-(defn get-jvm-bridge
-  ^JVMBridge [handle interpreter]
-  (if-let [bridge-obj (find-jvm-bridge-entry handle (main-interpreter))]
-    (:jvm-bridge bridge-obj)
-    (throw (Exception.
-            (format "Unable to find bridge for interpreter %s and handle %s"
-                    interpreter handle)))))
-
-
-(defn register-bridge!
-  [^JVMBridge bridge ^Pointer bridge-pyobject]
-  (let [interpreter (.interpreter bridge)
-        bridge-handle (Pointer/nativeValue bridge-pyobject)]
-    (when (contains? (get-in @(:interpreter-state* interpreter)
-                           [:bridge-objects])
-                     bridge-handle)
-      (throw (Exception. (format "Bridge already exists!! - 0x%x" bridge-handle))))
-    (swap! (:interpreter-state* interpreter) assoc-in
-           [:bridge-objects bridge-handle]
-           {:jvm-bridge bridge
-            :pyobject bridge-handle})
-    :ok))
-
-
-(defn unregister-bridge!
-  [^JVMBridge bridge ^Pointer bridge-pyobject]
-  (let [interpreter (.interpreter bridge)
-        bridge-handle (Pointer/nativeValue bridge-pyobject)]
-    (swap! (:interpreter-state* interpreter)
-           update :bridge-objects dissoc bridge-handle)
-    :ok))
-
-
-(defn- construct-main-interpreter!
-  [thread-state type-symbol-table]
-  (swap!
-   main-interpreter*
-   (fn [existing-interpreter]
-     (when existing-interpreter
-       (throw (Exception. "Main interpreter is already constructed")))
-
-     (let [retval (->Interpreter
-                                 (atom {:thread-state thread-state
-                                        :bridge-objects {}
-                                        :sub-interpreters []})
-                                 ;;This that have to live as long as the main
-                                 ;;interpreter does
-                                 (atom {:type-symbol-table type-symbol-table
-                                        :forever []}))]
-       retval)))
-  :ok)
-
-
-(defn- python-thread-state
-  [interpreter]
-  (get @(:interpreter-state* interpreter) :thread-state))
-
-
-(defn release-gil!
-  "Reentrant pathway to release the gil."
-  [gil-state ^long original-thread-id]
-  (.set ^AtomicLong libpy-base/gil-thread-id original-thread-id)
-  (libpy/PyGILState_Release gil-state))
-
-
-(defn acquire-gil!
-  "Reentrant pathway to acquire gil."
-  []
-  (let [retval (libpy/PyGILState_Ensure)]
-    (.set ^AtomicLong libpy-base/gil-thread-id (libpy-base/current-thread-id))
-    retval))
-
-
-(defn swap-interpreters!
-  "The gil must be held by this thread.  This swaps out the current interpreter
-  to make a new one current."
-  [old-interp new-interp]
-  (libpy/PyThreadState_Swap (python-thread-state old-interp)
-                            (python-thread-state new-interp)))
-
-
-(defn main-interpreter-thread-id
-  ^long []
-  (.get ^AtomicLong libpy-base/gil-thread-id))
-
-
-(defn ensure-interpreter
-  ^Interpreter []
-  (let [retval (main-interpreter)]
-    (when-not retval
-      (throw (Exception. "No interpreters found, perhaps an initialize! call is missing?")))
-    retval))
-
-
-(defn ensure-bound-interpreter
-  []
-  (let [interp (main-interpreter)]
-    (if (and interp
-             (= (libpy-base/current-thread-id) (main-interpreter-thread-id)))
-      interp
-      (throw (Exception. "No interpreters found, perhaps an initialize! call is missing?")))))
-
-
-(defn py-type-keyword
-  "Get a keyword that corresponds to the current type.  Uses global type symbol
-  table. Add the type to the symbol table if it does not exist already."
-  [typeobj]
-  (let [type-addr (Pointer/nativeValue (libpy/as-pyobj typeobj))
-        interpreter (ensure-bound-interpreter)
-        symbol-table (-> (swap! (:shared-state* interpreter)
-                                (fn [{:keys [type-symbol-table] :as shared-state}]
-                                  (if-let [found-item (get type-symbol-table
-                                                           type-addr)]
-                                    shared-state
-                                    (assoc-in shared-state [:type-symbol-table
-                                                            type-addr]
-                                              {:typename (libpy/get-type-name
-                                                          typeobj)}))))
-                         :type-symbol-table)]
-    (get-in symbol-table [type-addr :typename])))
-
-
-
-(defmacro with-gil
-  "Grab the gil and use the main interpreter using reentrant acquire-gil pathway."
-  [& body]
-  `(do
-     (let [interp# (ensure-interpreter)]
-       (let [[previously-bound-thread-id# gil-state#]
-             (locking interp#
-               (let [^AtomicLong bound-thread# libpy-base/gil-thread-id
-                     previously-bound-thread-id# (.get bound-thread#)
-                     thread-id# (libpy-base/current-thread-id)
-                     gil-state# (when-not (== thread-id# previously-bound-thread-id#)
-                                  (acquire-gil!))]
-                 [previously-bound-thread-id# gil-state#]))]
-         (try
-           ~@body
-           (finally
-             (when gil-state#
-               (pygc/clear-reference-queue)
-               (release-gil! gil-state# previously-bound-thread-id#))))))))
-
-
-(defonce ^:dynamic *program-name* "")
-
-
-(defn- find-python-lib-version
-  []
-  (let [{:keys [out err exit]} (ignore-shell-errors "python3" "--version")]
-    (when (= 0 exit)
-      ;;Anaconda prints version info only to the error stream.
-      (when-let [version-info (first (filter seq [out err]))]
-        (log/infof "Python detected: %s" version-info)
-        (let [version-data (re-find #"\d+\.\d+\.\d+" version-info)
-              parts (->> (s/split version-data #"\.")
-                         (take 2)
-                         seq)]
-          (s/join "." parts))))))
-
-
-(defn append-java-library-path!
-  [new-search-path]
-  (let [existing-paths (-> (System/getProperty "java.library.path")
-                           (s/split #":"))]
-    (when-not (contains? (set existing-paths) new-search-path)
-      (let [new-path-str (s/join ":" (concat [new-search-path]
-                                             existing-paths))]
-        (log/infof "Setting java library path: %s" new-path-str)
-        (System/setProperty "java.library.path" new-path-str)))))
-
-
-(defonce ^:private python-home-wide-ptr* (atom nil))
-(defonce ^:private python-path-wide-ptr* (atom nil))
-
-
-(defn- try-load-python-library!
-  [libname python-home-wide-ptr python-path-wide-ptr]
-  (try
-    (jna/load-library libname)
-    (alter-var-root #'libpy-base/*python-library* (constantly libname))
-    (when python-home-wide-ptr
-      (libpy/Py_SetPythonHome python-home-wide-ptr))
-    (when python-path-wide-ptr
-      (libpy/Py_SetProgramName python-path-wide-ptr))
-    (libpy/Py_InitializeEx 0)
-    libname
-    (catch Exception e)))
-
-
-(defn- setup-direct-mapping!
-  []
-  (let [library (jna/load-library libpy-base/*python-library*)]
-    (com.sun.jna.Native/register DirectMapped library)))
-
-
-(defn initialize!
-  [& {:keys [program-name
-             library-path]
-      :as options}]
-  (when-not (main-interpreter)
-    (log-info (str "Executing python initialize with options:" options))
-    (let [{:keys [python-home libnames java-library-path-addendum
-                  executable] :as startup-info}
-          (detect-startup-info options)
-          library-names (concat
-                         (when library-path
-                           [library-path])
-                         libnames
-                         (libpy-base/library-names))]
-      (reset! python-home-wide-ptr* nil)
-      (reset! python-path-wide-ptr* nil)
-      (log/infof "Trying python library names %s" (vec library-names))
-      (when python-home
-        (append-java-library-path! java-library-path-addendum)
-        ;;This can never be released if load-library succeeeds
-        (reset! python-home-wide-ptr* (jna/string->wide-ptr python-home))
-        (reset! python-path-wide-ptr* (jna/string->wide-ptr
-                                       (format "%s/bin/python3"
-                                               python-home))))
-      (loop [[library-name & library-names] library-names]
-        (if (and library-name
-                 (not (try-load-python-library! library-name
-                                                @python-home-wide-ptr*
-                                                @python-path-wide-ptr*)))
-          (recur library-names)))
-      (setup-direct-mapping!)
-      ;;Set program name
-      (when-let [program-name (or program-name executable "")]
-        (libpy/PySys_SetArgv 0 (-> program-name
-                                   (jna/string->wide-ptr)))))
-    (let [type-symbols (libpy/lookup-type-symbols)
-          context (do
-                    (libpy-base/set-gil-thread-id!
-                     Long/MAX_VALUE (libpy-base/current-thread-id))
-                    (let [retval (libpy/PyEval_SaveThread)]
-                      (libpy-base/set-gil-thread-id!
-                       (libpy-base/current-thread-id) Long/MAX_VALUE)
-                      retval))]
-      (construct-main-interpreter! context type-symbols))))
-
-
-(def ^:dynamic *python-error-handler* nil)
-
-
-(defn check-error-str
-  "Function assumes python stdout and stderr have been redirected"
-  []
-  (with-gil
-    (when-not (= nil (libpy/PyErr_Occurred))
-      (if-not *python-error-handler*
-        (let [custom-writer (StringWriter.)]
-          (with-bindings {#'*err* custom-writer}
-            (libpy/PyErr_Print))
-          (.toString custom-writer))
-        (*python-error-handler*)))))
-
-
-(defn check-error-throw
-  []
-  (when-let [error-str (check-error-str)]
-    (throw (Exception. ^String error-str))))
-
-
-(defn check-error-log
-  []
-  (when-let [error-str (check-error-str)]
-    (log-error error-str)))
-
-
-(defn finalize!
-  []
-  (when-not (== Long/MAX_VALUE (.get ^AtomicLong libpy-base/gil-thread-id))
-    (throw (Exception. (format "A thread still owns the interpreter: "
-                               (.get ^AtomicLong libpy-base/gil-thread-id)))))
-  (let [interp (ensure-interpreter)]
-    (locking interp
-      (check-error-throw)
-      (log-info "executing python finalize!")
-      (let [finalize-value (libpy/Py_FinalizeEx)]
-        (when-not (= 0 finalize-value)
-          (log-error (format "PyFinalize returned nonzero value: %s" finalize-value)))))))
-
-
-(defn conj-forever!
-  [items]
-  (let [interpreter (ensure-bound-interpreter)]
-    (swap! (:shared-state* interpreter) update :forever conj items)
-    :ok))
-
-
-;;Sub interpreter work goes here
diff --git a/src/libpython_clj/python/logging.clj b/src/libpython_clj/python/logging.clj
deleted file mode 100644
index 4098c8d..0000000
--- a/src/libpython_clj/python/logging.clj
+++ /dev/null
@@ -1,35 +0,0 @@
-(ns libpython-clj.python.logging
-  "Wrapper for simple logging.  Use tools.logging ns directly for more
-  advanced usages."
-  (:require [clojure.tools.logging :as log]))
-
-
-(set! *warn-on-reflection* true)
-
-
-(defn log-level
-  [level msg]
-  (case level
-    :info (log/info msg)
-    :warn (log/warn msg)
-    :error (log/error msg)))
-
-
-(defn log-error
-  [log-str]
-  (log-level :error log-str))
-
-
-(defn log-warn
-  [log-str]
-  (log-level :warn log-str))
-
-
-(defn log-info
-  [log-str]
-  (log-level :info log-str))
-
-
-(defn logthrow-error
-  [log-str & [data]]
-  (throw (ex-info log-str data)))
diff --git a/src/libpython_clj/python/np_array.clj b/src/libpython_clj/python/np_array.clj
deleted file mode 100644
index 38998e0..0000000
--- a/src/libpython_clj/python/np_array.clj
+++ /dev/null
@@ -1,114 +0,0 @@
-(ns libpython-clj.python.np-array
-  "Bindings for deeper intergration of numpy into the tech.v3.datatype system.  This allows somewhat more
-  seamless usage of numpy arrays in datatype and tensor functionality such as enabling
-  the tech.v3.tensor/ensure-tensor call to work with numpy arrays (as zero copying when possible)."
-  (:require [tech.v3.datatype.protocols :as dtype-proto]
-            [tech.v3.tensor :as dtt]
-            [libpython-clj.python.interpreter :as py-interp]
-            [libpython-clj.python.protocols :as py-proto]
-            [libpython-clj.python.bridge :as py-bridge]
-            [libpython-clj.python.interop :as py-interop]))
-
-
-(defmethod py-proto/pyobject->jvm :ndarray
-  [pyobj]
-  (-> (py-bridge/numpy->desc pyobj)
-      (dtt/nd-buffer-descriptor->tensor)
-      (dtt/clone)))
-
-
-(defmethod py-proto/pyobject-as-jvm :ndarray
-  [pyobj]
-  (py-interp/with-gil
-    (let [interpreter (py-interp/ensure-bound-interpreter)]
-      (py-bridge/bridge-pyobject
-       pyobj
-       interpreter
-       Iterable
-       (iterator [this]
-                 (py-proto/python-obj-iterator pyobj interpreter))
-       py-proto/PPyObjectBridgeToMap
-       (as-map [item]
-               (py-bridge/generic-python-as-map pyobj))
-       py-proto/PPyObjectBridgeToList
-       (as-list [item]
-                (py-bridge/generic-python-as-list pyobj))
-       dtype-proto/PToTensor
-       (as-tensor [item]
-                  (-> (py-bridge/numpy->desc item)
-                      dtt/nd-buffer-descriptor->tensor))
-       dtype-proto/PElemwiseDatatype
-       (elemwise-datatype
-        [this]
-        (-> (py-proto/get-attr pyobj "dtype")
-            (py-proto/as-jvm {})
-            (py-bridge/obj-dtype->dtype)))
-
-
-       dtype-proto/PECount
-       (ecount [this] (apply * (dtype-proto/shape this)))
-
-       dtype-proto/PShape
-       (shape
-        [this]
-        (-> (py-proto/get-attr pyobj "shape")
-            (py-proto/->jvm {})))
-
-       dtype-proto/PToNativeBuffer
-       (convertible-to-native-buffer? [item] true)
-       (->native-buffer
-        [item]
-        (dtype-proto/->native-buffer
-         (dtype-proto/as-tensor item)))
-
-       dtype-proto/PSubBuffer
-       (sub-buffer
-        [buffer offset length]
-        (-> (dtype-proto/as-tensor buffer)
-            (dtype-proto/sub-buffer offset length)))
-
-       dtype-proto/PToNDBufferDesc
-       (convertible-to-nd-buffer-desc? [item] true)
-       (->nd-buffer-descriptor
-        [item]
-        (py-bridge/numpy->desc item))))))
-
-
-(def np-mod*
-  "Delay that dereferences to the python numpy module"
-  (py-bridge/pydelay
-   (-> (py-interop/import-module "numpy")
-       (py-proto/as-jvm {}))))
-
-
-(comment
-
-  ;;Dispatch table for what tech.v3.datatype.functional methods are found in the numpy module.
-
-  (defn- dispatch-binary-op
-    [op lhs rhs options]
-    (case op
-      :max (py-proto/call-attr @np-mod "max" lhs rhs)
-      :min (py-proto/call-attr @np-mod "min" lhs rhs)
-      :+ (py-proto/call-attr @np-mod "add" lhs rhs)
-      :- (py-proto/call-attr @np-mod "subtract" lhs rhs)
-      :div (py-proto/call-attr @np-mod "divide" lhs rhs)
-      :* (py-proto/call-attr @np-mod "multiply" lhs rhs)
-      :pow (py-proto/call-attr @np-mod "power" lhs rhs)
-      :quot (py-proto/call-attr @np-mod "floor_divide" lhs rhs)
-      :rem (py-proto/call-attr @np-mod "mod" lhs rhs)
-      :bit-and (py-proto/call-attr @np-mod "bitwise_and" lhs rhs)
-      :bit-flip (py-proto/call-attr @np-mod "bitwise_not" lhs rhs)
-      :bit-or (py-proto/call-attr @np-mod "bitwise_or" lhs rhs)
-      :bit-xor (py-proto/call-attr @np-mod "bitwise_xor" lhs rhs)
-      :bit-shift-left (py-proto/call-attr @np-mod "left_shift" lhs rhs)
-      :bit-shift-right (py-proto/call-attr @np-mod "right_shift" lhs rhs)
-      :and (py-proto/call-attr @np-mod "logical_and" lhs rhs)
-      :or (py-proto/call-attr @np-mod "logical_or" lhs rhs)
-      :not (py-proto/call-attr @np-mod "logical_not" lhs rhs)
-      :xor (py-proto/call-attr @np-mod "logical_xor" lhs rhs)
-      :< (py-proto/call-attr @np-mod "less" lhs rhs)
-      :<= (py-proto/call-attr @np-mod "less_equal" lhs rhs)
-      :eq (py-proto/call-attr @np-mod "equal" lhs rhs)
-      :> (py-proto/call-attr @np-mod "greater" lhs rhs)
-      :>= (py-proto/call-attr @np-mod "greater_equal" lhs rhs))))
diff --git a/src/libpython_clj/python/object.clj b/src/libpython_clj/python/object.clj
deleted file mode 100644
index 352ec97..0000000
--- a/src/libpython_clj/python/object.clj
+++ /dev/null
@@ -1,1133 +0,0 @@
-(ns libpython-clj.python.object
-  "Base support for python objects and python->jvm interop.  At this level (without
-  interop), we can only support the copying protocols; we can't do bridging.  Still,
-  copying gets you quite far and you can, for instance, call python functions and get
-  the attribute map from a python object.
-
-  Protocol functions implemented:
-  python-type
-  ->python
-  ->jvm
-  dir
-  has-attr?
-  attr
-  callable?
-  has-item?
-  item
-  set-item!
-  do-call-fn
-  len
-
-  Results of these, when they return python pointers, return the raw,unwrapped pointers.
-  Callers at this level are sitting just close enough to the actual libpy calls to still
-  get pointers back *but* they don't have to manage the gil."
-  (:require [libpython-clj.python.interpreter
-             :refer [with-gil
-                     ensure-interpreter
-                     ensure-bound-interpreter
-                     check-error-throw]
-             :as pyinterp]
-            [libpython-clj.python.protocols
-             :refer [pyobject->jvm
-                     python-type]
-             :as py-proto]
-            [libpython-clj.jna.base :as libpy-base]
-            [libpython-clj.jna :as libpy]
-            [libpython-clj.python.gc :as pygc]
-            [clojure.stacktrace :as st]
-            [tech.v3.jna :as jna]
-            [tech.v3.datatype :as dtype]
-            [tech.v3.datatype.protocols :as dtype-proto]
-            [tech.v3.datatype.casting :as casting]
-            [clojure.tools.logging :as log])
-  (:import [com.sun.jna Pointer CallbackReference]
-           [com.sun.jna.ptr PointerByReference]
-           [java.lang.reflect Field]
-           [libpython_clj.jna
-            PyObject
-            CFunction$KeyWordFunction
-            CFunction$TupleFunction
-            CFunction$NoArgFunction
-            PyMethodDef]
-           [java.nio.charset StandardCharsets]
-           [java.util RandomAccess Map Set Map$Entry]
-           [tech.v3.datatype ObjectReader NDBuffer Buffer]
-           [tech.v3.datatype.array_buffer ArrayBuffer]
-           [tech.v3.datatype.native_buffer NativeBuffer]
-           [clojure.lang Symbol Keyword
-            IPersistentMap
-            IPersistentVector
-            IPersistentSet
-            Range LongRange]))
-
-
-(set! *warn-on-reflection* true)
-
-
-
-(extend-protocol py-proto/PCopyToJVM
-  Pointer
-  (->jvm [item options]
-    (pyobject->jvm item))
-  PyObject
-  (->jvm [item options]
-    (pyobject->jvm (.getPointer item))))
-
-
-(extend-protocol py-proto/PBridgeToPython
-  Pointer
-  (as-python [item options] item)
-  PyObject
-  (as-python [item options] (.getPointer item)))
-
-
-(extend-protocol py-proto/PCopyToPython
-  Pointer
-  (->python [item options] item)
-  PyObject
-  (->python [item options] (.getPointer item)))
-
-
-(defn ->jvm
-  "Copy an object into the jvm (if it wasn't there already.)"
-  [item & [options]]
-  (when-not (nil? item)
-    (py-proto/->jvm item options)))
-
-
-(def object-reference-logging (atom false))
-
-
-(defn incref
-  "Incref and return object"
-  [pyobj]
-  (let [pyobj (libpy/as-pyobj pyobj)]
-    (libpy/Py_IncRef pyobj)
-    pyobj))
-
-
-(defn refcount
-  ^long [pyobj]
-  (long (.ob_refcnt (PyObject. (libpy/as-pyobj pyobj)))))
-
-
-(def ^:private non-native-val
-  (delay (Pointer/nativeValue (libpy/as-pyobj (libpy/Py_None)))))
-
-
-(defn wrap-pyobject
-  "Wrap object such that when it is no longer accessible via the program decref is
-  called. Used for new references.  This is some of the meat of the issue, however,
-  in that getting the two system's garbage collectors to play nice is kind
-  of tough.
-  This is a hot path; it is called quite a lot from a lot of places."
-  ([pyobj skip-check-error?]
-   ;;We don't wrap pynone
-   (if pyobj
-     (let [pyobj-value (Pointer/nativeValue (libpy/as-pyobj pyobj))
-           ^PyObject obj-data (when @object-reference-logging
-                                (PyObject. (Pointer. pyobj-value)))]
-       (if (not= pyobj-value
-                 (long @non-native-val))
-         (do
-           (ensure-bound-interpreter)
-           (when @object-reference-logging
-             (println (format "tracking object  - 0x%x:%4d:%s"
-                              pyobj-value
-                              (.ob_refcnt obj-data)
-                              (name (python-type pyobj)))))
-           ;;We ask the garbage collector to track the python object and notify
-           ;;us when it is released.  We then decref on that event.
-           (pygc/track
-            pyobj
-            ;;No longer with-gil.  Because cleanup is cooperative, the gil is
-            ;;guaranteed to be captured here already.
-            #(try
-               ;;Intentionally overshadow pyobj.  We cannot access it here.
-               (let [pyobj (Pointer. pyobj-value)]
-                 (when @object-reference-logging
-                   (let [_ (.read obj-data)
-                         refcount (.ob_refcnt obj-data)]
-                     (if (< refcount 1)
-                       (log/errorf "Fatal error -- releasing object - 0x%x:%4d:%s
-Object's refcount is bad.  Crash is imminent"
-                                   pyobj-value
-                                   refcount
-                                   (name (python-type pyobj)))
-                       (println (format "releasing object - 0x%x:%4d:%s"
-                                        pyobj-value
-                                        refcount
-                                        (name (python-type pyobj)))))))
-                 (libpy/Py_DecRef pyobj))
-               (catch Throwable e
-                 (log/error e "Exception while releasing object"))))
-           (when-not skip-check-error? (check-error-throw))
-           pyobj)
-         (do
-           ;;Special handling for PyNone types
-           (libpy/Py_DecRef pyobj)
-           (when-not skip-check-error? (check-error-throw))
-           nil)))
-     (when-not skip-check-error? (check-error-throw))))
-  ([pyobj]
-   (wrap-pyobject pyobj false)))
-
-
-(defmacro stack-resource-context
-  [& body]
-  `(pygc/with-stack-context
-     ~@body))
-
-
-(defn incref-wrap-pyobject
-  "Increment the object's refcount and then call wrap-pyobject.  Used for borrowed
-  references that need to escape the current scope."
-  [pyobj]
-  (with-gil
-    (let [pyobj (libpy/as-pyobj pyobj)]
-      (libpy/Py_IncRef pyobj)
-      (wrap-pyobject pyobj))))
-
-
-(defn py-true
-  []
-  (libpy/Py_True))
-
-
-(defn py-false
-  []
-  (libpy/Py_False))
-
-
-(defn py-none
-  []
-  (libpy/Py_None))
-
-
-(defn py-not-implemented
-  []
-  (libpy/Py_NotImplemented))
-
-
-;; Now we can completely implement ->python
-(defn ->python
-  "Completely convert a jvm object to a python copy."
-  [item & [options]]
-  (if (nil? item)
-    (py-none)
-    (py-proto/->python item options)))
-
-
-(defn py-raw-type
-  ^Pointer [pyobj]
-  (let [^Pointer pyobj (libpy/as-pyobj pyobj)]
-    (jna/size-t-compile-time-switch
-     (.getPointer pyobj 4) (.getPointer pyobj 8))))
-
-
-(extend-protocol py-proto/PPythonType
-  Pointer
-  (get-python-type [pyobj]
-    (with-gil
-    (-> pyobj
-        py-raw-type
-        pyinterp/py-type-keyword)))
-  PyObject
-  (get-python-type [item]
-    (py-proto/get-python-type (.getPointer item))))
-
-
-(defn py-string->string
-  "Given a python string return a string"
-  ^String [pyobj]
-  (with-gil
-    (when-not (= :str (python-type pyobj))
-      (throw (ex-info (format "Object passed in is not a string: %s"
-                              (python-type pyobj))
-                      {})))
-    (let [size-obj (jna/size-t-ref)
-          ^Pointer str-ptr (libpy/PyUnicode_AsUTF8AndSize pyobj size-obj)
-          n-elems (jna/size-t-ref-value size-obj)]
-      (-> (.decode StandardCharsets/UTF_8 (.getByteBuffer str-ptr 0 n-elems))
-          (.toString)))))
-
-
-(defn py-str
-  "Call the __str__ attribute on an object return a new string pyobject"
-  ^String [pyobj]
-  (with-gil
-    (let [py-str (if (= :str (python-type pyobj))
-                   pyobj
-                   (-> (libpy/PyObject_Str pyobj)
-                       wrap-pyobject))]
-      (py-string->string py-str))))
-
-
-(defn py-dir
-  "List the attribute names of an object"
-  [pyobj]
-  (with-gil
-    (-> (libpy/PyObject_Dir pyobj)
-        wrap-pyobject
-        (py-proto/->jvm {}))))
-
-
-(defn ->py-long
-  "Convert an object into a python long"
-  [item]
-  (with-gil
-    (wrap-pyobject
-     (libpy/PyLong_FromLongLong (long item)))))
-
-
-(defn ->py-float
-  "Convert an object into a python float"
-  [item]
-  (with-gil
-    (wrap-pyobject
-     (libpy/PyFloat_FromDouble (double item)))))
-
-
-(defn ->py-string
-  "Copy an object into a python string"
-  [item]
-  (with-gil
-    (let [byte-data (.getBytes ^String item StandardCharsets/UTF_16)]
-      (wrap-pyobject
-       (libpy/PyUnicode_Decode byte-data (dtype/ecount byte-data)
-                               "UTF-16" "strict")))))
-
-
-(defn ->py-dict
-  "Copy an object into a new python dictionary."
-  [item]
-  (with-gil
-    (let [dict (libpy/PyDict_New)]
-      (doseq [[k v] item]
-        (libpy/PyDict_SetItem dict (->python k)
-                              (->python v)))
-      (wrap-pyobject
-       dict))))
-
-
-(defn ->py-list
-  "Copy an object into a new python list."
-  [item-seq]
-  (with-gil
-    (let [retval (libpy/PyList_New (count item-seq))]
-      (->> item-seq
-           (map-indexed (fn [idx item]
-                          (libpy/PyList_SetItem
-                           retval
-                           idx
-                           (let [new-val (->python item)]
-                             (libpy/Py_IncRef new-val)
-                             new-val))))
-           dorun)
-      (wrap-pyobject retval))))
-
-
-(defn ->py-tuple
-  "Copy an object into a new python tuple"
-  [item-seq]
-  (with-gil
-    (let [n-items (count item-seq)
-          new-tuple (libpy/PyTuple_New n-items)]
-      (->> item-seq
-           (map-indexed (fn [idx item]
-                          (libpy/PyTuple_SetItem
-                           new-tuple
-                           idx
-                           (let [new-val (->python item)]
-                             (libpy/Py_IncRef new-val)
-                             new-val))))
-           dorun)
-      (wrap-pyobject new-tuple))))
-
-
-(defn ->py-set
-  [item]
-  (with-gil
-    (-> (libpy/PySet_New (->py-list item))
-        wrap-pyobject)))
-
-
-
-(extend-protocol libpy-base/PToPyObjectPtr
-  Number
-  (convertible-to-pyobject-ptr? [item] true)
-  (->py-object-ptr [item]
-    (with-gil
-      (->python item)))
-  Character
-  (convertible-to-pyobject-ptr? [item] true)
-  (->py-object-ptr [item]
-    (with-gil
-      (->python (str item))))
-  String
-  (convertible-to-pyobject-ptr? [item] true)
-  (->py-object-ptr [item]
-    (with-gil
-      (->python item)))
-  ;;The container classes are mirrored into python, not copied.
-  ;;so no entries here for map, list, etc.
-  )
-
-
-;; Chosen by fair dice roll, the item cutoff decides how many items
-;; a persistent vector should have in it before it is considered a list
-;; instead of a tuple.  If you know something should always be a tuple, then
-;; call ->py-tuple explicitly.
-(def ^:dynamic *item-tuple-cutoff* 8)
-
-
-(defn- cfunc-instance?
-  [function]
-  (or (instance? CFunction$KeyWordFunction function)
-      (instance? CFunction$TupleFunction function)
-      (instance? CFunction$NoArgFunction function)))
-
-
-(defn apply-method-def-data!
-  [^PyMethodDef method-def {:keys [name
-                                   doc
-                                   function]
-                            :as method-data}]
-  ;;Here we really do need a resource stack context
-  (when-not (cfunc-instance? function)
-    (throw (Exception.
-            (format "Callbacks must implement one of the CFunction interfaces:
-%s" (type function)))))
-  (let [meth-flags (long (cond
-                           (instance? CFunction$NoArgFunction function)
-                           libpy/METH_NOARGS
-
-                           (instance? CFunction$TupleFunction function)
-                           libpy/METH_VARARGS
-
-                           (instance? CFunction$KeyWordFunction function)
-                           (bit-or libpy/METH_KEYWORDS libpy/METH_VARARGS)
-                           :else
-                           (throw (ex-info (format "Failed due to type: %s"
-                                                   (type function))
-                                           {}))))
-        name-ptr (jna/string->ptr-untracked name)
-        doc-ptr (jna/string->ptr-untracked doc)]
-    (set! (.ml_name method-def) name-ptr)
-    (set! (.ml_meth method-def) (CallbackReference/getFunctionPointer function))
-    (set! (.ml_flags method-def) (int meth-flags))
-    (set! (.ml_doc method-def) doc-ptr)
-    (.write method-def)
-    (pyinterp/conj-forever! (assoc method-data
-                                   :name-ptr name-ptr
-                                   :doc-ptr doc-ptr
-                                   :callback-object function
-                                   :method-definition method-def))
-    method-def))
-
-
-(defn method-def-data->method-def
-  [method-data]
-  (apply-method-def-data! (PyMethodDef.) method-data))
-
-
-
-(defn- cfunc-impl->pyobject
-  [cfunc {:keys [method-name
-                 documentation
-                 py-self]
-          :or {method-name "unnamed_function"
-               documentation "not documented"}}]
-  (with-gil
-    ;;This is a nice little tidbit, cfunction_new
-    ;;steals the reference.
-    (let [py-self (when py-self (incref (->python py-self)))]
-      (-> (libpy/PyCFunction_NewEx (method-def-data->method-def
-                                    {:name method-name
-                                     :doc documentation
-                                     :function cfunc})
-                                   py-self
-                                   nil)
-          (wrap-pyobject)))))
-
-
-(defn py-tuple->borrowed-reference-reader
-  "Given a python tuple, return an object reader that iterates
-  through the items.  If you hold onto one of these items you need
-  to add to it's reference count.  If unsure of what you are doing
-  don't use this method, use ->jvm."
-  [tuple]
-  (with-gil
-    (let [interpreter (ensure-bound-interpreter)
-          n-items (long (libpy/PyObject_Length tuple))]
-      (reify ObjectReader
-        (lsize [_] n-items)
-        (readObject [_ idx]
-          (with-gil
-            (libpy/PyTuple_GetItem tuple idx)))))))
-
-
-(defn ->python-incref
-  "Convert to python and add a reference.  This is necessary for return values from
-  functions as the ->python pathway adds a reference but it also tracks it and
-  releases it when it is not in use any more.  Thus python ends up holding onto
-  something with fewer refcounts than it should have.  If you are just messing
-  around in the repl you only need ->python.  There is an expectation that the
-  return value of a function call is a new reference and not a borrowed reference
-  hence this pathway."
-  [item]
-  (-> (->python item)
-      (incref)))
-
-
-
-(defn make-tuple-fn
-  "Given a clojure function, create a python tuple function.
-  arg-convert is applied to arguments before the clojure function
-  gets them and result-converter is applied to the outbound result.
-  Exceptions are caught, logged, and propagated to python.
-
-  arg-converter: A function to be called on arguments before they get to
-    clojure.  Defaults to ->jvm.
-  result-converter: A function to be called on the return value before it
-    makes it back to python.  Defaults to ->python-incref.
-  method-name: Name of function exposed to python.
-  documentation: Documentation of function exposed to python."
-  [fn-obj & {:keys [arg-converter
-                    result-converter]
-             :or {arg-converter ->jvm
-                  result-converter ->python-incref}
-             :as options}]
-  (with-gil
-    (-> (reify CFunction$TupleFunction
-          (pyinvoke [this self args]
-            (try
-              (let [argseq (cond->> (py-tuple->borrowed-reference-reader args)
-                             arg-converter
-                             (map arg-converter))]
-                (cond-> (apply fn-obj argseq)
-                  result-converter
-                  (result-converter)))
-              (catch Throwable e
-                (log/error e "Error executing clojure function.")
-                (libpy/PyErr_SetString (libpy/PyExc_Exception)
-                                       (format "%s:%s" e (with-out-str
-                                                           (st/print-stack-trace e))))
-                nil))))
-        (cfunc-impl->pyobject options))))
-
-
-(defn ->py-fn
-  "Create a python callback from a clojure fn.
-  If clojure fn, then tuple arguments are used.  If keyword arguments are desired,
-  the pass in something derived from: libpython-clj.jna.CFunction$KeyWordFunction.
-  If a pure fn is passed in, arguments are marshalled from python if possible and
-  then to-python in the case of successful execution.  An exception will set the error
-  indicator.
-  Options are
-  method-name: Name of function exposed to python.
-  documentation: Documentation of function exposed to python.
-  py-self: The 'self' object to be used for the function."
-  ([fn-obj {:keys []
-            :as options}]
-   (cond
-     (instance? clojure.lang.IFn fn-obj)
-     (apply make-tuple-fn fn-obj (apply concat options))
-     (cfunc-instance? fn-obj)
-     (cfunc-impl->pyobject fn-obj options)
-     :else
-     (throw (Exception. "fn-obj is neither a CFunction nor clojure callable."))))
-  ([fn-obj]
-   (->py-fn fn-obj {})))
-
-
-(defn py-fn->instance-fn
-  "Given a python callable, return an instance function meant to be used
-  in class definitions."
-  [py-fn]
-  (with-gil
-    (-> (libpy/PyInstanceMethod_New py-fn)
-        (wrap-pyobject))))
-
-
-(defn make-tuple-instance-fn
-  "Make an instance function.  In this case the default behavior is to
-  pass raw python object ptr args  to the clojure function without marshalling
-  as that can add confusion and unnecessary overhead.  Self will be the first argument.
-  Callers can change this behavior by setting the 'arg-converter' option as in
-  'make-tuple-fn'.
-  Options are the same as make-tuple-fn."
-  [clj-fn & {:keys [arg-converter]
-             :as options}]
-  (with-gil
-    (-> (apply make-tuple-fn
-               clj-fn
-               ;;Explicity set arg-convert to override make-tuple-fn's default
-               ;;->jvm arg-converter.
-               (->> (assoc options :arg-converter arg-converter)
-                    (apply concat)))
-        ;;Mark this as an instance function.
-        (py-fn->instance-fn))))
-
-
-(defn create-class
-  "Create a new class object.  Any callable values in the cls-hashmap
-  will be presented as instance methods.
-  Things in the cls hashmap had better be either atoms or already converted
-  python objects.  You may get surprised otherwise; you have been warned.
-  See the classes-test file in test/libpython-clj"
-  [name bases cls-hashmap]
-  (with-gil
-    (let [cls-dict (reduce (fn [cls-dict [k v]]
-                             (py-proto/set-item! cls-dict k (->python v))
-                             cls-dict)
-                           (->py-dict {})
-                           cls-hashmap)
-          bases (->py-tuple bases)
-          new-cls (py-proto/call (libpy/PyType_Type) name bases cls-dict)]
-      (py-proto/as-jvm new-cls nil))))
-
-(def ^:private lr-step-field (doto (.getDeclaredField ^Class LongRange "step")
-                               (.setAccessible true)))
-
-
-(def ^:private r-step-field (doto (.getDeclaredField ^Class Range "step")
-                              (.setAccessible true)))
-
-
-(extend-protocol py-proto/PCopyToPython
-  Number
-  (->python [item options]
-    (if (integer? item)
-      (->py-long item)
-      (->py-float item)))
-  String
-  (->python [item options]
-    (->py-string item))
-  Character
-  (->python [item options]
-    (->py-string (str item)))
-  Symbol
-  (->python [item options]
-    (->py-string (name item)))
-  Keyword
-  (->python [item optins]
-    (->py-string (name item)))
-  Boolean
-  (->python [item options]
-    (if item
-      (py-true)
-      (py-false)))
-  Range
-  (->python [item options]
-    (if (casting/integer-type? (dtype/get-datatype item))
-      (let [start (first item)
-            step (.get ^Field r-step-field item)
-            stop (+ start (* step (count item)))]
-        (py-proto/call (libpy/PyRange_Type) start stop step))
-      (->py-list item)))
-  LongRange
-  (->python [item options]
-    (let [start (first item)
-          step (.get ^Field lr-step-field item)
-          stop (+ start (* step (count item)))]
-      (py-proto/call (libpy/PyRange_Type) start stop step)))
-  Iterable
-  (->python [item options]
-    (cond
-      (instance? RandomAccess item)
-      (if (and (instance? IPersistentVector item)
-               (< (count item) (long *item-tuple-cutoff*)))
-        (->py-tuple item)
-        (->py-list item))
-      (instance? Map$Entry item)
-      (->py-tuple [(.getKey ^Map$Entry item)
-                   (.getValue ^Map$Entry item)])
-      (or (set? item)
-          (instance? Set item))
-      (->py-set item)
-      ;;Careful here!
-      (fn? item)
-      (->py-fn item)
-      (or (map? item)
-          (instance? Map item))
-      (->py-dict item)
-      :else
-      (->py-list item)))
-  Pointer
-  (->python [item options] item)
-  PyObject
-  (->python [item options] (.getPointer item))
-  NDBuffer
-  (->python [item options] (py-proto/as-numpy item options))
-  ArrayBuffer
-  (->python [item options] (py-proto/as-numpy item options))
-  NativeBuffer
-  (->python [item options] (py-proto/as-numpy item options))
-  Object
-  (->python [item options]
-    (cond
-      (fn? item)
-      (->py-fn item {})
-      (casting/numeric-type? (dtype/get-datatype item))
-      (py-proto/as-numpy item options)
-      :else
-      (if-let [item-reader (dtype/->reader item)]
-        (->py-list item-reader)
-        ;;Out of sane options at the moment.
-        (throw (Exception. (format "Unable to convert java object to python: %s"
-                                   (type item))))))))
-
-
-(extend-protocol py-proto/PPythonType
-  Number
-  (get-python-type [item]
-    (if (integer? item)
-      :int
-      :float))
-  Boolean
-  (get-python-type [item] :bool)
-  String
-  (get-python-type [item] :str)
-  Symbol
-  (get-python-type [item] :str)
-  Keyword
-  (get-python-type [item] :str)
-  Map$Entry
-  (get-python-type [item] :tuple)
-  Iterable
-  (get-python-type [item]
-    (cond
-      (instance? RandomAccess item)
-      (if (and (instance? IPersistentVector item)
-               (< (count item) (long *item-tuple-cutoff*)))
-        :tuple
-        :list)
-      (or (instance? Set item)
-          (instance? IPersistentSet item))
-      :set
-      (or (instance? Map item)
-          (instance? IPersistentMap item))
-      :dict
-      :else
-      :list))
-  Object
-  (get-python-type [item]
-    (if (casting/numeric-type? (dtype/get-datatype item))
-      :nd-array
-      (if (dtype-proto/convertible-to-reader? item)
-        :list
-        :unknown-type))))
-
-
-(defn stringable?
-  [item]
-  (or (keyword? item)
-      (string? item)
-      (symbol? item)))
-
-
-(defn stringable
-  ^String [item]
-  (when (stringable? item)
-    (if (string? item)
-      item
-      (name item))))
-
-
-(defn has-attr?
-  [pyobj attr-name]
-  (with-gil
-    (= 1
-       (if (stringable? attr-name)
-         (libpy/PyObject_HasAttrString pyobj (stringable attr-name))
-         (libpy/PyObject_HasAttr pyobj (->python attr-name))))))
-
-
-(defn get-attr
-  [pyobj attr-name]
-  (with-gil
-    (-> (if (stringable? attr-name)
-          (libpy/PyObject_GetAttrString pyobj (stringable attr-name))
-          (libpy/PyObject_GetAttr pyobj (->python attr-name)))
-        wrap-pyobject)))
-
-
-(defn set-attr!
-  [pyobj attr-name attr-value]
-  (with-gil
-    (let [py-value (->python attr-value)]
-      (if (stringable? attr-name)
-        (libpy/PyObject_SetAttrString pyobj
-                                      (stringable attr-name)
-                                      py-value)
-        (libpy/PyObject_SetAttr pyobj
-                                (->python attr-name)
-                                py-value)))
-    pyobj))
-
-
-(defn obj-has-item?
-  [elem elem-name]
-  (with-gil
-    (= 1
-       (if (stringable? elem-name)
-         (libpy/PyMapping_HasKeyString elem (stringable elem-name))
-         (libpy/PyMapping_HasKey elem (->python elem-name))))))
-
-
-(defn obj-get-item
-  [elem elem-name]
-  (with-gil
-    (-> (libpy/PyObject_GetItem elem (->python elem-name))
-        wrap-pyobject)))
-
-
-(defn obj-set-item!
-  [elem elem-name elem-value]
-  (with-gil
-    (let [py-value (->python elem-value)]
-      (libpy/PyObject_SetItem elem
-                              (->python elem-name)
-                              (->python elem-value)))
-    elem))
-
-
-(extend-protocol py-proto/PPyObject
-  Pointer
-  (dir [item]
-    (py-dir item))
-  (has-attr? [item name] (has-attr? item name))
-  (get-attr [item name] (get-attr item name))
-  (set-attr! [item item-name item-value] (set-attr! item item-name item-value))
-  (callable? [item] (= 1 (libpy/PyCallable_Check item)))
-  (has-item? [item item-name] (obj-has-item? item item-name))
-  (get-item [item item-name] (obj-get-item item item-name))
-  (set-item! [item item-name item-value] (obj-set-item! item item-name item-value))
-  PyObject
-  (dir [item] (py-proto/dir (.getPointer item)))
-  (has-attr? [item item-name] (py-proto/has-attr? (.getPointer item) item-name))
-  (get-attr [item item-name] (py-proto/get-attr (.getPointer item) item-name))
-  (set-attr! [item item-name item-value]
-    (py-proto/set-attr! (.getPointer item) item-name item-value))
-  (callable? [item] (py-proto/callable? (.getPointer item)))
-  (has-item? [item item-name] (py-proto/has-item? (.getPointer item) item-name))
-  (item [item item-name] (py-proto/get-item (.getPointer item) item-name))
-  (set-item! [item item-name item-value]
-    (py-proto/set-item! (.getPointer item) item-name item-value)))
-
-
-;;This one is dangerous.  But there are times (like in the actual type object)
-;;that we don't want to be wrapping the results in any way; they are getting passed
-;;directly to python and not to java.
-(def ^:dynamic *passthrough-exceptions* false)
-
-
-(extend-protocol py-proto/PyCall
-  Pointer
-  (do-call-fn [callable arglist kw-arg-map]
-    (with-gil
-      (-> (cond
-            (seq kw-arg-map)
-            (libpy/PyObject_Call callable
-                                 (->py-tuple arglist)
-                                 (->py-dict kw-arg-map))
-            (seq arglist)
-            (libpy/PyObject_CallObject callable (->py-tuple arglist))
-            :else
-            (libpy/PyObject_CallObject callable nil))
-          (wrap-pyobject *passthrough-exceptions*))))
-  PyObject
-  (do-call-fn [callable arglist kw-arg-map]
-    (py-proto/do-call-fn (.getPointer callable) arglist kw-arg-map)))
-
-
-(extend-protocol py-proto/PPyObjLength
-  Pointer
-  (len [item]
-    (libpy/PyObject_Length item))
-  PyObject
-  (len [item]
-    (py-proto/len (.getPointer item))))
-
-
-(defn python->jvm-copy-hashmap
-  [pyobj & [map-items]]
-  (with-gil
-    (when-not (= 1 (libpy/PyMapping_Check pyobj))
-      (throw (ex-info (format "Object does not implement the mapping protocol: %s"
-                              (python-type pyobj)))))
-    (->> (or map-items
-             (libpy/PyMapping_Items pyobj)
-             wrap-pyobject)
-         ->jvm
-         (into {}))))
-
-
-(defn python->jvm-copy-persistent-vector
-  [pyobj]
-  (with-gil
-    (when-not (= 1 (libpy/PySequence_Check pyobj))
-      (throw (ex-info (format "Object does not implement sequence protocol: %s"
-                              (python-type pyobj)))))
-
-    (->> (range (libpy/PySequence_Length pyobj))
-         (mapv (fn [idx]
-                 (-> (libpy/PySequence_GetItem pyobj idx)
-                     wrap-pyobject
-                     ->jvm))))))
-
-
-(defn python->jvm-iterator
-  "This is a tough function to get right.  The iterator could return nil as in
-  you could have a list of python none types or something so you have to iterate
-  till you get a StopIteration error."
-  [iter-fn & [item-conversion-fn]]
-  (with-gil
-    (let [interpreter (ensure-bound-interpreter)]
-      (let [py-iter (py-proto/call iter-fn)
-            py-next-fn (when py-iter (py-proto/get-attr py-iter "__next__"))
-            next-fn (fn [last-item]
-                      (with-gil
-                        (let [retval (libpy/PyObject_CallObject py-next-fn nil)]
-                          (if (libpy/PyErr_Occurred)
-                            (let [ptype (PointerByReference.)
-                                  pvalue (PointerByReference.)
-                                  ptraceback (PointerByReference.)
-                                  _ (libpy/PyErr_Fetch ptype pvalue ptraceback)
-                                  ptype (jna/->ptr-backing-store ptype)
-                                  pvalue (jna/->ptr-backing-store pvalue)
-                                  ptraceback (jna/->ptr-backing-store ptraceback)]
-                              (if (= ptype
-                                     (libpy/PyExc_StopIteration))
-                                (do
-                                  (libpy/Py_DecRef ptype)
-                                  (when pvalue (libpy/Py_DecRef pvalue))
-                                  (when ptraceback (libpy/Py_DecRef ptraceback))
-                                  nil)
-                                (do (libpy/PyErr_Restore ptype pvalue ptraceback)
-                                    (check-error-throw))))
-                            [(cond-> (wrap-pyobject retval)
-                               item-conversion-fn
-                               item-conversion-fn)]))))
-            cur-item-store (atom (next-fn nil))]
-        (reify
-          jna/PToPtr
-          (is-jna-ptr-convertible? [item] true)
-          (->ptr-backing-store [item] py-iter)
-
-          java.util.Iterator
-          (hasNext [obj-iter]
-            (not (nil? @cur-item-store)))
-          (next [obj-iter]
-            (-> (swap-vals! cur-item-store next-fn)
-                ffirst)))))))
-
-
-(defn python->jvm-iterable
-  "Create an iterable that auto-copies what it iterates completely into the jvm.  It
-  maintains a reference to the python object, however, so this method isn't necessarily
-  safe."
-  [pyobj & [item-conversion-fn]]
-  (with-gil
-    (when-not (has-attr? pyobj "__iter__")
-      (throw (ex-info (format "object is not iterable: %s"
-                              (python-type pyobj))
-                      {})))
-    (let [item-conversion-fn (or item-conversion-fn ->jvm)
-          iter-callable (get-attr pyobj "__iter__")
-          interpreter (ensure-bound-interpreter)]
-      (reify
-        jna/PToPtr
-        (is-jna-ptr-convertible? [item] true)
-        (->ptr-backing-store [item] pyobj)
-        Iterable
-        (iterator [item]
-          (python->jvm-iterator iter-callable item-conversion-fn))))))
-
-
-(defmethod pyobject->jvm :int
-  [pyobj]
-  (with-gil
-    (libpy/PyLong_AsLongLong pyobj)))
-
-
-(defmethod pyobject->jvm :float
-  [pyobj]
-  (with-gil
-    (libpy/PyFloat_AsDouble pyobj)))
-
-
-(defmethod pyobject->jvm :none-type
-  [pyobj]
-  nil)
-
-
-(defmethod pyobject->jvm :str
-  [pyobj]
-  (with-gil
-    (py-string->string pyobj)))
-
-
-(defn pyobj-true?
-  [pyobj]
-  (with-gil
-    (= 1 (libpy/PyObject_IsTrue pyobj))))
-
-
-(defmethod pyobject->jvm :bool
-  [pyobj]
-  (pyobj-true? pyobj))
-
-
-(defmethod pyobject->jvm :tuple
-  [pyobj]
-  (python->jvm-copy-persistent-vector pyobj))
-
-
-(defmethod pyobject->jvm :list
-  [pyobj]
-  (python->jvm-copy-persistent-vector pyobj))
-
-
-(defmethod pyobject->jvm :dict
-  [pyobj]
-  (with-gil
-    (let [ppos (jna/size-t-ref 0)
-          pkey (PointerByReference.)
-          pvalue (PointerByReference.)
-          retval (java.util.ArrayList.)]
-      ;;Dictionary iteration doesn't appear to be reentrant so we have
-      ;;to do 2 passes.
-      (loop [next-retval (libpy/PyDict_Next pyobj ppos pkey pvalue)]
-        (if (not= 0 next-retval)
-          (do
-            (.add retval [(libpy/as-pyobj pkey)
-                          (libpy/as-pyobj pvalue)])
-            (recur (libpy/PyDict_Next pyobj ppos pkey pvalue)))
-          (->> retval
-               (map (fn [[k v]]
-                      [(->jvm k) (->jvm v)]))
-               (into {})))))))
-
-
-(defmethod pyobject->jvm :set
-  [pyobj]
-  (with-gil
-    (->> (python->jvm-iterable pyobj)
-         set)))
-
-
-;;numpy types
-(defn numpy-scalar->jvm
-  [pyobj]
-  (with-gil
-    (-> (py-proto/get-attr pyobj "data")
-        (py-proto/get-item (->py-tuple []))
-        ->jvm)))
-
-(defmethod pyobject->jvm :uint-8
-  [pyobj]
-  (numpy-scalar->jvm pyobj))
-
-
-(defmethod pyobject->jvm :int-8
-  [pyobj]
-  (numpy-scalar->jvm pyobj))
-
-
-(defmethod pyobject->jvm :uint-16
-  [pyobj]
-  (numpy-scalar->jvm pyobj))
-
-
-(defmethod pyobject->jvm :int-16
-  [pyobj]
-  (numpy-scalar->jvm pyobj))
-
-
-(defmethod pyobject->jvm :uint-32
-  [pyobj]
-  (numpy-scalar->jvm pyobj))
-
-
-(defmethod pyobject->jvm :int-32
-  [pyobj]
-  (numpy-scalar->jvm pyobj))
-
-
-(defmethod pyobject->jvm :uint-64
-  [pyobj]
-  (numpy-scalar->jvm pyobj))
-
-
-(defmethod pyobject->jvm :int-64
-  [pyobj]
-  (numpy-scalar->jvm pyobj))
-
-
-(defmethod pyobject->jvm :float-64
-  [pyobj]
-  (numpy-scalar->jvm pyobj))
-
-
-(defmethod pyobject->jvm :float-32
-  [pyobj]
-  (numpy-scalar->jvm pyobj))
-
-
-(defmethod pyobject->jvm :range
-  [pyobj]
-  (with-gil
-    (let [start (->jvm (py-proto/get-attr pyobj "start"))
-          step (->jvm (py-proto/get-attr pyobj "step"))
-          stop (->jvm (py-proto/get-attr pyobj "stop"))]
-      (range start stop step))))
-
-
-(defmethod pyobject->jvm :default
-  [pyobj]
-  (with-gil
-    (cond
-      ;;Things could implement mapping and sequence logically so mapping
-      ;;takes precedence
-      (= 1 (libpy/PyMapping_Check pyobj))
-      (if-let [map-items (try (-> (libpy/PyMapping_Items pyobj)
-                                  wrap-pyobject)
-                              (catch Throwable e nil))]
-        (python->jvm-copy-hashmap pyobj map-items)
-        (do
-          ;;Ignore error.  The mapping check isn't thorough enough to work.
-          (libpy/PyErr_Clear)
-          (python->jvm-copy-persistent-vector pyobj)))
-      ;;Sequences become persistent vectors
-      (= 1 (libpy/PySequence_Check pyobj))
-      (python->jvm-copy-persistent-vector pyobj)
-      :else
-      {:type (python-type pyobj)
-       :value (Pointer/nativeValue (libpy/as-pyobj pyobj))})))
-
-
-(defn is-instance?
-  "Returns true if inst is an instance of type.
-  False otherwise."
-  [py-inst py-type]
-  (with-gil
-    (= 1 (libpy/PyObject_IsInstance (->python py-inst)
-                                    ;;The type has to be a python type already.
-                                    py-type))))
-
-
-(defn hash-code
-  ^long [py-inst]
-  (with-gil
-    (long (libpy/PyObject_Hash (->python py-inst)))))
-
-
-(defn equals?
-  "Returns true of the python equals operator returns 1."
-  [lhs rhs]
-  (with-gil
-    (= 1 (libpy/PyObject_RichCompareBool (->python lhs)
-                                         (->python rhs)
-                                         :py-eq))))
diff --git a/src/libpython_clj/python/protocols.clj b/src/libpython_clj/python/protocols.clj
deleted file mode 100644
index 2f36bf1..0000000
--- a/src/libpython_clj/python/protocols.clj
+++ /dev/null
@@ -1,182 +0,0 @@
-(ns libpython-clj.python.protocols
-  "Protocols to help generalize the python bindings.  There is a clear distinction
-  made between building a bridging type and thus allowing communication between the
-  two systems and building a complete copy of the datastructure of one system in
-  another.  Generic objects must only bridge but if we know more about the object
-  (like it implements java.util.Map) then we can implement either a bridge or a
-  copy.
-
-  Atomic objects:
-  * numbers
-  * booleans
-  * strings-convertible things like strings, keywords, symbols
-
-  Standard containers:
-  * list
-  * map
-  * set
-  * tuple (persistent vector of length less than 8)")
-
-
-(defprotocol PPythonType
-  (get-python-type [item]
-    "Return a keyword that describes the python datatype of this object."))
-
-
-(defn python-type
-  "Return a keyword that describes the python datatype of this object."
-  [item]
-  (if item
-    (get-python-type item)
-    :none-type))
-
-
-(defprotocol PCopyToPython
-  (->python [item options]
-    "Copy this item into a python representation.  Must never return nil.
-Items may fallback to as-python if copying is untenable."))
-
-
-(defprotocol PBridgeToPython
-  (as-python [item options]
-    "Aside from atom types, this means the object represented by a zero copy python
-    mirror.  May return nil.  This convertible to pointers get converted
-    to numpy implementations that share the backing store."))
-
-
-
-(defprotocol PCopyToJVM
-  (->jvm [item options]
-    "Copy the python object into the jvm leaving no references.  This not copying
-are converted into a {:type :pyobject-address} pairs."))
-
-
-(defprotocol PBridgeToJVM
-  (as-jvm [item options]
-    "Return a pyobject implementation that wraps the python object."))
-
-
-(extend-type Object
-  PBridgeToPython
-  (as-python [item options] nil)
-  PCopyToJVM
-  (->jvm [item options] item)
-  PBridgeToJVM
-  (as-jvm [item options] item))
-
-
-(extend-type Object
-  PBridgeToJVM
-  (as-jvm [item options] item))
-
-
-(defprotocol PPyObject
-  (dir [item]
-    "Get sorted list of all attribute names.")
-  (has-attr? [item item-name] "Return true of object has attribute")
-  (get-attr [item item-name] "Get attribute from object")
-  (set-attr! [item item-name item-value] "Set attribute on object")
-  (callable? [item] "Return true if object is a python callable object.")
-  (has-item? [item item-name] "Return true of object has item")
-  (get-item [item item-name] "Get an item of a given name from an object")
-  (set-item! [item item-name item-value] "Set an item of to a value"))
-
-
-(defprotocol PPyAttMap
-  (att-type-map [item]
-    "Get hashmap of att name to keyword datatype."))
-
-
-(extend-type Object
-  PPyObject
-  (callable? [_] false)
-  (has-attr? [_ _] false)
-  PPyAttMap
-  (att-type-map [item]
-    (->> (dir item)
-         (map (juxt identity #(try
-                                (-> (get-attr item %)
-                                    python-type)
-                                (catch Throwable e
-                                  :get-attr-exception!!))))
-         (into (sorted-map)))))
-
-
-(defprotocol PyCall
-  (do-call-fn [callable arglist kw-arg-map]))
-
-
-(defn call
-  "Call a python function with positional args.  For keyword args, see call-kw."
-  [callable & args]
-  (do-call-fn callable args nil))
-
-
-(defn call-kw
-  "Call a python function with a vector of positional args and a map of keyword args."
-  [callable arglist kw-args]
-  (do-call-fn callable arglist kw-args))
-
-(defn call-attr
-  "Call an object attribute with positional arguments."
-  [item att-name & args]
-  (-> (get-attr item att-name)
-      (do-call-fn args nil)))
-
-
-(defn call-attr-kw
-  "Call an object attribute with a vector of positional args and a
-  map of keyword args."
-  [item att-name arglist kw-map]
-  (-> (get-attr item att-name)
-      (do-call-fn arglist kw-map)))
-
-
-(defprotocol PPyObjLength
-  "Call the __len__ attribute."
-  (len [item] "Call the __len__ attribute."))
-
-
-(extend-type Object
-  PPyObjLength
-  (len [item]
-    (-> (call-attr item "__len__")
-        (->jvm {}))))
-
-
-(defprotocol PPyObjectBridgeToMap
-  (as-map [item]
-    "Return a Map implementation using __getitem__, __setitem__.  Note that it may be
-incomplete especially if the object has no 'keys' attribute."))
-
-
-(defprotocol PPyObjectBridgeToList
-  (as-list [item]
-    "Return a List implementation using __getitem__, __setitem__."))
-
-
-(defprotocol PJvmToNumpyBridge
-  (as-numpy [item options]
-    "Never copy data, operation returns nil of would require copy."))
-
-
-(defprotocol PJvmToNumpy
-  (->numpy [item options]
-    "Never return nil.  Copy or otherwise, numpy or bust."))
-
-
-(defmulti pyobject->jvm
-  (fn [pyobj]
-    (python-type pyobj)))
-
-
-(defmulti pyobject-as-jvm
-  (fn [pyobj]
-    (python-type pyobj)))
-
-(defmulti python-obj-iterator
-  "Given a python object, produce an iterator.  Python is fairly confused about
-  what an iterator is or does, so some things require iteritems and some things
-  require calling __iter__."
-  (fn [pyobj interpreter]
-    (python-type pyobj)))
diff --git a/src/libpython_clj/metadata.clj b/src/libpython_clj2/metadata.clj
similarity index 84%
rename from src/libpython_clj/metadata.clj
rename to src/libpython_clj2/metadata.clj
index 4f5f7c7..a4fdc3f 100644
--- a/src/libpython_clj/metadata.clj
+++ b/src/libpython_clj2/metadata.clj
@@ -1,20 +1,24 @@
-(ns libpython-clj.metadata
+(ns libpython-clj2.metadata
+  "Namespace to create metadata from python objects.   This namespace requires
+  python helper fns to work correctly and thus cannot be used until after python
+  has been initialized."
   (:refer-clojure :exclude [fn? doc])
-  (:require [libpython-clj.python
+  (:require [libpython-clj2.python
              :refer [import-module as-jvm get-attr call-attr callable? has-attr?
                      ->jvm with-gil]
              :as py]
-            [libpython-clj.python.protocols :as py-proto]
+            [libpython-clj2.python.ffi :as py-ffi]
+            [libpython-clj2.python.protocols :as py-proto]
+            [tech.v3.datatype.ffi :as dt-ffi]
             [clojure.core.protocols :as clj-proto]
-            [clojure.tools.logging :as log])
-  (:import [libpython_clj.python.protocols PPyObject]))
+            [clojure.tools.logging :as log]))
 
 
 (py/initialize!)
 
 
-(def builtins (as-jvm (import-module "builtins") {}))
-(def inspect (as-jvm (import-module "inspect") {}))
+(def builtins (import-module "builtins"))
+(def inspect (import-module "inspect"))
 (def argspec (get-attr inspect "getfullargspec"))
 (def py-source (get-attr inspect "getsource"))
 (def py-sourcelines (get-attr inspect "getsourcelines"))
@@ -52,7 +56,7 @@
 
 
 (defn find-lineno [x]
-  (try 
+  (try
     (-> x py-sourcelines last)
     (catch Exception _
       nil)))
@@ -67,13 +71,13 @@
   (if-let [spec (try (when-not (pyclass? f)
                        (argspec f))
                      (catch Throwable e nil))]
-    {:args           (->jvm (get-attr spec "args") {})
-     :varargs        (->jvm (get-attr spec "varargs") {})
-     :varkw          (->jvm (get-attr spec "varkw") {})
-     :defaults       (->jvm (get-attr spec "defaults") {})
-     :kwonlyargs     (->jvm (get-attr spec "kwonlyargs") {})
-     :kwonlydefaults (->jvm (get-attr spec "kwonlydefaults") {})
-     :annotations    (->jvm (get-attr spec "annotations") {})}
+    {:args           (->jvm (get-attr spec "args"))
+     :varargs        (->jvm (get-attr spec "varargs"))
+     :varkw          (->jvm (get-attr spec "varkw"))
+     :defaults       (->jvm (get-attr spec "defaults"))
+     :kwonlyargs     (->jvm (get-attr spec "kwonlyargs"))
+     :kwonlydefaults (->jvm (get-attr spec "kwonlydefaults"))
+     :annotations    (->jvm (get-attr spec "annotations"))}
     (py-fn-argspec (get-attr f "__init__"))))
 
 (defn py-class-argspec [class]
@@ -202,7 +206,9 @@
 
 (defn base-pyobj-map
   [item]
-  (cond-> {:type  (py/python-type item)
+  (cond-> {:type  (cond (pyclass? item) :type
+                        (pymodule? item) :module
+                        :else (py/python-type item))
            :doc   (doc item)
            :str   (.toString item)
            :flags (pyobj-flags item)
@@ -221,12 +227,12 @@
   (or (string? att-val)
       (number? att-val)))
 
-(defn datafy-module [item]
+(defn datafy-module-or-class [item]
   (with-gil
     (->> (if (or (pyclass? item)
                  (pymodule? item))
            (->> (vars item)
-                (py-proto/as-map)
+                (py/as-map)
                 (into {}))
            (->> (py/dir item)
                 (map (juxt identity #(get-attr item %)))))
@@ -247,14 +253,57 @@
          (remove nil?)
          (into (base-pyobj-map item)))))
 
+
+(defmethod py-proto/pydatafy :default
+  [pyobj]
+  (if (or (pyclass? pyobj)
+          (pymodule? pyobj))
+    (datafy-module-or-class pyobj)
+    (base-pyobj-map pyobj)))
+
+
+(defmethod py-proto/pydatafy :dict
+  [pyobj]
+  (->> (py/as-jvm pyobj)
+       (map (fn [[k v]]
+              [k (clj-proto/datafy v)]))
+       (into {})))
+
+
+(defmethod py-proto/pydatafy :list
+  [pyobj]
+  (->> (py/as-jvm pyobj)
+       (mapv clj-proto/datafy)))
+
+
+(defmethod py-proto/pydatafy :tuple
+  [pyobj]
+  (->> (py/as-jvm pyobj)
+       (mapv clj-proto/datafy)))
+
+
+(defmethod py-proto/pydatafy :set
+  [pyobj]
+  (->> (py/as-jvm pyobj)
+       (map clj-proto/datafy)
+       (set)))
+
+
+(defmethod py-proto/pydatafy :frozenset
+  [pyobj]
+  (->> (py/as-jvm pyobj)
+       (map clj-proto/datafy)
+       (set)))
+
+
 (defn nav-module [coll f val]
   (with-gil
     (if (map? val)
       (cond
         (= :module (:type val))
-        (as-jvm (import-module (:name val)) {})
+        (import-module (:name val))
         (= :type (:type val))
-        (let [mod (as-jvm (import-module (:module val)) {})
+        (let [mod (import-module (:module val))
               cls-obj (get-attr mod (:name val))]
           cls-obj)
         :else
@@ -301,7 +350,8 @@
       :module (import-module (:name metadata-map))
       :type   (-> (import-module (:module metadata-map))
                   (get-attr (:name metadata-map))))
-    (catch Exception _
+    (catch Exception e
+      (log/warnf e "metadata-map: %s" metadata-map)
       ;; metatypes -- e.g. socket.SocketIO
       (-> (import-module (:module metadata-map))
           (get-attr (:name metadata-map))))))
diff --git a/src/libpython_clj2/python.clj b/src/libpython_clj2/python.clj
new file mode 100644
index 0000000..99587dd
--- /dev/null
+++ b/src/libpython_clj2/python.clj
@@ -0,0 +1,575 @@
+(ns libpython-clj2.python
+  "Python bindings for Clojure.  This library dynamically finds the installed
+  python, loads the shared library and allows Clojure users to use Python modules
+  as if they were Clojure namespaces.
+
+
+Example:
+
+  ```clojure
+user> (require '[libpython-clj2.python :as py])
+nil
+user> (py/initialize!)
+;;  ... (logging)
+:ok
+user> (def np (py/import-module \"numpy\"))
+#'user/np
+user> (py/py. np linspace 2 3 :num 10)
+[2.         2.11111111 2.22222222 2.33333333 2.44444444 2.55555556
+ 2.66666667 2.77777778 2.88888889 3.        ]
+```"
+  (:require [libpython-clj2.python.info :as py-info]
+            [libpython-clj2.python.ffi :as py-ffi]
+            [libpython-clj2.python.base :as py-base]
+            [libpython-clj2.python.fn :as py-fn]
+            [libpython-clj2.python.protocols :as py-proto]
+            [libpython-clj2.python.class :as py-class]
+            [libpython-clj2.python.with :as py-with]
+            [libpython-clj2.python.dechunk-map :refer [dechunk-map]]
+            [libpython-clj2.python.copy :as py-copy]
+            [libpython-clj2.python.bridge-as-jvm :as py-bridge-jvm]
+            [libpython-clj2.python.bridge-as-python]
+            [libpython-clj2.python.io-redirect :as io-redirect]
+            [libpython-clj2.python.gc :as pygc]
+            [libpython-clj2.python.windows :as win]
+            [tech.v3.datatype.ffi :as dtype-ffi]
+            [clojure.tools.logging :as log])
+  (:import [java.util Map List]
+           [clojure.lang IFn]))
+
+
+
+(defn initialize!
+    "Initialize the python library.  If library path is not provided, then the system
+  attempts to execute a simple python program and have python return system info.
+
+
+  Returns either `:ok` in which case the initialization completed successfully or
+  `:already-initialized` in which case we detected that python has already been
+  initialized via `Py_IsInitialized` and we do nothing more.
+
+  Options:
+
+  * `:library-path` - Library path of the python library to use.
+  * `:program-name` - Optional -- will show up in error messages from python.
+  * `:no-io-redirect?` - True if you don't want python stdout and stderr redirection
+     to *out* and *err*.
+  * `:python-executable` - The python executable to use to find system information.
+  * `:python-home` - Python home directory.  The system first uses this variable, then
+     the environment variable PYTHON_HOME, and finally information returned from
+     python system info.
+  * `:signals?` - defaults to false - true if you want python to initialized signals.
+     Be aware that the JVM itself uses quite a few signals - SIGSEGV, for instance -
+     during it's normal course of operation.  For more information see:
+       * [used signals](https://docs.oracle.com/javase/10/troubleshoot/handle-signals-and-exceptions.htm#JSTGD356)
+       * [signal-chaining](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/signal-chaining.html)"
+  [& [{:keys [windows-anaconda-activate-bat
+              library-path
+              no-io-redirect?]} options]]
+  (if-not (and (py-ffi/library-loaded?)
+                 (= 1 (py-ffi/Py_IsInitialized)))
+    (let [info (py-info/detect-startup-info options)
+          _ (log/infof "Startup info %s" info)
+          libname (->> (concat (when library-path [library-path]) (:libnames info))
+                       (dechunk-map identity)
+                       (filter #(try
+                                  (boolean (dtype-ffi/library-loadable? %))
+                                  (catch Throwable e false)))
+                       (first))]
+      (log/infof "Loading python library: %s" libname)
+      (py-ffi/initialize! libname (:python-home info)
+                          (assoc options :program-name
+                                 (or (:program-name options)
+                                     (:executable info)
+                                     "")))
+
+      (when-not (nil? windows-anaconda-activate-bat)
+        (win/setup-windows-conda! windows-anaconda-activate-bat
+                                  py-ffi/run-simple-string))
+
+      (when-not no-io-redirect?
+        (io-redirect/redirect-io!))
+      :ok)
+    :already-initialized))
+
+
+(defmacro stack-resource-context
+  "Create a stack-based resource context.  All python objects allocated within this
+  context will be released at the termination of this context.
+  !!This means that no python objects can escape from this context!!
+  You must use copy semantics (->jvm) for anything escaping this context.
+  Furthermore, if you are returning generic python objects you may need
+  to call (into {}) or something like that just to ensure that absolutely
+  everything is copied into the jvm."
+  [& body]
+  `(pygc/with-stack-context
+     ~@body))
+
+
+(defmacro with-gil
+  "Capture the gil for an extended amount of time.  This can greatly speed up
+  operations as the mutex is captured and held once as opposed to find grained
+  grabbing/releasing of the mutex."
+  [& body]
+  `(py-ffi/with-gil
+     ~@body))
+
+
+(defmacro with-gil-stack-rc-context
+  "Capture the gil, open a resource context.  The resource context is released
+  before the gil is leading to much faster resource collection.  See documentation
+  on `stack-resource-context` for multiple warnings; the most important one being
+  that if a python object escapes this context your program will eventually, at
+  some undefined point in the future crash.  That being said, this is the recommended
+  pathway to use in production contexts where you want defined behavior and timings
+  related to use of python."
+  [& body]
+  `(py-ffi/with-gil
+     (pygc/with-stack-context
+      ~@body)))
+
+
+(defn import-module
+  "Import a python module returning an implementation of java.util.Map wrapping
+  the module object and consisting of module attributes."
+  [modname]
+  (with-gil
+    (if-let [mod (py-ffi/PyImport_ImportModule modname)]
+      (-> (py-ffi/track-pyobject mod)
+          (py-base/as-jvm))
+      (py-ffi/check-error-throw))))
+
+
+(defn add-module
+  "Add a python module.  This can create a module if it doesn't exist."
+  [modname]
+  (with-gil
+    (-> (py-ffi/PyImport_AddModule modname)
+        (py-ffi/incref-track-pyobject)
+        (py-base/as-jvm))))
+
+
+(defn module-dict
+  "Get the module dictionary."
+  [mod]
+  (with-gil
+    (-> (py-ffi/PyModule_GetDict mod)
+        (py-ffi/incref-track-pyobject)
+        (py-base/as-jvm))))
+
+
+(defn dir
+  [pyobj]
+  (with-gil (py-proto/dir pyobj)))
+
+
+(defn call-attr
+  "Call an attribute on a python object using only positional arguments"
+  [pyobj attname & args]
+  (with-gil (py-fn/call-attr pyobj attname args)))
+
+
+(defn call-attr-kw
+  "Call an attribute passing in both positional and keyword arguments."
+  [pyobj attname args kw-list]
+  (with-gil (py-fn/call-attr-kw pyobj attname args kw-list py-base/as-python)))
+
+(defn get-attr
+  "Get an attribute from a python object"
+  [pyobj attname]
+  (with-gil (py-proto/get-attr pyobj attname)))
+
+
+(defn set-attr!
+  "Set an attribute on a python object.  Returns pyobj."
+  [pyobj attname attval]
+  (with-gil (py-proto/set-attr! pyobj attname attval))
+  pyobj)
+
+
+(defn set-attrs!
+  "Set a sequence of [name value] attributes.  Returns pyobj."
+  [pyobj att-seq]
+  (with-gil (doseq [[k v] att-seq] (set-attr! pyobj k v)))
+  pyobj)
+
+
+(defn has-attr?
+  "Return true if this python object has this attribute."
+  [pyobj att-name]
+  (py-proto/has-attr? pyobj att-name))
+
+
+(defn get-item
+  "Get an item from a python object using  __getitem__"
+  [pyobj item-name]
+  (with-gil (py-proto/get-item pyobj item-name)))
+
+
+(defn set-item!
+  "Get an item from a python object using  __setitem__"
+  [pyobj item-name item-val]
+  (with-gil (py-proto/set-item! pyobj item-name item-val))
+  pyobj)
+
+
+(defn set-items!
+  "Set a sequence of [name value]. Returns pyobj"
+  [pyobj item-seq]
+  (with-gil (doseq [[k v] item-seq] (set-item! pyobj k v)))
+  pyobj)
+
+
+(defn ->python
+  "Copy a jvm value into a python object"
+  [v]
+  (py-ffi/with-gil (py-base/->python v)))
+
+
+(defn as-python
+  "Bridge a jvm value into a python object"
+  [v]
+  (py-ffi/with-gil (py-base/as-python v)))
+
+
+(defn ->jvm
+  "Copy a python value into java datastructures"
+  [v & [opts]]
+  (py-ffi/with-gil (py-base/->jvm v opts)))
+
+
+(defn as-jvm
+  "Copy a python value into java datastructures"
+  [v & [opts]]
+  (py-ffi/with-gil (py-base/as-jvm v opts)))
+
+
+(defn as-map
+  "Make a python object appear as a map of it's items"
+  ^Map [pobj]
+  (py-bridge-jvm/generic-python-as-map pobj))
+
+
+(defn as-list
+  "Make a python object appear as a list"
+  ^List [pobj]
+  (py-bridge-jvm/generic-python-as-list pobj))
+
+
+(defn python-type
+  "Get the type (as a keyword) of a python object"
+  [v]
+  (py-ffi/with-gil (py-proto/python-type v)))
+
+
+(defn is-instance?
+  "Return true if inst is an instance of cls.  Note that arguments
+  are reversed as compared to `instance?`"
+  [py-inst py-cls]
+  (py-ffi/with-gil
+    (let [retval (long (py-ffi/PyObject_IsInstance py-inst py-cls))]
+      (case retval
+        0 false
+        1 true
+        (py-ffi/check-error-throw)))))
+
+
+(defn callable?
+  "Return true if python object is callable."
+  [pyobj]
+  (cond
+    (instance? IFn pyobj)
+    true
+    (dtype-ffi/convertible-to-pointer? pyobj)
+    (py-ffi/with-gil
+      (let [retval (long (py-ffi/PyCallable_Check pyobj))]
+        (case retval
+          0 false
+          1 true
+          (py-ffi/check-error-throw))))
+    :else
+    false))
+
+
+(defn ->py-list
+  "Copy the data into a python list"
+  [v]
+  (py-ffi/with-gil (-> (py-copy/->py-list v) (as-jvm))))
+
+
+(defn ->py-tuple
+  "Copy v into a python tuple"
+  [v]
+  (py-ffi/with-gil (-> (py-copy/->py-tuple v) (as-jvm))))
+
+
+(defn ->py-dict
+  "Copy v into a python dict"
+  [v]
+  (py-ffi/with-gil (-> (py-copy/->py-dict v) (as-jvm))))
+
+
+(defn run-simple-string
+  "Run a string expression returning a map of
+  {:globals :locals}.
+  This uses the global __main__ dict under the covers so it matches the behavior
+  of the cpython implementation with the exception of returning the various maps
+  used.
+
+  Note this will never return the result of the expression:
+  https://mail.python.org/pipermail/python-list/1999-April/018011.html
+
+  Globals, locals may be provided but are not necessary.
+
+  Implemented in cpython as:
+
+    PyObject *m, *d, *v;
+    m = PyImport_AddModule(\"__main__\");
+    if (m == NULL)
+        return -1;
+    d = PyModule_GetDict(m);
+    v = PyRun_StringFlags(command, Py_file_input, d, d, flags);
+    if (v == NULL) {
+        PyErr_Print();
+        return -1;
+    }
+    Py_DECREF(v);
+    return 0;"
+  [program & {:keys [globals locals]}]
+  (->> (py-ffi/run-simple-string program :globals globals :locals locals)
+       (map (fn [[k v]]
+              [k (py-base/as-jvm v)]))
+       (into {})))
+
+
+(defn make-callable
+  "Make a python callable object from a clojure function.  This is called for you
+  if you use `as-python` on an implementation of IFn.
+
+Options:
+  * `:arg-converter` - Function called for each function argument before your ifn
+     gets access to it.  Defaults to `->jvm`.
+  * `:result-converter` - Function called on return value before it gets returned to
+     python.  Must return a python object.  Defaults to `->python`; the result will
+     get an extra incref before being returned to Python to account for the implied
+     tracking of `as-python` or `->python`.
+  * `:name` - Name of the python method.  This will appear in stack traces.
+  * `:doc` - documentation for method."
+  ([ifn options]
+   (py-fn/make-tuple-fn ifn options))
+  ([ifn] (make-callable ifn nil)))
+
+
+(defn ^:no-doc make-tuple-fn
+  "Deprecated - use make-callable"
+  [ifn & {:as options}]
+  (make-callable ifn options))
+
+
+(defn make-instance-fn
+  "Make an callable instance function - a function which will be passed the 'this'
+  object as it's first argument.  In addition, this function calls `make-callable`
+  with a `arg-converter` defaulted to `as-jvm`.  See documentation for
+  make-callable."
+  ([ifn options] (py-class/make-tuple-instance-fn ifn options))
+  ([ifn] (make-instance-fn ifn nil)))
+
+
+(defn ^:no-doc make-tuple-instance-fn
+  [ifn & {:as options}]
+  (make-instance-fn ifn options))
+
+
+(defn create-class
+  "Create a new class object.  Any callable values in the cls-hashmap
+  will be presented as instance methods.  If you want access to the
+  'this' object then you must use `make-instance-fn`.
+
+  Example:
+
+```clojure
+user> (require '[libpython-clj2.python :as py])
+nil
+user> (def cls-obj (py/create-class
+                    \"myfancyclass\"
+                    nil
+                    {\"__init__\" (py/make-instance-fn
+                                 (fn [this arg]
+                                   (py/set-attr! this \"arg\" arg)
+                                   ;;If you don't return nil from __init__ that is an
+                                   ;;error.
+                                   nil))
+                     \"addarg\" (py/make-instance-fn
+                               (fn [this otherarg]
+                                 (+ (py/get-attr this \"arg\")
+                                    otherarg)))}))
+#'user/cls-obj
+user> cls-obj
+__no_module__.myfancyclass
+user> (def inst (cls-obj 10))
+#'user/inst
+user> (py/call-attr inst \"addarg\" 10)
+20
+```"
+  [name bases cls-hashmap]
+  (py-class/create-class name bases cls-hashmap))
+
+
+(defn cfn
+  "Call an object.
+  Arguments are passed in positionally.  Any keyword
+  arguments are paired with the next arg, gathered, and passed into the
+  system as *kwargs.
+
+  Not having an argument after a keyword argument is an error."
+  [item & args]
+  (apply py-fn/cfn item args))
+
+
+(defn afn
+  "Call an attribute of an object.
+  Arguments are passed in positionally.  Any keyword
+  arguments are paired with the next arg, gathered, and passed into the
+  system as *kwargs.
+
+  Not having an argument after a keyword is an error."
+  [item attr & args]
+  (apply py-fn/afn item attr args))
+
+
+(defmacro with
+  "Support for the 'with' statement in python:
+  (py/with [item (py/call-attr testcode-module \"WithObjClass\" true fn-list)]
+      (py/call-attr item \"doit_err\"))"
+  [bind-vec & body]
+  `(py-with/with ~bind-vec ~@body))
+
+
+(defmacro $a
+  "Call an attribute of an object using automatic detection of the python kwargs.
+  Keywords must be compile time constants.  So this won't work with 'apply'.  On the
+  other hand, building the positional and kw argmaps happens at compile time as
+  opposed to at runtime.  The attr name can be a symbol."
+  [item attr & args]
+  (let [[pos-args kw-args] (py-fn/args->pos-kw-args args)]
+    `(call-attr-kw ~item ~(py-fn/key-sym-str->str attr)
+                   ~pos-args ~kw-args)))
+
+
+(defmacro $c
+  "Call an object using automatic detection of the python kwargs.
+  Keywords must be compile time constants.  So this won't work with 'apply'.  On the
+  other hand, building the positional and kw argmaps happens at compile time as
+  opposed to at runtime."
+  [item & args]
+  (let [[pos-args kw-args] (py-fn/args->pos-kw-args args)]
+    `(py-fn/call-kw ~item ~pos-args ~kw-args)))
+
+
+(defmacro py.-
+  "Class/object getter syntax.  (py.- obj attr) is equivalent to
+  Python's obj.attr syntax."
+  [x arg]
+  `(get-attr ~x ~(py-fn/key-sym-str->str arg)))
+
+
+(defmacro py.
+  "Class/object method syntax.  (py. obj method arg1 arg2 ... argN)
+  is equivalent to Python's obj.method(arg1, arg2, ..., argN) syntax."
+  [x & args]
+  (list* (into (vector #'$a x) args)))
+
+
+(defmacro py*
+  "Special syntax for passing along *args and **kwargs style arguments
+  to methods.
+
+  Usage:
+
+  (py* obj method args kwargs)
+
+  Example:
+
+  (def d (python/dict))
+  d ;;=> {}
+  (def iterable [[:a 1] [:b 2]])
+  (def kwargs {:cat \"dog\" :name \"taco\"})
+  (py* d  update [iterable] kwargs)
+  d ;;=> {\"a\": 1, \"b\": 2, \"cat\": \"dog\", \"name\": \"taco\"}"
+  ([x method args]
+   (list #'call-attr-kw x (py-fn/key-sym-str->str method) args nil))
+  ([x method args kwargs]
+   (list #'call-attr-kw x (py-fn/key-sym-str->str method) args kwargs)))
+
+
+(defmacro py**
+  "Like py*, but it is assumed that the LAST argument is kwargs."
+  ([x method kwargs]
+   (list #'call-attr-kw x (str method) nil kwargs))
+  ([x method arg & args]
+   (let [args   (into [arg] args)
+         kwargs (last args)
+         args   (vec (pop args))]
+     (list #'call-attr-kw x (py-fn/key-sym-str->str method) args kwargs))))
+
+
+(defn ^:private handle-pydotdot
+  ([x form]
+   (if (list? form)
+     (let [form-data (vec form)
+           [instance-member & args] form-data
+           symbol-str (str instance-member)]
+       (cond
+         (clojure.string/starts-with? symbol-str "-")
+         (list #'py.- x (symbol (subs symbol-str 1 (count symbol-str))))
+
+         (clojure.string/starts-with? symbol-str "**")
+         (list* #'py** x (symbol (subs symbol-str 2 (count symbol-str))) args)
+
+         (clojure.string/starts-with? symbol-str "*")
+         (list* #'py* x (symbol (subs symbol-str 1 (count symbol-str))) args)
+
+         :else ;; assumed to be method invocation
+
+         (list* (into (vector #'py. x instance-member) args))))
+     (handle-pydotdot x (list form))))
+  ([x form & more]
+   (apply handle-pydotdot (handle-pydotdot x form) more)))
+
+
+(defmacro py..
+  "Extended accessor notation, similar to the `..` macro in Clojure.
+
+  (require-python 'sys)
+  (py.. sys -path (append \"/home/user/bin\"))
+
+  is equivalent to Python's
+
+  import sys
+  sys.path.append('/home/user/bin')
+
+  SPECIAL SYNTAX for programmatic *args and **kwargs
+
+  Special syntax is provided to meet the needs required by
+  Python's *args and **kwargs syntax programmatically.
+
+
+  (= (py.. obj (*method args))
+     (py* obj methods args))
+
+  (= (py.. obj (*method args kwargs))
+     (py* obj method args kwargs))
+
+  (= (py.. obj (**method kwargs))
+     (py** obj method kwargs))
+
+  (= (py.. obj (**method arg1 arg2 arg3 ... argN kwargs))
+     (py** obj method arg1 arg2 arg3 ... argN kwargs)
+     (py*  obj method [arg1 arg2 arg3 ... argN] kwargs))
+
+
+  These forms exist for when you need to pass in a map of options
+  in the same way you would use the f(*args, **kwargs) forms in
+  Python."
+  [x & args]
+  (apply handle-pydotdot x args))
diff --git a/src/libpython_clj2/python/base.clj b/src/libpython_clj2/python/base.clj
new file mode 100644
index 0000000..0eec110
--- /dev/null
+++ b/src/libpython_clj2/python/base.clj
@@ -0,0 +1,190 @@
+(ns libpython-clj2.python.base
+  "Shared basic functionality and wrapper functions"
+  (:require [libpython-clj2.python.protocols :as py-proto]
+            [libpython-clj2.python.ffi :as py-ffi]
+            [tech.v3.datatype.ffi :as dt-ffi]
+            [camel-snake-kebab.core :as csk])
+  (:import [tech.v3.datatype.ffi Pointer]))
+
+
+(defn ->jvm
+  "Copying conversion to the jvm."
+  ([obj]
+   (when obj
+     (py-proto/->jvm obj nil)))
+  ([obj opts]
+   (when obj
+     (py-proto/->jvm obj opts))))
+
+
+(defn as-jvm
+  "Bridge/proxy conversion to the jvm"
+  ([obj]
+   (as-jvm obj nil))
+  ([obj opts]
+   (when obj
+     (py-proto/as-jvm obj opts))))
+
+
+(defn ->python
+  "Copying conversion to python"
+  ([obj]
+   (->python obj nil))
+  ([obj opts]
+   (cond
+     (nil? obj)
+     (py-ffi/py-none)
+     (boolean? obj)
+     (if obj (py-ffi/py-true) (py-ffi/py-false))
+     :else
+     (py-proto/->python obj nil))))
+
+
+(defn ->python-incref
+  [pyobj]
+  (let [pyobj (->python pyobj)]
+    (py-ffi/Py_IncRef pyobj)
+    pyobj))
+
+
+(defn as-python
+  "Bridge/proxy conversion to python"
+  ([obj]
+   (as-python obj nil))
+  ([obj opts]
+   (cond
+     (nil? obj)
+     (py-ffi/py-none)
+     (boolean? obj)
+     (if obj (py-ffi/py-true) (py-ffi/py-false))
+     :else
+     (py-proto/as-python obj nil))))
+
+
+(defn stringable?
+  [item]
+  (or (keyword? item)
+      (string? item)
+      (symbol? item)))
+
+
+(defn stringable
+  ^String [item]
+  (when (stringable? item)
+    (if (string? item)
+      item
+      (name item))))
+
+
+;;base defaults for forwarding calls
+(extend-type Object
+  py-proto/PCopyToJVM
+  (->jvm [item options]
+    (if (dt-ffi/convertible-to-pointer? item)
+      (py-proto/pyobject->jvm item options)
+      ;;item is already a jvm object
+      item))
+  py-proto/PBridgeToJVM
+  (as-jvm [item options]
+    (if (dt-ffi/convertible-to-pointer? item)
+      (py-proto/pyobject-as-jvm item options)
+      item))
+  py-proto/PPyCallable
+  (callable? [this] false)
+  py-proto/PPyAttr
+  (has-attr? [this attr-name] false)
+  py-proto/PPyItem
+  (has-item? [this item-name] false)
+  py-proto/PPyDir
+  (dir [this] []))
+
+
+(extend-protocol py-proto/PPythonType
+  Boolean
+  (get-python-type [item] :bool)
+  Number
+  (get-python-type [item]
+    (if (integer? item) :int :float))
+  String
+  (get-python-type [item] :str)
+  Object
+  (get-python-type [item] (py-ffi/pyobject-type-kwd item)))
+
+
+(extend-type Pointer
+  py-proto/PCopyToJVM
+  (->jvm [item options]
+    (py-proto/pyobject->jvm item options))
+  py-proto/PBridgeToJVM
+  (as-jvm [item options]
+    (py-proto/pyobject-as-jvm item options))
+  py-proto/PPyDir
+  (dir [item]
+    (py-ffi/with-decref [dirlist (py-ffi/PyObject_Dir item)]
+      (if dirlist
+        (->jvm dirlist)
+        (py-ffi/check-error-throw))))
+  py-proto/PPyAttr
+  (has-attr? [item item-name]
+    (if (stringable? item-name)
+      (= 1 (py-ffi/PyObject_HasAttrString item (stringable item-name)))
+      (= 1 (py-ffi/PyObject_HasAttr item (->python item-name nil)))))
+  (get-attr [item item-name]
+    (->
+     (if (stringable? item-name)
+       (py-ffi/PyObject_GetAttrString item (stringable item-name))
+       (py-ffi/with-decref [item-name (py-ffi/untracked->python item-name ->python)]
+         (py-ffi/PyObject_GetAttr item item-name)))
+        (py-ffi/simplify-or-track)))
+  (set-attr! [item item-name item-value]
+    (let [item-val (->python item-value)]
+      (if (stringable? item-name)
+        (py-ffi/PyObject_SetAttrString item (stringable item-name) item-val)
+        (py-ffi/PyObject_SetAttr item (->python item-name) item-val)))
+    (py-ffi/check-error-throw)
+    nil)
+  py-proto/PPyCallable
+  (callable? [item]
+    (== 1 (long (py-ffi/PyCallable_Check item))))
+  py-proto/PPyItem
+  (has-item? [item item-name]
+    (if (stringable? item-name)
+      (= 1 (py-ffi/PyObject_HasAttrString item (stringable item-name)))
+      (= 1 (py-ffi/PyObject_HasAttr item (->python item-name)))))
+  (get-item [item item-name]
+    (py-ffi/with-decref [item-name (py-ffi/untracked->python item-name ->python)]
+      (-> (py-ffi/PyObject_GetItem item item-name)
+          (py-ffi/simplify-or-track))))
+  (set-item! [item item-name item-value]
+    (py-ffi/with-decref [item-name (py-ffi/untracked->python item-name ->python)
+                         item-val (py-ffi/untracked->python item-value ->python)]
+      (py-ffi/PyObject_SetItem item item-name item-val)
+      (py-ffi/check-error-throw))
+    nil))
+
+
+(def bool-fn-table
+  (->> {"Py_LT" 0
+        "Py_LE" 1
+        "Py_EQ" 2
+        "Py_NE" 3
+        "Py_GT" 4
+        "Py_GE" 5}
+       (map (fn [[k v]]
+              [(csk/->kebab-case-keyword k) v]))
+       (into {})))
+
+
+(defn hash-code
+  ^long [py-inst]
+  (py-ffi/with-gil
+    (long (py-ffi/PyObject_Hash py-inst))))
+
+
+(defn equals?
+  "Returns true of the python equals operator returns 1."
+  [lhs rhs]
+  (py-ffi/with-gil
+    (= 1 (py-ffi/PyObject_RichCompareBool (->python lhs)
+                                          (->python rhs)
+                                          (bool-fn-table :py-eq)))))
diff --git a/src/libpython_clj2/python/bridge_as_jvm.clj b/src/libpython_clj2/python/bridge_as_jvm.clj
new file mode 100644
index 0000000..0cb2a84
--- /dev/null
+++ b/src/libpython_clj2/python/bridge_as_jvm.clj
@@ -0,0 +1,435 @@
+(ns libpython-clj2.python.bridge-as-jvm
+  "Functionality related to proxying python objects such that they interact natively
+  with the JVM --  Python dicts become java.util.Maps, for instance."
+  (:require [libpython-clj2.python.protocols :as py-proto]
+            [libpython-clj2.python.base :as py-base]
+            [libpython-clj2.python.fn :as py-fn]
+            [libpython-clj2.python.ffi :refer [with-gil] :as py-ffi]
+            [libpython-clj2.python.gc :as pygc]
+            [tech.v3.datatype.errors :as errors]
+            [tech.v3.datatype.ffi :as dt-ffi]
+            [clojure.core.protocols :as clj-proto])
+  (:import [java.util Map]
+           [clojure.lang IFn MapEntry Fn]
+           [tech.v3.datatype.ffi Pointer]
+           [tech.v3.datatype ObjectBuffer]))
+
+
+(extend-protocol py-proto/PBridgeToJVM
+  Pointer
+  (as-jvm [ptr opts]
+    (py-proto/pyobject-as-jvm ptr opts)))
+
+
+(defmethod py-proto/pyobject-as-jvm :int
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :float
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :int-8
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :uint-8
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :int-16
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :uint-16
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :int-32
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :uint-32
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :int-64
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :uint-64
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :float-64
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :float-32
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :str
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :bool
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defmethod py-proto/pyobject-as-jvm :range
+  [pyobj & [opts]]
+  (py-base/->jvm pyobj opts))
+
+
+(defn python->jvm-iterator
+  "This is a tough function to get right.  The iterator could return nil as in
+  you could have a list of python none types or something so you have to iterate
+  till you get a StopIteration error.
+
+  If item-conversion-fn is nil, returns the raw fn output.
+  Else calls item-conversion-fn."
+  [pyobj item-conversion-fn]
+  (with-gil
+    (let [py-iter (py-fn/call-attr pyobj "__iter__" nil)
+          py-next-fn (when py-iter (py-proto/get-attr py-iter "__next__"))
+          next-fn (fn [last-item]
+                    (when (= last-item ::iteration-finished)
+                      (errors/throw-iterator-past-end))
+                    (with-gil
+                      (let [retval (py-ffi/PyObject_CallObject py-next-fn nil)]
+                        (if (and (nil? retval) (py-ffi/PyErr_Occurred))
+                          (pygc/with-stack-context
+                            (let [type (dt-ffi/make-ptr :pointer 0)
+                                  value (dt-ffi/make-ptr :pointer 0)
+                                  tb (dt-ffi/make-ptr :pointer 0)
+                                  _ (py-ffi/PyErr_Fetch type value tb)]
+                              (if (= (Pointer. (type 0)) (py-ffi/py-exc-stopiter-type))
+                                (do
+                                  (py-ffi/Py_DecRef (Pointer. (type 0)))
+                                  (when-not (== 0 (long (value 0)))
+                                    (py-ffi/Py_DecRef (Pointer. (value 0))))
+                                  (when-not (== 0 (long (tb 0)))
+                                    (py-ffi/Py_DecRef (Pointer. (tb 0))))
+                                  ::iteration-finished)
+                                (do
+                                  (py-ffi/PyErr_Restore (Pointer. (type 0))
+                                                        (Pointer. (value 0))
+                                                        (Pointer. (tb 0)))
+                                  (py-ffi/check-error-throw)))))
+                          (if item-conversion-fn
+                            (item-conversion-fn (py-ffi/track-pyobject retval))
+                            retval)))))
+          cur-item-store (atom (next-fn nil))]
+      (reify
+        java.util.Iterator
+        (hasNext [obj-iter]
+          (boolean (not= ::iteration-finished @cur-item-store)))
+        (next [obj-iter]
+          (-> (swap-vals! cur-item-store next-fn)
+              first))))))
+
+
+(defmacro bridge-pyobject
+  [pyobj & body]
+  `(let [pyobj# ~pyobj]
+     (with-meta
+       (reify
+         dt-ffi/PToPointer
+         (convertible-to-pointer? [item#] true)
+         (->pointer [item#] (dt-ffi/->pointer pyobj#))
+         py-proto/PPythonType
+         (get-python-type [item]
+           (with-gil (py-proto/get-python-type pyobj#)))
+         py-proto/PCopyToPython
+         (py-base/->python [item# options#] pyobj#)
+         py-proto/PBridgeToPython
+         (py-base/as-python [item# options#] pyobj#)
+         py-proto/PBridgeToJVM
+         (py-base/as-jvm [item# options#] item#)
+         py-proto/PCopyToJVM
+         (->jvm [item# options#]
+           (with-gil
+             (py-base/->jvm pyobj# options#)))
+         py-proto/PPyDir
+         (dir [item#]
+           (with-gil (py-proto/dir pyobj#)))
+         py-proto/PPyAttr
+         (has-attr? [item# item-name#]
+           (with-gil
+             (py-proto/has-attr? pyobj# item-name#)))
+         (get-attr [item# item-name#]
+           (with-gil
+             (-> (py-proto/get-attr pyobj# item-name#)
+                 py-base/as-jvm)))
+         (set-attr! [item# item-name# item-value#]
+           (with-gil
+             (py-ffi/with-decref [item-value# (py-ffi/untracked->python
+                                               item-value# py-base/as-python)]
+               (py-proto/set-attr! pyobj# item-name# item-value#))))
+         py-proto/PPyItem
+         (has-item? [item# item-name#]
+           (with-gil
+             (py-proto/has-item? pyobj# item-name#)))
+         (get-item [item# item-name#]
+           (with-gil
+             (-> (py-proto/get-item pyobj# item-name#)
+                 py-base/as-jvm)))
+         (set-item! [item# item-name# item-value#]
+           (with-gil
+             (py-ffi/with-decref [item-value# (py-ffi/untracked->python
+                                               item-value# py-base/as-python)]
+               (py-proto/set-item! pyobj# item-name# item-value#))))
+         py-proto/PyCall
+         (call [callable# arglist# kw-arg-map#]
+           (-> (py-fn/call-py-fn pyobj# arglist# kw-arg-map# py-base/as-python)
+               (py-base/as-jvm)))
+         (marshal-return [callable# retval#]
+           (py-base/as-jvm retval#))
+         clj-proto/Datafiable
+         (datafy [callable#] (py-proto/pydatafy callable#))
+         Object
+         (toString [this#]
+           (with-gil
+             (pygc/with-stack-context
+               (if (= 1 (py-ffi/PyObject_IsInstance pyobj# (py-ffi/py-type-type)))
+                 (format "%s.%s"
+                         (if (py-proto/has-attr? pyobj# "__module__")
+                           (py-base/->jvm (py-proto/get-attr pyobj# "__module__"))
+                           "__no_module__")
+                         (if (py-proto/has-attr? pyobj# "__name__")
+                           (py-base/->jvm (py-proto/get-attr pyobj# "__name__"))
+                           "__unnamed__"))
+                 (py-base/->jvm (py-fn/call-attr pyobj# "__str__" nil))))))
+         (equals [this# other#]
+           (boolean
+            (when (dt-ffi/convertible-to-pointer? other#)
+              (py-base/equals? pyobj# other#))))
+         (hashCode [this#]
+           (.hashCode ^Object (py-base/hash-code this#)))
+         ~@body)
+       {:type :pyobject})))
+
+
+(defmethod print-method :pyobject
+  [pyobj w]
+  (.write ^java.io.Writer w ^String (.toString ^Object pyobj)))
+
+
+(defn call-impl-fn
+  [fn-name att-map args]
+  (if-let [py-fn* (get att-map fn-name)]
+    ;;laziness is carefully constructed here in order to allow the arguments to
+    ;;be released within the context of the function call during fn.clj call-py-fn.
+    (-> (py-fn/call-py-fn @py-fn* args nil py-base/as-python)
+        (py-base/as-jvm))
+    (throw (UnsupportedOperationException.
+            (format "Python object has no attribute: %s"
+                    fn-name)))))
+
+
+(defn make-dict-att-map
+  [pyobj attnames]
+  (let [dict-atts (set attnames)
+        gc-ctx (pygc/gc-context)]
+    (->> (py-proto/dir pyobj)
+         (filter dict-atts)
+         (map (juxt identity #(delay
+                                (pygc/with-gc-context gc-ctx
+                                  (py-proto/get-attr pyobj %)))))
+         (into {}))))
+
+
+(defn make-instance-pycall
+  [pyobj attnames]
+  (let [dict-att-map (make-dict-att-map pyobj attnames)]
+    (fn [fn-name & args]
+      (py-ffi/with-gil (call-impl-fn fn-name dict-att-map args)))))
+
+
+
+(defn generic-python-as-map
+  [pyobj]
+  (with-gil
+    (let [py-call
+          (make-instance-pycall
+           pyobj
+           #{"__len__" "__getitem__" "__setitem__" "__iter__" "__contains__"
+             "__eq__" "__hash__" "clear" "keys" "values" "__delitem__"})]
+      (bridge-pyobject
+       pyobj
+       Map
+       (clear [item] (py-call "clear"))
+       (containsKey [item k] (boolean (py-call "__contains__" k)))
+       (entrySet
+        [this]
+        (py-ffi/with-gil
+          (->> (.iterator this)
+               iterator-seq
+               set)))
+       (get [this obj-key]
+            (py-call "__getitem__" obj-key))
+       (getOrDefault [item obj-key obj-default-value]
+                     (if (.containsKey item obj-key)
+                       (.get item obj-key)
+                       obj-default-value))
+       (isEmpty [this] (= 0 (.size this)))
+       (keySet [this] (->> (py-call "keys")
+                           set))
+       (put [this k v]
+            (py-call "__setitem__" k v))
+
+       (remove [this k]
+               (py-call "__delitem__" k))
+
+       (size [this]
+             (int (py-call "__len__")))
+       (values [this]
+               (py-ffi/with-gil
+                 (-> (py-call "values")
+                     (vec))))
+       Iterable
+       (iterator
+        [this]
+        (let [mapentry-seq
+              (->> (python->jvm-iterator pyobj nil)
+                   iterator-seq
+                   (map (fn [pyobj-key]
+                          (with-gil
+                            (let [k (py-base/as-jvm pyobj-key)
+                                  v (.get this pyobj-key)
+                                  retval
+                                  (MapEntry. k v)]
+                              retval)))))]
+
+          (.iterator ^Iterable mapentry-seq)))
+       IFn
+       (invoke [this arg] (.getOrDefault this arg nil))
+       (applyTo [this arglist]
+                (let [arglist (vec arglist)]
+                  (case (count arglist)
+                    1 (.get this (first arglist)))))))))
+
+
+(defmethod py-proto/pyobject-as-jvm :dict
+  [pyobj & [opts]]
+  (generic-python-as-map pyobj))
+
+
+(defn generic-python-as-list
+  [pyobj]
+  (with-gil
+    (let [py-call (make-instance-pycall
+                  pyobj
+                  #{"__len__" "__getitem__" "__setitem__" "__iter__" "__contains__"
+                    "__eq__" "__hash__" "clear" "insert" "pop" "append"
+                    "__delitem__" "sort"})]
+      (bridge-pyobject
+       pyobj
+       ObjectBuffer
+       (lsize [reader]
+              (long (py-call "__len__")))
+       (readObject [reader idx]
+             (py-call "__getitem__" idx))
+       (sort [reader obj-com]
+             (when-not (= nil obj-com)
+               (throw (ex-info "Python lists do not support comparators" {})))
+             (py-call "sort"))
+       (writeObject [writer idx value]
+              (py-call "__setitem__" idx value))
+       (remove [writer ^int idx]
+               (py-call "__delitem__" idx))
+       (add [mutable idx value]
+            (py-call "insert" idx value))
+       (add [mutable value]
+            (.add mutable (.size mutable) value))))))
+
+
+(defmethod py-proto/pyobject-as-jvm :list
+  [pyobj & [opts]]
+  (generic-python-as-list pyobj))
+
+
+(defmethod py-proto/pyobject-as-jvm :tuple
+  [pyobj & [opts]]
+  (generic-python-as-list pyobj))
+
+
+;;utility fn to generate IFn arities
+(defn- emit-args
+  [bodyf varf]
+   (let [argify (fn [n argfn bodyf]
+                  (let [raw  `[~'this ~@(map #(symbol (str "arg" %))
+                                             (range n))]]
+                    `~(bodyf (argfn raw))))]
+     (concat (for [i (range 21)]
+               (argify i identity bodyf))
+             [(argify 21 (fn [xs]
+                          `[~@(butlast xs) ~'arg20-obj-array])
+                     varf)])))
+
+;;Python specific interop wrapper for IFn invocations.
+(defn- emit-py-args []
+  (emit-args (fn [args] `(~'invoke [~@args]
+                          (py-fn/cfn ~@args)))
+             (fn [args]
+               `(~'invoke [~@args]
+                 (apply py-fn/cfn ~@(butlast args) ~(last args))))))
+
+
+(defmacro bridge-callable-pyobject
+  "Like bridge-pyobject, except it populates the implementation of IFn
+   for us, where all arg permutations are supplied, as well as applyTo,
+   and the function invocation is of the form
+   (invoke [this arg] (with-gil (cfn this arg))).
+   If caller supplies an implementation for clojure.lang.IFn or aliased
+   Fn, the macro will use that instead (allowing more control but
+   requiring caller to specify implementations for all desired arities)."
+  [pyobj interpreter & body]
+  (let [fn-specs (when-not (some #{'IFn 'clojure.lang.IFn} body)
+                   `(~'IFn
+                     ~@(emit-py-args)
+                     (~'applyTo [~'this ~'arglist]
+                      (~'apply py-fn/cfn ~'this ~'arglist))))]
+    `(bridge-pyobject ~pyobj ~interpreter
+                      ~@fn-specs
+                      ~@body)))
+
+
+(defn generic-python-as-jvm
+  "Given a generic pyobject, wrap it in a read-only map interface
+  where the keys are the attributes."
+  [pyobj]
+  (with-gil
+    (if (= :none-type (py-ffi/pyobject-type-kwd pyobj))
+      nil
+      (if (py-proto/callable? pyobj)
+        (bridge-callable-pyobject
+         pyobj
+         Iterable
+         (iterator [this] (python->jvm-iterator pyobj py-base/as-jvm))
+         Fn)
+        (bridge-pyobject
+         pyobj
+         Iterable
+         (iterator [this] (python->jvm-iterator pyobj  py-base/as-jvm)))))))
+
+
+
+(defmethod py-proto/pyobject-as-jvm :default
+  [pyobj & [opts]]
+  (generic-python-as-jvm pyobj))
diff --git a/src/libpython_clj2/python/bridge_as_python.clj b/src/libpython_clj2/python/bridge_as_python.clj
new file mode 100644
index 0000000..676e792
--- /dev/null
+++ b/src/libpython_clj2/python/bridge_as_python.clj
@@ -0,0 +1,252 @@
+(ns libpython-clj2.python.bridge-as-python
+  "Functionality related to proxying JVM objects into Python so for instance a
+  java.util.Map implementation will appear to python as being dict-like."
+  (:require [libpython-clj2.python.ffi :refer [with-gil] :as py-ffi]
+            [libpython-clj2.python.base :as py-base]
+            [libpython-clj2.python.class :as py-class]
+            [libpython-clj2.python.protocols :as py-proto]
+            [libpython-clj2.python.gc :as pygc]
+            [libpython-clj2.python.jvm-handle :as jvm-handle]
+            [tech.v3.datatype.errors :as errors]
+            [tech.v3.datatype :as dtype])
+  (:import [java.util Map RandomAccess UUID List Iterator]
+           [java.util.concurrent ConcurrentHashMap]
+           [tech.v3.datatype.ffi Pointer]
+           [tech.v3.datatype ObjectBuffer]
+           [clojure.lang Keyword Symbol IFn]))
+
+
+(defn as-python-incref
+  "Convert to python and add a reference.  Necessary for return values from
+  functions as python expects a new reference and the as-python pathway
+  ensures the jvm garbage collector also sees the reference."
+  [item]
+  (when-let [retval (py-base/as-python item)]
+    (do
+      (py-ffi/Py_IncRef retval)
+      retval)))
+
+
+(defn- as-tuple-instance-fn
+  [fn-obj & [options]]
+  (py-class/make-tuple-instance-fn fn-obj
+                                   (merge {:result-converter py-base/as-python
+                                           :arg-converter identity}
+                                          options)))
+
+(defn self->list
+  ^List [self]
+  (jvm-handle/py-self->jvm-obj self))
+
+
+(defonce sequence-type*
+  (jvm-handle/py-global-delay
+   (py-ffi/with-gil
+     (py-ffi/with-decref
+       [mod (py-ffi/PyImport_ImportModule "collections.abc")
+        seq-type (py-ffi/PyObject_GetAttrString mod "MutableSequence")]
+       (py-class/create-class
+        "jvm-list-as-python"
+        [seq-type]
+        {"__init__" (py-class/wrapped-jvm-constructor)
+         "__del__" (py-class/wrapped-jvm-destructor)
+         "__contains__" (as-tuple-instance-fn #(.contains (self->list %1) %2))
+         "__eq__" (as-tuple-instance-fn #(.equals (self->list %1)
+                                                  (py-base/->jvm %2)))
+         "__getitem__" (as-tuple-instance-fn #(.get (self->list %1)
+                                                    (int (py-base/->jvm %2))))
+         "__setitem__" (as-tuple-instance-fn #(.set (self->list %1)
+                                                    (int (py-base/->jvm %2))
+                                                    (py-base/as-jvm %3)))
+         "__delitem__" (as-tuple-instance-fn #(.remove (self->list %1)
+                                                       (int (py-base/->jvm %2))))
+         "__hash__" (as-tuple-instance-fn #(.hashCode (self->list %1)))
+         "__iter__" (as-tuple-instance-fn #(.iterator (self->list %1)))
+         "__len__" (as-tuple-instance-fn #(.size (self->list %1)))
+         "__str__" (as-tuple-instance-fn #(.toString (self->list %1)))
+         "clear" (as-tuple-instance-fn #(.clear (self->list %1)))
+         "sort" (as-tuple-instance-fn #(.sort (self->list %1) nil))
+         "append" (as-tuple-instance-fn #(.add (self->list %1) %2))
+         "insert" (as-tuple-instance-fn #(.add (self->list %1)
+                                               (int (py-base/->jvm %2)) %3))
+         "pop" (as-tuple-instance-fn
+                (fn [self & args]
+                  (let [jvm-data (self->list self)
+                        args (map py-base/->jvm args)
+                        index (int (if (first args)
+                                     (first args)
+                                     -1))
+                        index (if (< index 0)
+                                (- (.size jvm-data) index)
+                                index)]
+                    #(.remove jvm-data index))))})))))
+
+
+(defn list-as-python
+  [item]
+  (let [list-data (if (instance? List item)
+                    item
+                    (dtype/->buffer item))
+        hdl (jvm-handle/make-jvm-object-handle list-data)]
+    (@sequence-type* hdl)))
+
+
+(defmethod py-proto/pyobject->jvm :jvm-list-as-python
+  [pyobj opt]
+  (jvm-handle/py-self->jvm-obj pyobj))
+
+
+
+(defn self->map
+  ^Map [self]
+  (jvm-handle/py-self->jvm-obj self))
+
+
+(defonce mapping-type*
+  (jvm-handle/py-global-delay
+    (with-gil
+      (py-ffi/with-decref
+        [mod (py-ffi/PyImport_ImportModule "collections.abc")
+         map-type (py-ffi/PyObject_GetAttrString mod "MutableMapping")]
+        ;;In order to make things work ingeneral
+        (py-class/create-class
+         "jvm-map-as-python"
+         [map-type]
+         {"__init__" (py-class/wrapped-jvm-constructor)
+          "__del__" (py-class/wrapped-jvm-destructor)
+          "__contains__" (as-tuple-instance-fn #(.containsKey (self->map %1)
+                                                              (py-base/as-jvm %2)))
+          "__eq__" (as-tuple-instance-fn #(.equals (self->map %1) (py-base/as-jvm %2)))
+          "__getitem__" (as-tuple-instance-fn
+                         #(.get (self->map %1) (py-base/as-jvm %2)))
+          "__setitem__" (as-tuple-instance-fn #(.put (self->map %1) (py-base/as-jvm %2) %3))
+          "__delitem__" (as-tuple-instance-fn #(.remove (self->map %1) (py-base/as-jvm %2)))
+          "__hash__" (as-tuple-instance-fn #(.hashCode (self->map %1)))
+          "__iter__" (as-tuple-instance-fn #(.iterator ^Iterable (keys (self->map %1))))
+          "__len__" (as-tuple-instance-fn #(.size (self->map %1)))
+          "__str__" (as-tuple-instance-fn #(.toString (self->map %1)))
+          "clear" (as-tuple-instance-fn #(.clear (self->map %1)))
+          "keys" (as-tuple-instance-fn #(seq (.keySet (self->map %1))))
+          "values" (as-tuple-instance-fn #(seq (.values (self->map %1))))
+          "pop" (as-tuple-instance-fn #(.remove (self->map %1) (py-base/as-jvm %2)))})))))
+
+
+(defn map-as-python
+  [^Map jvm-data]
+  (errors/when-not-errorf
+   (instance? Map jvm-data)
+   "arg (%s) is not an instance of Map" (type jvm-data))
+  (@mapping-type* (jvm-handle/make-jvm-object-handle jvm-data)))
+
+
+(defmethod py-proto/pyobject->jvm :jvm-map-as-python
+  [pyobj opt]
+  (jvm-handle/py-self->jvm-obj pyobj))
+
+
+(def iterable-type*
+  (jvm-handle/py-global-delay
+    (py-ffi/with-gil
+      (py-ffi/with-decref
+        [mod (py-ffi/PyImport_ImportModule "collections.abc")
+         iter-base-cls (py-ffi/PyObject_GetAttrString mod  "Iterable")]
+        (py-class/create-class
+         "jvm-iterable-as-python"
+         [iter-base-cls]
+         {"__init__" (py-class/wrapped-jvm-constructor)
+          "__del__" (py-class/wrapped-jvm-destructor)
+          "__iter__" (as-tuple-instance-fn
+                      #(.iterator ^Iterable (jvm-handle/py-self->jvm-obj %))
+                      {:name "__iter__"})
+          "__eq__" (as-tuple-instance-fn #(.equals (jvm-handle/py-self->jvm-obj %1)
+                                                   (py-base/as-jvm %2))
+                                         {:name "__eq__"})
+          "__hash__" (as-tuple-instance-fn
+                      #(.hashCode (jvm-handle/py-self->jvm-obj %))
+                      {:name "__hash__"})
+          "__str__" (as-tuple-instance-fn
+                     #(.toString (jvm-handle/py-self->jvm-obj %))
+                     {:name "__str__"})})))))
+
+
+(defn iterable-as-python
+  [^Iterable jvm-data]
+  (errors/when-not-errorf
+   (instance? Iterable jvm-data)
+   "Argument (%s) is not an instance of Iterable" (type jvm-data))
+  (@iterable-type* (jvm-handle/make-jvm-object-handle jvm-data)))
+
+
+(defmethod py-proto/pyobject->jvm :jvm-iterable-as-python
+  [pyobj opt]
+  (jvm-handle/py-self->jvm-obj pyobj))
+
+
+(def iterator-type*
+  (jvm-handle/py-global-delay
+    (py-ffi/with-gil
+      (let [mod (py-ffi/PyImport_ImportModule "collections.abc")
+            iter-base-cls (py-ffi/PyObject_GetAttrString mod  "Iterator")
+            next-fn (fn [self]
+                      (let [^Iterator item (jvm-handle/py-self->jvm-obj self)]
+                        (if (.hasNext item)
+                          (pygc/with-stack-context
+                            (py-ffi/untracked->python (.next item) py-base/as-python))
+                          (do
+                            (py-ffi/PyErr_SetNone (py-ffi/py-exc-stopiter-type))
+                            nil))))]
+        (py-class/create-class
+         "jvm-iterator-as-python"
+         [iter-base-cls]
+         {"__init__" (py-class/wrapped-jvm-constructor)
+          "__del__" (py-class/wrapped-jvm-destructor)
+          "__next__" (py-class/make-tuple-instance-fn
+                      next-fn
+                      ;;In this case we are explicitly taking care of all conversions
+                      ;;to python and back so we do not ask for any converters.
+                      {:arg-converter nil
+                       :result-converter nil})})))))
+
+
+(defn iterator-as-python
+  [^Iterator jvm-data]
+  (errors/when-not-errorf
+   (instance? Iterator jvm-data)
+   "Argument (%s) is not a java Iterator" (type jvm-data))
+  (@iterator-type* (jvm-handle/make-jvm-object-handle jvm-data)))
+
+
+(defmethod py-proto/pyobject->jvm :jvm-iterator-as-python
+  [pyobj opt]
+  (jvm-handle/py-self->jvm-obj pyobj))
+
+
+(extend-protocol py-proto/PBridgeToPython
+  ;;already bridged!
+  Pointer
+  (as-python [item opts] item)
+  Boolean
+  (as-python [item opts] (py-proto/->python item opts))
+  Number
+  (as-python [item opts] (py-proto/->python item opts))
+  String
+  (as-python [item opts] (py-proto/->python item opts))
+  Keyword
+  (as-python [item opts] (py-proto/->python item opts))
+  Symbol
+  (as-python [item opts] (py-proto/->python item opts))
+  Object
+  (as-python [item opts]
+    (cond
+      (instance? Map item)
+      (map-as-python item)
+      (dtype/reader? item)
+      (list-as-python item)
+      (instance? IFn item)
+      (py-class/wrap-ifn item)
+      (instance? Iterable item)
+      (iterable-as-python item)
+      (instance? Iterator item)
+      (iterator-as-python item)
+      :else
+      (errors/throwf "Enable to bridge type %s" (type item)))))
diff --git a/src/libpython_clj2/python/class.clj b/src/libpython_clj2/python/class.clj
new file mode 100644
index 0000000..4e53310
--- /dev/null
+++ b/src/libpython_clj2/python/class.clj
@@ -0,0 +1,155 @@
+(ns libpython-clj2.python.class
+  "Namespace to help create a new python class from Clojure.  Used as a core
+  implementation technique for bridging JVM objects into python."
+  (:require [libpython-clj2.python.ffi :as py-ffi]
+            [libpython-clj2.python.base :as py-base]
+            [libpython-clj2.python.jvm-handle :as jvm-handle]
+            [libpython-clj2.python.fn :as py-fn]
+            [libpython-clj2.python.protocols :as py-proto]
+            [tech.v3.datatype.errors :as errors])
+  (:import [clojure.lang IFn]))
+
+
+(defn- py-fn->instance-fn
+  "Given a python callable, return an instance function meant to be used
+  in class definitions."
+  [py-fn]
+  (py-ffi/check-gil)
+  (let [retval (-> (py-ffi/PyInstanceMethod_New py-fn)
+                   (py-ffi/track-pyobject))]
+    retval))
+
+
+(defn make-tuple-instance-fn
+  "Make an instance function - a function which will be passed the 'this' object as
+  it's first argument.  In this case the default behavior is to
+  pass raw python object ptr args to the clojure function without marshalling
+  as that can add confusion and unnecessary overhead.  Self will be the first argument.
+  Callers can change this behavior by setting the 'arg-converter' option as in
+  'make-tuple-fn'.
+  Options are the same as make-tuple-fn."
+  ([clj-fn & [{:keys [arg-converter]
+               :or {arg-converter py-base/as-jvm}
+               :as options}]]
+   (py-ffi/with-gil
+     ;;Explicity set arg-converter to override make-tuple-fn's default
+     ;;->jvm arg-converter.
+     (-> (py-fn/make-tuple-fn clj-fn (assoc options :arg-converter arg-converter))
+         ;;Mark this as an instance function.
+         (py-fn->instance-fn)))))
+
+
+(defn create-class
+  "Create a new class object.  Any callable values in the cls-hashmap
+  will be presented as instance methods.
+  Things in the cls hashmap had better be either atoms or already converted
+  python objects.  You may get surprised otherwise; you have been warned.
+  See the classes-test file in test/libpython-clj"
+  [name bases cls-hashmap]
+  (py-ffi/with-gil
+    (py-ffi/with-decref
+      [cls-dict (py-ffi/untracked-dict cls-hashmap py-base/->python)
+       bases (py-ffi/untracked-tuple bases py-base/->python)]
+      (-> (py-fn/call (py-ffi/py-type-type) name bases cls-dict)
+          (py-base/as-jvm)))))
+
+
+(def wrapped-jvm-destructor*
+  (jvm-handle/py-global-delay
+   (make-tuple-instance-fn
+    (fn [self]
+      (let [jvm-hdl (jvm-handle/py-self->jvm-handle self)]
+        #_(log/debugf "Deleting bridged handle %d" jvm-hdl)
+        (jvm-handle/remove-jvm-object jvm-hdl)
+        nil)))))
+
+
+(defn wrapped-jvm-destructor
+  []
+  @wrapped-jvm-destructor*)
+
+
+(def wrapped-jvm-constructor*
+  (jvm-handle/py-global-delay
+   (make-tuple-instance-fn jvm-handle/py-self-set-jvm-handle!)))
+
+
+(defn wrapped-jvm-constructor
+  []
+  @wrapped-jvm-constructor*)
+
+
+(def abc-callable-type*
+  (jvm-handle/py-global-delay
+   (py-ffi/with-decref [mod (py-ffi/PyImport_ImportModule "collections.abc")]
+     (py-proto/get-attr mod "Callable"))))
+
+
+(def wrapped-fn-class*
+  (jvm-handle/py-global-delay
+     (create-class
+      "LibPythonCLJWrappedFn" [@abc-callable-type*]
+      {"__init__" (wrapped-jvm-constructor)
+       "__del__" (wrapped-jvm-destructor)
+       "__call__" (make-tuple-instance-fn
+                   (fn [self & args]
+                     (let [jvm-obj (jvm-handle/py-self->jvm-obj self)]
+                       (-> (apply jvm-obj (map py-base/as-jvm args))
+                           (py-ffi/untracked->python py-base/as-python)))))
+       "__str__" (make-tuple-instance-fn
+                  (fn [self]
+                    (format
+                     "libpython-clj-wrapper[%s]"
+                     (.toString (jvm-handle/py-self->jvm-obj self)))))})))
+
+
+(defn wrap-ifn
+  [ifn]
+  (errors/when-not-errorf
+   (instance? IFn ifn)
+   "Object %s is not an instance of clojure.lang.IFn" ifn)
+  (@wrapped-fn-class* (jvm-handle/make-jvm-object-handle ifn)))
+
+
+
+(comment
+  (def cls-obj*
+)
+  (@cls-obj* (jvm-handle/make-jvm-object-handle
+              #(println "in python:" %)))
+
+
+  (def cls-obj (create-class
+                "Stock" nil
+                {"__init__" (make-tuple-instance-fn
+                             (fn init [self name shares price]
+                               ;;Because we did not use an arg-converter, all the
+                               ;;arguments above are raw jna Pointers - borrowed
+                               ;;references.
+                               (py-proto/set-attr! self "name" name)
+                               (py-proto/set-attr! self "shares" shares)
+                               (py-proto/set-attr! self "price" price)
+                                ;;If you don't return nil from __init__ that is an
+                                ;;error.
+                               nil))
+                 "__del__" (wrapped-jvm-destructor)
+                 "cost" (make-tuple-instance-fn
+                         (fn cost [self]
+                           (* (py-proto/get-attr self "shares")
+                              (py-proto/get-attr self "price")))
+                          ;;Convert self to something that auto-marshals things.
+                          ;;This pathway will autoconvert all arguments to the function.
+                         {:arg-converter py-base/as-jvm})
+                  "__str__" (make-tuple-instance-fn
+                             (fn str [self]
+                               ;;Alternative to using arg-converter.  This way you can
+                               ;;explicitly control which arguments are converted.
+                               (let [self (py-base/as-jvm self)]
+                                 (pr-str {"name" (py-proto/get-attr self "name")
+                                          "shares" (py-proto/get-attr self "shares")
+                                          "price" (py-proto/get-attr self "price")}))))
+                 "clsattr" 55}))
+
+  (def inst (cls-obj "ACME" 50 90))
+  (py-fn/call-attr inst "cost")
+  )
diff --git a/src/libpython_clj2/python/copy.clj b/src/libpython_clj2/python/copy.clj
new file mode 100644
index 0000000..9b1af56
--- /dev/null
+++ b/src/libpython_clj2/python/copy.clj
@@ -0,0 +1,328 @@
+(ns libpython-clj2.python.copy
+  "Bindings to copy jvm <-> python.  Most functions in this namespace expect the
+  GIL to be captured."
+  (:require [libpython-clj2.python.ffi :as py-ffi]
+            [libpython-clj2.python.protocols :as py-proto]
+            [libpython-clj2.python.base :as py-base]
+            [libpython-clj2.python.gc :as pygc]
+            [tech.v3.datatype :as dtype]
+            [tech.v3.datatype.protocols :as dt-proto]
+            [tech.v3.datatype.ffi :as dt-ffi]
+            [tech.v3.datatype.errors :as errors])
+  (:import [tech.v3.datatype.ffi Pointer]
+           [java.util Map RandomAccess Set]
+           [clojure.lang Keyword Symbol IFn]))
+
+(declare ->py-tuple)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; python -> jvm
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;;protocol defaults and wrapper functions
+(defn python->jvm-copy-hashmap
+  [pyobj & [map-items]]
+  (when-not (= 1 (py-ffi/PyMapping_Check pyobj))
+    (errors/throwf "Object does not implement the mapping protocol: %s"
+                   (py-proto/python-type pyobj)))
+  (when-let [map-items (or map-items (py-ffi/PyMapping_Items pyobj))]
+    (try
+      (->> (py-base/->jvm map-items)
+           (into {}))
+      (finally
+        (py-ffi/Py_DecRef map-items)))))
+
+
+(defn python->jvm-copy-persistent-vector
+  [pyobj]
+  (when-not (= 1 (py-ffi/PySequence_Check pyobj))
+    (errors/throwf "Object does not implement sequence protocol: %s"
+                   (py-proto/python-type pyobj)))
+  (->> (range (py-ffi/PySequence_Length pyobj))
+       (mapv (fn [idx]
+               (let [pyitem (py-ffi/PySequence_GetItem pyobj idx)]
+                 (try
+                   (py-base/->jvm pyitem)
+                   (finally
+                     (py-ffi/Py_DecRef pyitem))))))))
+
+
+(defmethod py-proto/pyobject->jvm :str
+  [pyobj & args]
+  (py-ffi/pystr->str pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :int
+  [pyobj & args]
+  (py-ffi/PyLong_AsLongLong pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :float
+  [pyobj & args]
+  (py-ffi/PyFloat_AsDouble pyobj))
+
+
+(defn pyobj-true?
+  [pyobj]
+  (= 1 (py-ffi/PyObject_IsTrue pyobj)))
+
+
+(defmethod py-proto/pyobject->jvm :bool
+  [pyobj & [options]]
+  (pyobj-true? pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :tuple
+  [pyobj & [options]]
+  (let [n-elems (py-ffi/PyTuple_Size pyobj)]
+    (mapv (fn [^long idx]
+            (py-base/->jvm (py-ffi/PyTuple_GetItem pyobj idx)))
+          (range n-elems))))
+
+
+(defmethod py-proto/pyobject->jvm :dict
+  [pyobj & [options]]
+  (let [ppos (dt-ffi/make-ptr :size-t 0)
+        pkey (dt-ffi/make-ptr :pointer 0)
+        pvalue (dt-ffi/make-ptr :pointer 0)
+        retval (java.util.ArrayList.)]
+    ;;Dictionary iteration doesn't appear to be reentrant so we have
+    ;;to do 2 passes.
+    (loop [next-retval (py-ffi/PyDict_Next pyobj ppos pkey pvalue)]
+      (if (not= 0 next-retval)
+        (do
+          (.add retval [(Pointer. (long (pkey 0)))
+                        (Pointer. (long (pvalue 0)))])
+          (recur (py-ffi/PyDict_Next pyobj ppos pkey pvalue)))
+        (->> retval
+             (map (fn [[k v]]
+                    [(py-base/->jvm k) (py-base/->jvm v)]))
+             (into {}))))))
+
+
+(defn numpy-scalar->jvm
+  [pyobj]
+  (pygc/with-stack-context
+    (-> (py-proto/get-attr pyobj "data")
+        (py-proto/get-item (->py-tuple []))
+        py-base/->jvm)))
+
+
+(defmethod py-proto/pyobject->jvm :uint-8
+  [pyobj & [opts]]
+  (numpy-scalar->jvm pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :int-8
+  [pyobj & [opts]]
+  (numpy-scalar->jvm pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :uint-16
+  [pyobj & [opts]]
+  (numpy-scalar->jvm pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :int-16
+  [pyobj & [opts]]
+  (numpy-scalar->jvm pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :uint-32
+  [pyobj & [opts]]
+  (numpy-scalar->jvm pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :int-32
+  [pyobj & [opts]]
+  (numpy-scalar->jvm pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :uint-64
+  [pyobj & [opts]]
+  (numpy-scalar->jvm pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :int-64
+  [pyobj & [opts]]
+  (numpy-scalar->jvm pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :float-64
+  [pyobj & [opts]]
+  (numpy-scalar->jvm pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :float-32
+  [pyobj & [opts]]
+  (numpy-scalar->jvm pyobj))
+
+
+(defmethod py-proto/pyobject->jvm :range
+  [pyobj & [opts]]
+  (pygc/with-stack-context
+    (let [start (py-base/->jvm (py-proto/get-attr pyobj "start"))
+          step (py-base/->jvm (py-proto/get-attr pyobj "step"))
+          stop (py-base/->jvm (py-proto/get-attr pyobj "stop"))]
+      (range start stop step))))
+
+
+(defmethod py-proto/pyobject->jvm :default
+  [pyobj & [options]]
+  (cond
+    (= :none-type (py-ffi/pyobject-type-kwd pyobj))
+    nil
+    ;;Things could implement mapping and sequence logically so mapping
+    ;;takes precedence
+    (= 1 (py-ffi/PyMapping_Check pyobj))
+    (if-let [map-items (py-ffi/PyMapping_Items pyobj)]
+      (try
+        (python->jvm-copy-hashmap pyobj map-items)
+        (finally
+          (py-ffi/Py_DecRef map-items)))
+      (do
+        ;;Ignore error.  The mapping check isn't thorough enough to work for all
+        ;;python objects.
+        (py-ffi/PyErr_Clear)
+        (python->jvm-copy-persistent-vector pyobj)))
+    ;;Sequences become persistent vectors
+    (= 1 (py-ffi/PySequence_Check pyobj))
+    (python->jvm-copy-persistent-vector pyobj)
+    :else
+    {:type (py-ffi/pyobject-type-kwd pyobj)
+     ;;Create a new GC root as the old reference is released.
+     :value (let [new-obj (py-ffi/track-pyobject
+                           (Pointer. (.address (dt-ffi/->pointer pyobj))))]
+              (py-ffi/Py_IncRef new-obj)
+              new-obj)}))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; jvm -> python
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+(def ^:dynamic *item-tuple-cutoff* 8)
+
+
+(defn ->py-tuple
+  [args]
+  (-> (py-ffi/untracked-tuple args py-base/->python)
+      (py-ffi/track-pyobject)))
+
+
+(defn ->py-dict
+  "Copy an object into a new python dictionary."
+  [item]
+  (py-ffi/check-gil)
+  (-> (py-ffi/untracked-dict item py-base/->python)
+      (py-ffi/track-pyobject)))
+
+
+(defn ->py-string
+  "Copy an object into a python string"
+  [item]
+  (-> (py-ffi/PyUnicode_FromString (str item))
+      (py-ffi/track-pyobject)))
+
+
+(defn ->py-list
+  "Copy an object into a new python list."
+  [item-seq]
+  (py-ffi/check-gil)
+  (let [item-seq (vec item-seq)
+        retval (py-ffi/PyList_New (count item-seq))]
+    (pygc/with-stack-context
+      (dotimes [idx (count item-seq)]
+        (let [si-retval
+              ;;setitem does steal the reference
+              (py-ffi/PyList_SetItem
+               retval
+               idx
+               (py-ffi/untracked->python (item-seq idx) py-base/->python))]
+          (when-not (== 0 (long si-retval))
+            (py-ffi/check-error-throw)))))
+    (py-ffi/track-pyobject retval)))
+
+
+(defn ->py-set
+  [item]
+  (py-ffi/check-gil)
+  (-> (py-ffi/PySet_New (->py-list item))
+      (py-ffi/track-pyobject)))
+
+
+(defn ->py-long
+  [item]
+  (py-ffi/track-pyobject (py-ffi/PyLong_FromLongLong (long item))))
+
+
+(defn ->py-double
+  [item]
+  (py-ffi/track-pyobject (py-ffi/PyFloat_FromDouble (double item))))
+
+
+(defn ->py-range
+  [item]
+  (let [dt-range (dt-proto/->range item {})
+        start (long (dt-proto/range-start dt-range))
+        inc (long (dt-proto/range-increment dt-range))
+        n-elems (long (dtype/ecount dt-range))
+        stop (+ start (* inc n-elems))
+        ;;the tuple steals the references
+        argtuple (py-ffi/untracked-tuple [start stop inc])
+        retval (py-ffi/PyObject_CallObject (py-ffi/py-range-type) argtuple)]
+    ;;we drop the tuple
+    (py-ffi/Py_DecRef argtuple)
+    ;;and wrap the retval
+    (py-ffi/track-pyobject retval)))
+
+
+(extend-protocol py-proto/PCopyToPython
+  Boolean
+  (->python [item opts]
+    (if item (py-ffi/py-true) (py-ffi/py-false)))
+  Long
+  (->python [item opts] (->py-long item))
+  Double
+  (->python [item opts] (->py-double item))
+  Number
+  (->python [item opts]
+    (if (integer? item)
+      (->py-long item)
+      (->py-double item)))
+  String
+  (->python [item ops] (->py-string item))
+  Keyword
+  (->python [item ops] (->py-string (name item)))
+  Symbol
+  (->python [item ops] (->py-string (name item)))
+  Character
+  (->python [item ops] (->py-string (str item)))
+  Map
+  (->python [item opts] (->py-dict item))
+  RandomAccess
+  (->python [item opts]
+    (if (< (count item) (long *item-tuple-cutoff*))
+      (->py-tuple item)
+      (->py-list item)))
+  Set
+  (->python [item opts] (->py-set item))
+  Pointer
+  (->python [item opts] item)
+  Object
+  (->python [item opts]
+    (cond
+      (dt-proto/convertible-to-range? item)
+      (->py-range item)
+      (dtype/reader? item)
+      (py-proto/->python (dtype/->reader item) opts)
+      ;;There is one more case here for iterables (sequences)
+      (instance? Iterable item)
+      ;;Iterables we *have* to convert lazily; we cannot copy them.
+      (py-proto/as-python item opts)
+      (instance? IFn item)
+      (py-proto/as-python item opts)
+      :else
+      (errors/throwf "Unable to convert object: %s" item))))
diff --git a/src/libpython_clj2/python/dechunk_map.clj b/src/libpython_clj2/python/dechunk_map.clj
new file mode 100644
index 0000000..7146d72
--- /dev/null
+++ b/src/libpython_clj2/python/dechunk_map.clj
@@ -0,0 +1,11 @@
+(ns libpython-clj2.python.dechunk-map
+  "Utility namespace with a function that works like a single-sequence map but
+  stops chunking.")
+
+
+(defn dechunk-map
+  "Map a function across a sequence without chunking."
+  [f s]
+  (lazy-seq
+   (when-let [[x] (seq s)]
+     (cons (f x) (dechunk-map f (rest s))))))
diff --git a/src/libpython_clj2/python/ffi.clj b/src/libpython_clj2/python/ffi.clj
new file mode 100644
index 0000000..80ab939
--- /dev/null
+++ b/src/libpython_clj2/python/ffi.clj
@@ -0,0 +1,883 @@
+(ns libpython-clj2.python.ffi
+  "Low level bindings to the python shared library system.  Several key pieces of
+  functionality are implemented:
+
+  * Declare/implement actual C binding layer
+  * Low level library initialization
+  * GIL management
+  * Error checking
+  * High perf tuple, dict creation
+  * Addref/decref reference management including tracking objects - binding them
+    to the JVM GC
+  * python type->clojure keyword table"
+  (:require [tech.v3.datatype :as dtype]
+            [tech.v3.datatype.ffi :as dt-ffi]
+            [tech.v3.datatype.ffi.size-t :as ffi-size-t]
+            [tech.v3.datatype.struct :as dt-struct]
+            [tech.v3.datatype.errors :as errors]
+            [tech.v3.datatype.native-buffer :as native-buffer]
+            [tech.v3.datatype.nio-buffer :as nio-buffer]
+            [tech.v3.datatype.protocols :as dt-proto]
+            [tech.v3.resource :as resource]
+            [libpython-clj2.python.gc :as pygc]
+            [camel-snake-kebab.core :as csk]
+            [clojure.tools.logging :as log]
+            [clojure.string :as s])
+  (:import [java.util.concurrent ConcurrentHashMap]
+           [java.util.function Function]
+           [tech.v3.datatype.ffi Pointer Library]
+           [clojure.lang Keyword Symbol]
+           [java.nio.charset StandardCharsets]))
+
+
+(set! *warn-on-reflection* true)
+
+
+(declare track-pyobject py-none pystr->str check-error-throw pyobject-type-kwd
+         PyGILState_Check)
+
+
+(def python-library-fns
+  {:Py_InitializeEx {:rettype :void
+                     :argtypes [['signals :int32]]
+                     :requires-gil? false
+                     :doc "Initialize the python shared library"}
+   :Py_IsInitialized {:rettype :int32
+                      :requires-gil? false
+                      :doc "Return 1 if library is initalized, 0 otherwise"}
+
+   :PyRun_SimpleString {:rettype :int32
+                        :argtypes [['argstr :string]]
+                        :doc "Low-level run a simple python string."}
+   :PyRun_String {:rettype :pointer
+                  :argtypes [['program :string]
+                             ['start-sym :int32]
+                             ['globals :pointer]
+                             ['locals :pointer]]
+                  :doc "Run a string setting the start type, globals and locals"}
+   :PySys_SetArgvEx {:rettype :void
+                     :argtypes [['argc :int32]
+                                ['argv-wide-ptr-ptr :pointer]
+                                ['update :int32]]
+                   :doc "Set the argv/argc for the interpreter.
+Required for some python modules"}
+   :Py_SetProgramName {:rettype :void
+                       :requires-gil? false
+                       :argtypes [['program-name-wideptr :pointer]]
+                       :doc "Set the program name"}
+   :PyEval_SaveThread {:rettype :pointer
+                       :requires-gil? false
+                       :doc "Release the GIL on the current thread"}
+   :PyGILState_Ensure {:rettype :int32
+                       :requires-gil? false
+                       :doc "Ensure this thread owns the python GIL.
+Each call must be matched with PyGILState_Release"}
+   :PyGILState_Check {:rettype :int32
+                      :requires-gil? false
+                      :doc "Return 1 if gil is held, 0 otherwise"}
+   :PyGILState_Release {:rettype :void
+                        :argtypes [['modhdl :int32]]
+                        :doc "Release the GIL state."}
+   :Py_IncRef {:rettype :void
+               :argtypes [['pyobj :pointer]]
+               :doc "Increment the reference count on a pyobj"}
+   :Py_DecRef {:rettype :void
+               :argtypes [['pyobj :pointer]]
+               :doc "Decrement the reference count on a pyobj"}
+   :PyErr_Occurred {:rettype :pointer
+                    :doc "Return the current in-flight exception without clearing it."}
+   :PyErr_Fetch {:rettype :void
+                 :argtypes [['type :pointer]
+                            ['value :pointer]
+                            ['tb :pointer]]
+                 :doc "Fetch and clear the current exception information"}
+   :PyErr_Restore {:rettype :void
+                   :argtypes [['type :pointer]
+                              ['value :pointer?]
+                              ['tb :pointer?]]
+                   :doc "Restore the current error state"}
+   :PyErr_NormalizeException {:rettype :void
+                              :argtypes [['type :pointer]
+                                         ['value :pointer]
+                                         ['tb :pointer]]
+                              :doc "Normalize a python exception."}
+   :PyErr_Clear {:rettype :void
+                 :doc "Clear the current python error"}
+   :PyErr_SetString {:rettype :void
+                     :argtypes [['ex-type :pointer]
+                                ['data :string]]
+                     :doc "Raise an exception with a message"}
+   :PyErr_SetNone {:rettype :void
+                   :argtypes [['ex-type :pointer]]
+                   :doc "Raise an exception with no message"}
+   :PyException_SetTraceback {:rettype :int32
+                              :argtypes [['val :pointer]
+                                         ['tb :pointer]]
+                              :doc "Set the traceback on the exception object"}
+   :PyUnicode_AsUTF8 {:rettype :pointer
+                      :argtypes [['obj :pointer]]
+                      :doc "convert a python unicode object to a utf8 encoded string"}
+   :PyUnicode_AsUTF8AndSize {:rettype :pointer
+                             :argtypes [['obj :pointer]
+                                        ['size :pointer]]
+                             :doc "Return both the data and the size of the data"}
+   :PyImport_ImportModule {:rettype :pointer
+                           :argtypes [['modname :string]]
+                           :doc "Import a python module"}
+   :PyImport_AddModule {:rettype :pointer
+                        :argtypes [['modname :string]]
+                        :doc "Add a python module"}
+   :PyModule_GetDict {:rettype :pointer
+                      :argtypes [['module :pointer]]
+                      :doc "Get the module dictionary"}
+   :PyObject_Dir {:rettype :pointer
+                  :argtypes [['pyobj :pointer]]
+                  :doc "Get a python sequence of string attribute names"}
+   :PyObject_HasAttr {:rettype :int32
+                      :argtypes [['o :pointer]
+                                 ['attr_name :pointer]]
+                      :doc "Return 1 if object has an attribute"}
+   :PyObject_HasAttrString {:rettype :int32
+                            :argtypes [['o :pointer]
+                                       ['attr_name :string]]
+                            :doc "Return 1 if object has an attribute"}
+   :PyObject_GetAttr {:rettype :pointer
+                      :argtypes [['o :pointer]
+                                 ['attr_name :pointer]]
+                      :doc "get an attribute from an object"}
+   :PyObject_GetAttrString {:rettype :pointer
+                            :argtypes [['o :pointer]
+                                       ['attr_name :string]]
+                            :doc "get an attribute from an object"}
+   :PyObject_SetAttrString {:rettype :int32
+                            :argtypes [['o :pointer]
+                                       ['attr_name :string]
+                                       ['v :pointer]]}
+   :PyObject_SetAttr {:rettype :int32
+                      :argtypes [['o :pointer]
+                                 ['attr_name :pointer]
+                                 ['v :pointer]]
+                      :doc "Set an attribute on a python object"}
+   :PyObject_GetItem {:rettype :pointer
+                      :argtypes [['o :pointer]
+                                 ['args :pointer]]
+                      :doc "Get an item from a python object"}
+   :PyObject_SetItem {:rettype :int32
+                      :argtypes [['o :pointer]
+                                 ['args :pointer]
+                                 ['val :pointer]]
+                      :doc "Set an item on a python object"}
+   :PyObject_IsTrue {:rettype :int32
+                     :argtypes [['o :pointer]]
+                     :doc "Check if a python object is true"}
+   :PyObject_IsInstance {:rettype :int32
+                         :argtypes [['inst :pointer]
+                                    ['cls :pointer]]
+                         :doc "Check if this object is an instance of this class"}
+   :PyObject_Hash {:rettype :size-t
+                   :argtypes [['o :pointer]]
+                   :doc "Get the hash value of a python object"}
+   :PyObject_RichCompareBool {:rettype :int32
+                              :argtypes [['lhs :pointer]
+                                         ['rhs :pointer]
+                                         ['opid :int32]]
+                              :doc "Compare two python objects"}
+   :PyCallable_Check {:rettype :int32
+                      :argtypes [['o :pointer]]
+                      :doc "Return 1 if this is a callable object"}
+   :PyObject_Call {:rettype :pointer
+                   :argtypes [['callable :pointer]
+                              ['args :pointer]
+                              ['kwargs :pointer?]]
+                   :doc "Call a callable object"}
+   :PyObject_CallObject {:rettype :pointer
+                   :argtypes [['callable :pointer]
+                              ['args :pointer?]]
+                   :doc "Call a callable object with no kwargs.  args may be nil"}
+   :PyMapping_Check {:rettype :int32
+                     :argtypes [['pyobj :pointer]]
+                     :doc "Check if this object implements the mapping protocol"}
+   :PyMapping_Items {:rettype :pointer
+                     :argtypes [['pyobj :pointer]]
+                     :doc "Get an iterable of tuples of this map."}
+   :PySequence_Check {:rettype :int32
+                      :argtypes [['pyobj :pointer]]
+                      :doc "Check if this object implements the sequence protocol"}
+   :PySequence_Length {:rettype :size-t
+                       :argtypes [['pyobj :pointer]]
+                       :doc "Get the length of a sequence"}
+   :PySequence_GetItem {:rettype :pointer
+                        :argtypes [['pyobj :pointer]
+                                   ['idx :size-t]]
+                        :doc "Get a specific item from a sequence"}
+   :PyFloat_AsDouble {:rettype :float64
+                     :argtypes [['pyobj :pointer]]
+                       :doc "Get a double value from a python float"}
+   :PyFloat_FromDouble {:rettype :pointer
+                       :argtypes [['data :float64]]
+                       :doc "Get a pyobject form a long."}
+
+   :PyLong_AsLongLong {:rettype :int64
+                       :argtypes [['pyobj :pointer]]
+                       :doc "Get the long value from a python integer"}
+   :PyLong_FromLongLong {:rettype :pointer
+                         :argtypes [['data :int64]]
+                         :doc "Get a pyobject form a long."}
+   :PyDict_New {:rettype :pointer
+                :doc "Create a new dictionary"}
+   :PyDict_SetItem {:rettype :int32
+                    :argtypes [['dict :pointer]
+                               ['k :pointer]
+                               ['v :pointer]]
+                    :doc "Insert val into the dictionary p with a key of key. key must be hashable; if it isn’t, TypeError will be raised. Return 0 on success or -1 on failure. This function does not steal a reference to val."}
+   :PyDict_Next {:rettype :int32
+                 :argtypes [['pyobj :pointer]
+                            ['ppos :pointer]
+                            ['pkey :pointer]
+                            ['pvalue :pointer]]
+                 :doc "Get the next value from a dictionary"}
+   :PyTuple_New {:rettype :pointer
+                 :argtypes [['len :size-t]]
+                 :doc "Create a new uninitialized tuple"}
+   :PyTuple_SetItem {:rettype :int32
+                     :argtypes [['tuple :pointer]
+                                ['idx :size-t]
+                                ['pvalue :pointer]]
+                     :doc "Insert a reference to object o at position pos of the tuple pointed to by p. Return 0 on success. If pos is out of bounds, return -1 and set an IndexError exception."}
+   :PyTuple_Size {:rettype :size-t
+                  :argtypes [['o :pointer]]
+                  :doc "return the length of a tuple"}
+   :PyTuple_GetItem {:rettype :pointer
+                     :argtypes [['o :pointer]
+                                ['idx :size-t]]
+                     :doc "return a borrowed reference to item at idx"}
+   :PyList_New {:rettype :pointer
+                :argtypes [['len :size-t]]
+                :doc "create a new list"}
+   :PyList_SetItem {:rettype :int32
+                    :argtypes [['l :pointer]
+                               ['idx :size-t]
+                               ['v :pointer]]
+                    :doc "Set the item at index index in list to item. Return 0 on success. If index is out of bounds, return -1 and set an IndexError exception.  This function steals the reference to v"}
+   :PyList_Size {:rettype :size-t
+                  :argtypes [['o :pointer]]
+                  :doc "return the length of a list"}
+   :PyList_GetItem {:rettype :pointer
+                     :argtypes [['o :pointer]
+                                ['idx :size-t]]
+                     :doc "return a borrowed reference to item at idx"}
+   :PySet_New {:rettype :pointer
+               :argtypes [['items :pointer]]
+               :doc "Create a new set"}
+   :PyUnicode_FromString {:rettype :pointer
+                          :argtypes [['data :string]]
+                          :doc "Create a python unicode object from a string"}
+   :PyCFunction_NewEx {:rettype :pointer
+                       :argtypes [['method-def :pointer]
+                                  ['self :pointer?]
+                                  ['module :pointer?]]}
+   :PyInstanceMethod_New {:rettype :pointer
+                          :argtypes [['pyfn :pointer]]
+                          :doc "Mark a python function as being an instance method."}
+   })
+
+
+(def python-lib-def* (delay (dt-ffi/define-library python-library-fns)))
+(defonce pyobject-struct-type*
+  (delay (dt-struct/define-datatype!
+           :pyobject [{:name :ob_refcnt :datatype (ffi-size-t/size-t-type)}
+                      {:name :ob_type :datatype (ffi-size-t/size-t-type)}])))
+
+(defn pytype-offset
+  ^long []
+  (first (dt-struct/offset-of @pyobject-struct-type* :ob_type)))
+
+
+(defn pyrefcnt-offset
+  ^long []
+  (first (dt-struct/offset-of @pyobject-struct-type* :ob_refcnt)))
+
+
+(defn ptr->struct
+  [struct-type ptr-type]
+  (let [n-bytes (:datatype-size (dt-struct/get-struct-def struct-type))
+        src-ptr (dt-ffi/->pointer ptr-type)
+        nbuf (native-buffer/wrap-address (.address src-ptr)
+                                         n-bytes
+                                         src-ptr)]
+    (dt-struct/inplace-new-struct struct-type nbuf)))
+
+
+(defonce ^:private library* (atom nil))
+(defonce ^:private library-path* (atom nil))
+
+
+(defn reset-library!
+  []
+  (when @library-path*
+    (reset! library* (dt-ffi/instantiate-library @python-lib-def* @library-path*))))
+
+
+(defn set-library!
+  [libpath]
+  (when @library*
+    (log/warnf "Python library is being reinitialized to (%s).  Is this what you want?"
+               libpath))
+  (reset! library-path* libpath)
+  (reset-library!))
+
+
+
+
+;;Useful for repling around - this regenerates the library function bindings
+(reset-library!)
+
+
+(defn library-loaded? [] (not (nil? @library*)))
+
+
+(defn current-library
+  ^Library []
+  @library*)
+
+
+(defmacro check-gil
+  "Maybe the most important insurance policy"
+  []
+  `(errors/when-not-error
+    (= 1 (PyGILState_Check))
+    "GIL is not captured"))
+
+
+;;When this is true, generated functions will throw an exception if called when the
+;;GIL is not captured.  It makes sense to periodically enable this flag in order
+;;to ensure we aren't getting away with sneaky non-GIL access to Python.
+(def enable-api-gilcheck false)
+
+
+(defn- find-pylib-fn
+  [fn-kwd]
+  (let [pylib @library*]
+    (errors/when-not-error
+     pylib
+     "Library not found.  Has set-library! been called?")
+    (if-let [retval (fn-kwd @pylib)]
+      retval
+      (errors/throwf "Python function %s not found" (symbol (name fn-kwd))))))
+
+
+(defmacro def-py-fn
+  [fn-name docs & args]
+  (let [fn-kwd (keyword (name fn-name))]
+    (errors/when-not-errorf
+     (contains? python-library-fns fn-kwd)
+     "Python function %s is not defined" fn-name)
+    `(defn ~fn-name ~docs
+       ~(vec (map first args))
+       (let [retval#
+             (resource/stack-resource-context
+              ((find-pylib-fn ~fn-kwd) ~@(map (fn [[argname marshal-fn]]
+                                                `(~marshal-fn ~argname))
+                                              args)))]))))
+
+
+(defmacro define-pylib-functions
+  []
+  `(do
+     ~@(->>
+        python-library-fns
+        (map
+         (fn [[fn-name {:keys [rettype argtypes] :as fn-data}]]
+           (let [fn-symbol (symbol (name fn-name))
+                 requires-resctx? (first (filter #(= :string %)
+                                                 (map second argtypes)))
+                 gilcheck? (when enable-api-gilcheck
+                             (if (contains? fn-data :requires-gil?)
+                               (fn-data :requires-gil?)
+                               true))]
+             `(defn ~fn-symbol
+                ~(:doc fn-data "No documentation!")
+                ~(mapv first argtypes)
+                (let [~'ifn (find-pylib-fn ~fn-name)]
+                  (do
+                    ~(when gilcheck? `(check-gil))
+                    ~(if requires-resctx?
+                       `(resource/stack-resource-context
+                         (~'ifn ~@(map (fn [[argname argtype]]
+                                         (cond
+                                           (#{:int8 :int16 :int32 :int64} argtype)
+                                           `(long ~argname)
+                                           (#{:float32 :float64} argtype)
+                                           `(double ~argname)
+                                           (= :string argtype)
+                                           `(dt-ffi/string->c ~argname)
+                                           :else
+                                           argname))
+                                       argtypes)))
+                       `(~'ifn ~@(map (fn [[argname argtype]]
+                                        (cond
+                                          (#{:int8 :int16 :int32 :int64} argtype)
+                                          `(long ~argname)
+                                          (#{:float32 :float64} argtype)
+                                          `(double ~argname)
+                                          (= :string argtype)
+                                          `(dt-ffi/string->c ~argname)
+                                          :else
+                                          argname))
+                                      argtypes))))))))))))
+
+
+(define-pylib-functions)
+
+
+(defn- deref-ptr-ptr
+  ^Pointer [^Pointer val]
+  (Pointer. (case (ffi-size-t/size-t-type)
+              :int32 (.getInt (native-buffer/unsafe) (.address val))
+              :int64 (.getLong (native-buffer/unsafe) (.address val)))))
+
+
+(defmacro define-static-symbol
+  [symbol-fn symbol-name deref-ptr?]
+  (let [sym-delay-name (with-meta (symbol (str symbol-fn "*"))
+                         {:doc (format "Dereferences to the value of %s" symbol-name)
+                          :private true})]
+    `(do
+       (def ~sym-delay-name (delay (.findSymbol (current-library) ~symbol-name)))
+       ~(if deref-ptr?
+          `(defn ~symbol-fn [] (deref-ptr-ptr (deref ~sym-delay-name)))
+          `(defn ~symbol-fn [] (deref ~sym-delay-name))))))
+
+
+(define-static-symbol py-none "_Py_NoneStruct" false)
+(define-static-symbol py-true "_Py_TrueStruct" false)
+(define-static-symbol py-false "_Py_FalseStruct" false)
+(define-static-symbol py-range-type "PyRange_Type" false)
+(define-static-symbol py-exc-type "PyExc_Exception" true)
+(define-static-symbol py-exc-stopiter-type "PyExc_StopIteration" true)
+(define-static-symbol py-type-type "PyType_Type" false)
+
+
+(defmacro with-decref
+  [vardefs & body]
+  (let [n-vars (count vardefs)]
+    (if (= 2 n-vars)
+      `(let ~vardefs
+         (try
+           ~@body
+           (finally
+             (when ~(first vardefs)
+               (Py_DecRef ~(first vardefs))))))
+      `(let [~'obj-data (object-array ~n-vars)]
+         (try
+           (let [~@(mapcat (fn [[idx [varsym varform]]]
+                             [varsym `(let [vardata# ~varform]
+                                        (aset ~'obj-data ~idx vardata#)
+                                        vardata#)])
+                           (map-indexed vector (partition 2 vardefs)))]
+             ~@body)
+           (finally
+             (check-gil)
+             (dotimes [idx# ~n-vars]
+               (when-let [pyobj#  (aget ~'obj-data idx#)]
+                 (Py_DecRef pyobj#)))))))))
+
+
+(defn incref
+  "Increment the refcount returning object.  Legal to call
+  on nil, will not incref item and return nil."
+  [pyobj]
+  (when pyobj (Py_IncRef pyobj))
+  pyobj)
+
+
+(defonce ^{:tag ConcurrentHashMap
+           :private true}
+  forever-map (ConcurrentHashMap.))
+
+
+(defn retain-forever
+  [item-key item-val]
+  (.put forever-map item-key item-val)
+  item-val)
+
+
+(defonce format-exc-pyfn* (atom nil))
+
+
+(defn- init-exc-formatter
+  []
+  (check-gil)
+  (let [tback-mod (PyImport_ImportModule "traceback")
+        format-fn (PyObject_GetAttrString tback-mod "format_exception")]
+    ;;the tback module cannot be removed from memory and it always has a reference
+    ;;to format-fn so we drop our reference.
+    (Py_DecRef tback-mod)
+    (Py_DecRef format-fn)
+    (reset! format-exc-pyfn* format-fn)))
+
+
+(defn- exc-formatter
+  []
+  (when-not @format-exc-pyfn*
+    (init-exc-formatter))
+  @format-exc-pyfn*)
+
+
+(defn initialize!
+  [libpath python-home & [{:keys [signals? program-name]
+                           :or {signals? true
+                                program-name ""}}]]
+  (set-library! libpath)
+  (when-not (= 1 (Py_IsInitialized))
+    (log/debug "Initializing Python C Layer")
+    (let [program-name (retain-forever :program-name
+                                       (-> (or program-name "")
+                                           (dt-ffi/string->c {:encoding :utf-16})))]
+      (Py_SetProgramName program-name)
+      (Py_InitializeEx (if signals? 1 0))
+      (PySys_SetArgvEx 0 program-name 1)
+      ;;return value ignored :-)
+      ;;This releases the GIL until further processing and allows with-gil to work
+      ;;correctly.
+      (PyEval_SaveThread)))
+  :ok)
+
+
+(defn untracked->python
+  ^Pointer [item & [conversion-fallback]]
+  (cond
+    (instance? Pointer item)
+    (-> (if (.isNil ^Pointer item)
+          (py-none)
+          item)
+        (incref))
+    (instance? Number item) (if (integer? item)
+                              (PyLong_FromLongLong (long item))
+                              (PyFloat_FromDouble (double item)))
+    (instance? Boolean item) (-> (if item (py-true) (py-false))
+                                 (incref))
+    (instance? String item) (PyUnicode_FromString item)
+    (instance? Keyword item) (PyUnicode_FromString (name item))
+    (instance? Symbol item) (PyUnicode_FromString (name item))
+    (nil? item) (incref (py-none))
+    :else
+    (if conversion-fallback
+      (incref (conversion-fallback item))
+      (throw (Exception. "Unable to convert value %s" item)))))
+
+
+(defn untracked-tuple
+  "Low-level make tuple fn.  conv-fallback is used when an argument isn't an
+  atomically convertible python type.  Returns an untracked python tuple."
+  [args & [conv-fallback]]
+  (check-gil)
+  (let [args (vec args)
+        argcount (count args)
+        tuple (PyTuple_New argcount)]
+    (dotimes [idx argcount]
+      (PyTuple_SetItem tuple idx (untracked->python (args idx)
+                                                    conv-fallback)))
+    tuple))
+
+
+(defn untracked-dict
+  "Low-level make dict fn.  conv-fallback is used when a key or value isn't an
+  atomically convertible python type. Returns an untracked dict."
+  [item-seq & [conv-fallback]]
+  (check-gil)
+  (let [dict (PyDict_New)]
+    (pygc/with-stack-context
+      (doseq [[k v] item-seq]
+        ;;setitem does not steal the reference
+        (let [k (untracked->python k conv-fallback)
+              v (untracked->python v conv-fallback)
+              si-retval (PyDict_SetItem dict k v)]
+          (Py_DecRef k)
+          (Py_DecRef v)
+          (when-not (== (long si-retval) 0)
+            (check-error-throw)))))
+    dict))
+
+(def ^:dynamic *python-error-handler* nil)
+
+
+(defn fetch-normalize-exception
+  []
+  (check-gil)
+  (resource/stack-resource-context
+   (let [type (dt-ffi/make-ptr :pointer 0)
+         value (dt-ffi/make-ptr :pointer 0)
+         tb (dt-ffi/make-ptr :pointer 0)]
+     (PyErr_Fetch type value tb)
+     (PyErr_NormalizeException type value tb)
+     {:type (Pointer/constructNonZero (type 0))
+      :value (Pointer/constructNonZero (value 0))
+      :traceback (Pointer/constructNonZero (tb 0))})))
+
+
+(defn check-error-str
+  "Function assumes python stdout and stderr have been redirected"
+  []
+  (check-gil)
+  (when-not (= nil (PyErr_Occurred))
+    (if *python-error-handler*
+      (*python-error-handler*)
+      (let [{:keys [type value traceback]}
+            (fetch-normalize-exception)]
+        (with-decref [argtuple (untracked-tuple [type value traceback])
+                      exc-str-tuple (PyObject_CallObject (exc-formatter) argtuple)]
+          (case (pyobject-type-kwd exc-str-tuple)
+            :list (->> (range (PyList_Size exc-str-tuple))
+                       (map (fn [idx]
+                              (let [strdata (PyList_GetItem exc-str-tuple idx)]
+                                (pystr->str strdata))))
+                       (s/join))
+            :tuple (->> (range (PyTuple_Size exc-str-tuple))
+                        (map (fn [idx]
+                               (let [strdata (PyTuple_GetItem exc-str-tuple idx)]
+                                 (pystr->str strdata))))
+                       (s/join))))))))
+
+(defn check-error-throw
+  []
+  (when-let [error-str (check-error-str)]
+    (throw (Exception. ^String error-str))))
+
+
+(defmacro with-error-check
+  [& body]
+  `(let [retval# (do ~@body)]
+     (check-error-throw)
+     retval#))
+
+
+(defn check-py-method-return
+  [^long retval]
+  (when-not (= 0 retval)
+    (check-error-throw)))
+
+
+(defmacro with-gil
+  "Grab the gil and use the main interpreter using reentrant acquire-gil pathway."
+  [& body]
+  `(let [gil-state# (when-not (== 1 (long (PyGILState_Check)))
+                      (PyGILState_Ensure))]
+     (try
+       (let [retval# (do ~@body)]
+         (check-error-throw)
+         retval#)
+       (finally
+         (when gil-state#
+           #_(System/gc)
+           (pygc/clear-reference-queue)
+           (PyGILState_Release gil-state#))))))
+
+
+(defn pyobject-type
+  ^Pointer [pobj]
+  (if (= :int32 (ffi-size-t/size-t-type))
+    (Pointer. (.getInt (native-buffer/unsafe)
+                       (+ (.address (dt-ffi/->pointer pobj)) (pytype-offset))))
+    (Pointer. (.getLong (native-buffer/unsafe)
+                        (+ (.address (dt-ffi/->pointer pobj)) (pytype-offset))))))
+
+
+(defn pyobject-refcount
+  ^long [pobj]
+  (if (= :int32 (ffi-size-t/size-t-type))
+    (.getInt (native-buffer/unsafe)
+             (+ (.address (dt-ffi/->pointer pobj)) (pyrefcnt-offset)))
+    (.getLong (native-buffer/unsafe)
+              (+ (.address (dt-ffi/->pointer pobj)) (pyrefcnt-offset)))))
+
+
+(defn pystr->str
+  ^String [pyobj]
+  ;;manually allocate/deallocate for speed; this gets called a lot
+  (let [size-obj (dt-ffi/make-ptr (ffi-size-t/size-t-type) 0 {:resource-type nil
+                                                              :uninitialized? true})
+        ^Pointer str-ptr (PyUnicode_AsUTF8AndSize pyobj size-obj)
+        nbuf (native-buffer/wrap-address (.address str-ptr)
+                                         (size-obj 0)
+                                         nil)]
+    (native-buffer/free size-obj)
+    (-> (.decode StandardCharsets/UTF_8
+                 ;;avoid resource chaining for performance
+                 ^java.nio.ByteBuffer (nio-buffer/native-buf->nio-buf
+                                       nbuf {:resource-type nil}))
+        (.toString))))
+
+(defn pytype-name
+  ^String [type-pyobj]
+  (with-gil
+    (if-let [obj-name (PyObject_GetAttrString type-pyobj "__name__")]
+      (pystr->str obj-name)
+      (do
+        (log/warn "Failed to get typename for object")
+        "failed-typename-lookup"))))
+
+
+(defonce ^{:tag ConcurrentHashMap} type-addr->typename-kwd (ConcurrentHashMap.))
+
+
+(defn pyobject-type-kwd
+  [pyobject]
+  (let [pytype (pyobject-type pyobject)]
+    (.computeIfAbsent type-addr->typename-kwd
+                      (.address pytype)
+                      (reify Function
+                        (apply [this type-addr]
+                          (-> (pytype-name pytype)
+                              (csk/->kebab-case-keyword)))))))
+
+
+
+(def object-reference-logging (atom nil))
+
+
+(defn- wrap-obj-ptr
+  "This must be called with the GIL captured"
+  [pyobj ^Pointer pyobjptr gc-data]
+  (let [addr (.address pyobjptr)]
+    (when @object-reference-logging
+      (log/infof "tracking object  - 0x%x:%4d:%s"
+                 addr
+                 (pyobject-refcount pyobj)
+                 (name (pyobject-type-kwd pyobjptr))
+                 #_(with-out-str
+                     (try
+                       (throw (Exception. "test"))
+                       ""
+                       (catch Exception e
+                         (clojure.stacktrace/print-stack-trace e))))))
+    (pygc/track pyobj
+                ;;we know the GIL is captured in this method
+                #(try
+                   ;;Intentionally overshadow pyobj.  We cannot access it here.
+                   (let [pyobjptr (Pointer. addr)
+                         ;;reference gc data
+                         gc-data (identity gc-data)]
+                     (let [refcount (pyobject-refcount pyobjptr)
+                           typename (pyobject-type-kwd pyobjptr)]
+                       (if (< refcount 1)
+                         (log/errorf "Fatal error -- releasing object - 0x%x:%4d:%s
+Object's refcount is bad.  Crash is imminent"
+                                     addr
+                                     refcount
+                                     typename)
+                         (when @object-reference-logging
+                           (log/infof (format "releasing object - 0x%x:%4d:%s"
+                                              addr
+                                              refcount
+                                              typename)))))
+                     (Py_DecRef pyobjptr))
+                   (catch Throwable e
+                     (log/error e "Exception while releasing object"))))))
+
+
+(defn track-pyobject
+  ^Pointer [pyobj & [{:keys [skip-error-check?
+                             gc-data]}]]
+  (check-gil)
+  (when-let [^Pointer pyobjptr (when pyobj (dt-ffi/->pointer pyobj))]
+    (if-not (= (py-none) pyobjptr)
+      (wrap-obj-ptr pyobj pyobjptr gc-data)
+      ;;Py_None is handled separately
+      (do
+        (Py_DecRef pyobjptr)
+        (when-not skip-error-check? (check-error-throw))
+        nil))))
+
+
+(defn incref-track-pyobject
+  ^Pointer [pyobj]
+  (when pyobj
+    (Py_IncRef pyobj)
+    (track-pyobject pyobj)))
+
+
+
+(def start-symbol-table
+  {:py-single-input 256
+   :py-file-input 257
+   :py-eval-input 258})
+
+
+(defn start-symbol
+  [item]
+  (let [value (cond
+               (number? item)
+               (long item)
+               (keyword? item)
+               (get start-symbol-table item 0))
+        valid-values (set (vals start-symbol-table))]
+    (when-not (contains? valid-values value)
+      (throw (ex-info (format "%s is not a start symbol" item) {})))
+    (int value)))
+
+
+(defn import-module
+  [modname]
+  (if-let [mod (PyImport_ImportModule modname)]
+    (track-pyobject mod)
+    (check-error-throw)))
+
+
+(defn run-simple-string
+  "Run a simple string.  Results are only visible if they are saved in the
+  global or local context.
+
+  https://mail.python.org/pipermail/python-list/1999-April/018011.html
+
+  Implemented in cpython as:
+
+    PyObject *m, *d, *v;
+    m = PyImport_AddModule(\"__main__\");
+    if (m == NULL)
+        return -1;
+    d = PyModule_GetDict(m);
+    v = PyRun_StringFlags(command, Py_file_input, d, d, flags);
+    if (v == NULL) {
+        PyErr_Print();
+        return -1;
+    }
+    Py_DECREF(v);
+    return 0;"
+  [program & {:keys [globals locals]}]
+  (with-gil
+    ;;borrowed reference
+    (let [main-mod (PyImport_AddModule "__main__")
+          ;;another borrowed reference that we will expose to the user
+          globals (or globals (incref-track-pyobject (PyModule_GetDict main-mod)))
+          locals (or locals globals)
+          retval (track-pyobject
+                  (PyRun_String (str program)
+                                (start-symbol :py-file-input)
+                                globals locals))]
+      {:globals globals
+       :locals locals
+       :retval retval})))
+
+
+(defn simplify-or-track
+  "If input is an atomic python object (long, float, string, bool), return
+  the JVM equivalent and release the reference to pyobj.  Else track the object."
+  [^Pointer pyobj]
+  (if-not (or (nil? pyobj) (.isNil pyobj))
+    (if (= pyobj (py-none))
+      (do (Py_DecRef pyobj)
+          nil)
+      (case (pyobject-type-kwd pyobj)
+        :int (with-decref [pyobj pyobj]
+               (PyLong_AsLongLong pyobj))
+        :float (with-decref [pyobj pyobj]
+                 (PyFloat_AsDouble pyobj))
+        :str (with-decref [pyobj pyobj]
+               (pystr->str pyobj))
+        :bool (with-decref [pyobj pyobj]
+                (= (dt-ffi/->pointer pyobj)
+                   (py-true)))
+        ;;maybe copy, maybe bridge - in any case we have to decref the item
+        (track-pyobject pyobj)))
+    (check-error-throw)))
diff --git a/src/libpython_clj2/python/fn.clj b/src/libpython_clj2/python/fn.clj
new file mode 100644
index 0000000..5d7dc98
--- /dev/null
+++ b/src/libpython_clj2/python/fn.clj
@@ -0,0 +1,218 @@
+(ns libpython-clj2.python.fn
+  "Pathways for creating clojure functions from python callable objects and
+  vice versa.  This namespace expects the GIL is captured.
+  Functions bridging this way is relatively expensive but it is the foundation
+  that the more advanced bridging in class.clj is built upon.
+
+  Also contains mechanisms for calling python functions and attributes."
+  (:require [libpython-clj2.python.ffi :as py-ffi]
+            [libpython-clj2.python.base :as py-base]
+            [libpython-clj2.python.protocols :as py-proto]
+            [libpython-clj2.python.gc :as pygc]
+            [tech.v3.datatype.ffi :as dt-ffi]
+            [tech.v3.datatype.ffi.size-t :as ffi-size-t]
+            [tech.v3.datatype.struct :as dt-struct]
+            [clojure.tools.logging :as log]
+            [clojure.stacktrace :as st])
+  (:import [tech.v3.datatype.ffi Pointer]))
+
+(set! *warn-on-reflection* true)
+
+
+(def methoddef-type (dt-struct/define-datatype! :pymethoddef
+                      [{:name :ml_name :datatype (ffi-size-t/ptr-t-type)}
+                       {:name :ml_meth :datatype (ffi-size-t/ptr-t-type)}
+                       {:name :ml_flags :datatype :int32}
+                       {:name :ml_doc :datatype (ffi-size-t/ptr-t-type)}]))
+
+
+(def tuple-fn-iface* (delay  (dt-ffi/define-foreign-interface :pointer? [:pointer :pointer])))
+
+
+(def ^{:tag 'long} METH_VARARGS  0x0001)
+(def ^{:tag 'long} METH_KEYWORDS 0x0002)
+;; METH_NOARGS and METH_O must not be combined with the flags above.
+(def ^{:tag 'long} METH_NOARGS   0x0004)
+
+
+(defn make-tuple-fn
+  ([ifn {:keys [arg-converter
+                result-converter
+                name doc]
+         :or {arg-converter py-base/->jvm
+              result-converter py-base/->python
+              name "_unamed"
+              doc "no documentation provided"}}]
+   (py-ffi/with-gil
+     (let [arg-converter (or arg-converter identity)
+           fn-inst
+           (dt-ffi/instantiate-foreign-interface
+            @tuple-fn-iface*
+            (fn [self tuple-args]
+              (try
+                (let [retval
+                      (apply ifn
+                             (->> (range (py-ffi/PyTuple_Size tuple-args))
+                                  (map (fn [idx]
+                                         (-> (py-ffi/PyTuple_GetItem tuple-args idx)
+                                             (arg-converter))))))]
+                  (if result-converter
+                    (py-ffi/untracked->python retval result-converter)
+                    retval))
+                (catch Throwable e
+                  (log/error e "Error executing clojure function.")
+                  (py-ffi/PyErr_SetString
+                   (py-ffi/py-exc-type)
+                   (format "%s:%s" e (with-out-str
+                                       (st/print-stack-trace e))))))))
+           fn-ptr (dt-ffi/foreign-interface-instance->c @tuple-fn-iface* fn-inst)
+           ;;no resource tracking - we leak the struct
+           method-def (dt-struct/new-struct :pymethoddef {:resource-type nil
+                                                          :container-type :native-heap})
+           name (dt-ffi/string->c name {:resource-type nil})
+           doc (dt-ffi/string->c doc {:resource-type nil})]
+       (.put method-def :ml_name (.address (dt-ffi/->pointer name)))
+       (.put method-def :ml_meth (.address (dt-ffi/->pointer fn-ptr)))
+       (.put method-def :ml_flags METH_VARARGS)
+       (.put method-def :ml_doc (.address (dt-ffi/->pointer doc)))
+       ;;the method def cannot ever go out of scope
+       (py-ffi/retain-forever (gensym) {:md method-def
+                                        :name name
+                                        :doc doc
+                                        :fn-ptr fn-ptr
+                                        :fn-inst fn-inst})
+       ;;no self, no module reference
+       (-> (py-ffi/PyCFunction_NewEx method-def nil nil)
+           (py-ffi/track-pyobject)))))
+  ([ifn]
+   (make-tuple-fn ifn nil)))
+
+
+(defn call-py-fn
+  [callable arglist kw-arg-map arg-converter]
+  (py-ffi/with-gil
+    ;;Release objects marshalled just for this call immediately
+    (let [retval
+          (pygc/with-stack-context
+            (py-ffi/with-decref
+              ;;We go out of our way to avoid tracking the arglist because it is
+              ;;allocated/deallocated so often
+              [arglist (when (or (seq kw-arg-map) (seq arglist))
+                         (py-ffi/untracked-tuple arglist arg-converter))
+               kw-arg-map (when (seq kw-arg-map)
+                            (py-ffi/untracked-dict kw-arg-map arg-converter))]
+              (cond
+                kw-arg-map
+                (py-ffi/PyObject_Call callable arglist kw-arg-map)
+                arglist
+                (py-ffi/PyObject_CallObject callable arglist)
+                :else
+                (py-ffi/PyObject_CallObject callable nil))))]
+      (py-ffi/simplify-or-track retval))))
+
+
+(extend-type Pointer
+  py-proto/PyCall
+  (call [callable arglist kw-arg-map]
+    (call-py-fn callable arglist kw-arg-map py-base/->python))
+  (marshal-return [callable retval] retval))
+
+
+(defn call
+  "Call a python function with positional args.  For keyword args, see call-kw."
+  [callable & args]
+  (py-proto/call callable args nil))
+
+
+(defn call-kw
+  "Call a python function with a vector of positional args and a map of keyword args."
+  [callable arglist kw-args]
+  (py-proto/call callable arglist kw-args))
+
+
+(defn call-attr-kw
+  "Call an object attribute with a vector of positional args and a
+  map of keyword args."
+  [item att-name arglist kw-map arg-converter]
+  (py-ffi/with-gil
+    (if (string? att-name)
+      (py-ffi/with-decref [attval (py-ffi/PyObject_GetAttrString item att-name)]
+        (->> (call-py-fn attval arglist kw-map arg-converter)
+             (py-proto/marshal-return item)))
+      (py-ffi/with-decref
+        [att-name (py-ffi/untracked->python att-name py-base/->python)
+         att-val (py-ffi/untracked->python att-name py-base/->python)]
+        (->> (call-py-fn att-val arglist kw-map arg-converter)
+             (py-proto/marshal-return item))))))
+
+(defn call-attr
+  "Call an object attribute with only positional arguments."
+  [item att-name arglist]
+  (call-attr-kw item att-name arglist nil py-base/->python))
+
+(defn args->pos-kw-args
+  "Utility function that, given a list of arguments, separates them
+  into positional and keyword arguments.  Throws an exception if the
+  keyword argument is not followed by any more arguments."
+  [arglist]
+  (loop [args arglist
+         pos-args []
+         kw-args nil
+         found-kw? false]
+    (if-not (seq args)
+      [pos-args kw-args]
+      (let [arg (first args)
+            [pos-args kw-args args found-kw?]
+            (if (keyword? arg)
+              (if-not (seq (rest args))
+                (throw (Exception.
+                        (format "Keyword arguments must be followed by another arg: %s"
+                                (str arglist))))
+                [pos-args (assoc kw-args arg (first (rest args)))
+                 (drop 2 args) true])
+              (if found-kw?
+                (throw (Exception.
+                        (format "Positional arguments are not allowed after keyword arguments: %s"
+                                arglist)))
+                [(conj pos-args (first args))
+                 kw-args
+                 (rest args) found-kw?]))]
+        (recur args pos-args kw-args found-kw?)))))
+
+
+(defn cfn
+  "Call an object.
+  Arguments are passed in positionally.  Any keyword
+  arguments are paired with the next arg, gathered, and passed into the
+  system as *kwargs.
+
+  Not having an argument after a keyword argument is an error."
+  [item & args]
+  (let [[pos-args kw-args] (args->pos-kw-args args)]
+    (call-kw item pos-args kw-args)))
+
+
+(defn key-sym-str->str
+  [attr-name]
+  (cond
+    (or (keyword? attr-name)
+        (symbol? attr-name))
+    (name attr-name)
+    (string? attr-name)
+    attr-name
+    :else
+    (throw (Exception.
+            "Only keywords, symbols, or strings can be used to access attributes."))))
+
+
+(defn afn
+  "Call an attribute of an object.
+  Arguments are passed in positionally.  Any keyword
+  arguments are paired with the next arg, gathered, and passed into the
+  system as *kwargs.
+
+  Not having an argument after a keyword argument is an error."
+  [item attr & args]
+  (let [[pos-args kw-args] (args->pos-kw-args args)]
+    (call-attr-kw item (key-sym-str->str attr)
+                  pos-args kw-args py-base/->python)))
diff --git a/src/libpython_clj2/python/gc.clj b/src/libpython_clj2/python/gc.clj
new file mode 100644
index 0000000..c68e0e8
--- /dev/null
+++ b/src/libpython_clj2/python/gc.clj
@@ -0,0 +1,85 @@
+(ns libpython-clj2.python.gc
+  "Binding of various sort of gc semantics optimized specifically for
+  libpython-clj."
+  (:import [java.util.concurrent ConcurrentHashMap ConcurrentLinkedDeque]
+           [java.lang.ref ReferenceQueue]
+           [tech.resource GCReference]))
+
+
+(set! *warn-on-reflection* true)
+
+
+(defonce ^:dynamic *stack-gc-context* nil)
+(defn stack-context
+  ^ConcurrentLinkedDeque []
+  *stack-gc-context*)
+
+
+(defonce reference-queue-var (ReferenceQueue.))
+(defn reference-queue
+  ^ReferenceQueue []
+  reference-queue-var)
+
+
+(defonce ptr-set-var (ConcurrentHashMap/newKeySet))
+(defn ptr-set
+  ^java.util.Set []
+  ptr-set-var)
+
+
+(defn track
+  [item dispose-fn]
+  (let [stack-context (stack-context)]
+    (if (= stack-context :disabled)
+      item
+      (let [ptr-val (GCReference. item (reference-queue) (fn [ptr-val]
+                                                           (.remove (ptr-set) ptr-val)
+                                                           (dispose-fn)))]
+        ;;We have to keep track of the pointer.  If we do not the pointer gets gc'd then
+        ;;it will not be put on the reference queue when the object itself is gc'd.
+        ;;Nice little gotcha there.
+        (if stack-context
+          (.add ^ConcurrentLinkedDeque stack-context ptr-val)
+          ;;Ensure we don't lose track of the weak reference.  If it gets cleaned up
+          ;;the gc system will fail.
+          (.add (ptr-set) ptr-val))
+        item))))
+
+
+(defn clear-reference-queue
+  []
+  (when-let [next-ref (.poll (reference-queue))]
+    (.run ^Runnable next-ref)
+    (recur)))
+
+
+(defn clear-stack-context
+  []
+  (when-let [next-ref (.pollLast (stack-context))]
+    (.run ^Runnable next-ref)
+    (recur)))
+
+
+(defmacro with-stack-context
+  [& body]
+  `(with-bindings {#'*stack-gc-context* (ConcurrentLinkedDeque.)}
+     (try
+       ~@body
+       (finally
+         (clear-stack-context)))))
+
+
+(defmacro with-disabled-gc
+  [& body]
+  `(with-bindings {#'*stack-gc-context* :disabled}
+     ~@body))
+
+(defn gc-context
+  []
+  *stack-gc-context*)
+
+
+(defmacro with-gc-context
+  [gc-ctx & body]
+  `(with-bindings {#'*stack-gc-context* ~gc-ctx}
+     ~@body))
diff --git a/src/libpython_clj2/python/info.clj b/src/libpython_clj2/python/info.clj
new file mode 100644
index 0000000..fca3a7d
--- /dev/null
+++ b/src/libpython_clj2/python/info.clj
@@ -0,0 +1,107 @@
+(ns libpython-clj2.python.info
+  "Python system information.  Uses the installed python executable to find out
+  various details about how python is expecting to run."
+  (:require [clojure.java.shell :as sh]
+            [clojure.data.json :as json]
+            [clojure.tools.logging :as log])
+  (:import [java.nio.file Paths]))
+
+
+(def ^:private default-python-executables
+  ["python3" "python3.6" "python3.7" "python3.8" "python3.9" "python"])
+
+
+(defn python-system-info
+  "An information map about the Python system information provided
+  by a Python executable (string).
+
+  :platform (operating system information)
+  :prefix
+  A string giving the site-specific directory prefix where the platform independent Python files are installed; by default, this is the string '/usr/local'. This can be set at build time with the --prefix argument to the configure script. The main collection of Python library modules is installed in the directory prefix/lib/pythonX.Y while the platform independent header files (all except pyconfig.h) are stored in prefix/include/pythonX.Y, where X.Y is the version number of Python, for example 3.2.
+  Note If a virtual environment is in effect, this value will be changed in site.py to point to the virtual environment. The value for the Python installation will still be available, via base_prefix.
+
+  :base-prefix
+  Set during Python startup, before site.py is run, to the same value as prefix. If not running in a virtual environment, the values will stay the same; if site.py finds that a virtual environment is in use, the values of prefix and exec_prefix will be changed to point to the virtual environment, whereas base_prefix and base_exec_prefix will remain pointing to the base Python installation (the one which the virtual environment was created from).
+
+  :executable
+  A string giving the absolute path of the executable binary for the Python interpreter, on systems where this makes sense. If Python is unable to retrieve the real path to its executable, sys.executable will be an empty string or None.
+
+  :exec-prefix
+  A string giving the site-specific directory prefix where the platform-dependent Python files are installed; by default, this is also '/usr/local'. This can be set at build time with the --exec-prefix argument to the configure script. Specifically, all configuration files (e.g. the pyconfig.h header file) are installed in the directory exec_prefix/lib/pythonX.Y/config, and shared library modules are installed in exec_prefix/lib/pythonX.Y/lib-dynload, where X.Y is the version number of Python, for example 3.2.
+  Note If a virtual environment is in effect, this value will be changed in site.py to point to the virtual environment. The value for the Python installation will still be available, via base_exec_prefix.
+
+  :base-exec-prefix
+  Set during Python startup, before site.py is run, to the same value as exec_prefix. If not running in a virtual environment, the values will stay the same; if site.py finds that a virtual environment is in use, the values of prefix and exec_prefix will be changed to point to the virtual environment, whereas base_prefix and base_exec_prefix will remain pointing to the base Python installation (the one which the virtual environment was created from).
+
+  :version
+  (list python-major python-minor python-micro)"
+  [executable]
+  (let [{:keys [out err exit]}
+        (sh/sh executable "-c" "import sys, json;
+print(json.dumps(
+{'platform':          sys.platform,
+  'prefix':           sys.prefix,
+  'base-prefix':      sys.base_prefix,
+  'executable':       sys.executable,
+  'base-exec-prefix': sys.base_exec_prefix,
+  'exec-prefix':      sys.exec_prefix,
+  'version':          list(sys.version_info)[:3]}))")]
+    (when (= 0 exit)
+      (json/read-str out :key-fn keyword))))
+
+
+(defn find-python-info
+  [& [{:keys [python-executable]}]]
+  (->> (concat (when python-executable [python-executable])
+               default-python-executables)
+       (map #(try
+               (python-system-info %)
+               (catch Throwable e nil)))
+       (remove nil?)
+       (first)))
+
+
+(defn find-python-home
+  [system-info & [{:keys [python-home]}]]
+  (cond
+    python-home
+    python-home
+    (seq (System/getenv "PYTHONHOME"))
+    (System/getenv "PYTHONHOME")
+    :else
+    (:prefix system-info)))
+
+
+(defn java-library-path-addendum
+  [system-info & [{:keys [python-home]}]]
+  (when python-home
+    (-> (Paths/get python-home
+                   (into-array String ["lib"]))
+        (.toString))))
+
+
+(defn detect-startup-info
+  [& [{:keys [python-executable library-path] :as options}]]
+  (if python-executable
+    (log/infof "Detecting startup info for Python executable %s" python-executable)
+    (log/info "Detecting startup info"))
+  (let [system-info (find-python-info options)
+        python-home (find-python-home system-info options)
+        java-lib-path (java-library-path-addendum system-info options)
+        [ver-maj ver-med _ver-min] (:version system-info)
+        lib-version                (format "%s.%s" ver-maj ver-med)
+        libname                    (or library-path
+                                       (when (seq lib-version)
+                                         (str "python" lib-version "m")))
+        libnames                   (concat [libname]
+                                           ;;Make sure we try without the 'm' suffix
+                                           (when lib-version
+                                             [(str "python" lib-version)]))]
+    (merge
+     system-info
+     {:python-home                python-home
+      :lib-version                lib-version
+      :libname                    libname
+      :libnames                   libnames}
+     (when java-lib-path
+       {:java-library-path-addendum java-lib-path}))))
diff --git a/src/libpython_clj2/python/io_redirect.clj b/src/libpython_clj2/python/io_redirect.clj
new file mode 100644
index 0000000..5849275
--- /dev/null
+++ b/src/libpython_clj2/python/io_redirect.clj
@@ -0,0 +1,55 @@
+(ns libpython-clj2.python.io-redirect
+  "Implementation of optional io redirection."
+  (:require [libpython-clj2.python.class :as py-class]
+            [libpython-clj2.python.ffi :as py-ffi]
+            [libpython-clj2.python.bridge-as-python :as py-bridge-py]
+            [libpython-clj2.python.base :as py-base]
+            [libpython-clj2.python.protocols :as py-proto]
+            [libpython-clj2.python.gc :as pygc]
+            [libpython-clj2.python.jvm-handle :as jvm-handle]
+            [clojure.tools.logging :as log])
+  (:import [java.io Writer]))
+
+
+(defn self->writer
+  ^Writer [self]
+  (deref (jvm-handle/py-self->jvm-obj self)))
+
+
+(def writer-cls*
+  (jvm-handle/py-global-delay
+   (py-class/create-class
+    "jvm_io_bridge"
+    nil
+    {"__init__" (py-class/wrapped-jvm-constructor)
+     "__del__" (py-class/wrapped-jvm-destructor)
+     "write" (py-class/make-tuple-instance-fn
+              (fn [self & args]
+                (when (seq args)
+                  (.write (self->writer self) (str
+                                               (py-base/->jvm
+                                                (first args)))))
+                (py-ffi/py-none))
+              {:arg-converter identity})
+     "flush" (py-class/make-tuple-instance-fn
+              (constantly (py-ffi/py-none)))
+     "isatty" (py-class/make-tuple-instance-fn
+               (constantly (py-ffi/py-false)))})))
+
+
+(defn setup-std-writer
+  [writer-var sys-mod-attname]
+  (assert (instance? Writer (deref writer-var)))
+  (py-ffi/with-gil
+    (pygc/with-stack-context
+      (let [sys-module (py-ffi/import-module "sys")
+            std-out-writer (@writer-cls* (jvm-handle/make-jvm-object-handle
+                                          writer-var))]
+        (py-proto/set-attr! sys-module sys-mod-attname std-out-writer)
+        :ok))))
+
+
+(defn redirect-io!
+  []
+  (setup-std-writer #'*err* "stderr")
+  (setup-std-writer #'*out* "stdout"))
diff --git a/src/libpython_clj2/python/jvm_handle.clj b/src/libpython_clj2/python/jvm_handle.clj
new file mode 100644
index 0000000..ec0b153
--- /dev/null
+++ b/src/libpython_clj2/python/jvm_handle.clj
@@ -0,0 +1,69 @@
+(ns libpython-clj2.python.jvm-handle
+  "Conversion of a jvm object into an integer and back.  Used for storing handles
+  on python proxies."
+  (:require [libpython-clj2.python.protocols :as py-proto]
+            [libpython-clj2.python.gc :as pygc])
+  (:import [java.util.concurrent ConcurrentHashMap]
+           [java.util UUID]))
+
+
+(defonce ^{:private true
+           :tag ConcurrentHashMap}
+  jvm-handle-map (ConcurrentHashMap.))
+
+
+(defn identity-hash-code
+  ^long [obj]
+  (long (System/identityHashCode obj)))
+
+
+(defn make-jvm-object-handle
+  ^long [item]
+  (let [^ConcurrentHashMap hash-map jvm-handle-map]
+    (loop [handle (identity-hash-code item)]
+      (if (not (.containsKey hash-map handle))
+        (do
+          (.put hash-map handle item)
+          handle)
+        (recur (.hashCode (UUID/randomUUID)))))))
+
+
+(defn get-jvm-object
+  [handle]
+  (.get ^ConcurrentHashMap jvm-handle-map (long handle)))
+
+
+(defn remove-jvm-object
+  [handle]
+  (.remove ^ConcurrentHashMap jvm-handle-map (long handle))
+  nil)
+
+
+(defn py-self->jvm-handle
+  ^long [self]
+  (long (py-proto/get-attr self "jvm_handle")))
+
+
+(defn py-self->jvm-obj
+  ^Object [self]
+  (-> (py-self->jvm-handle self)
+      get-jvm-object))
+
+
+(defn py-self-set-jvm-handle!
+  [self hdl]
+  (py-proto/set-attr! self "jvm_handle" (long hdl))
+  nil)
+
+
+(defmacro py-global-delay
+  "Create a delay object that uses only gc reference semantics.  If stack reference
+  semantics happen to be in effect when this delay executes the object may still be
+  reachable by your program when it's reference counts are released leading to
+  bad/crashy behavior.  This ensures that can't happen at the cost of possibly an object
+  sticking around."
+  [& body]
+  `(delay
+     (py-ffi/with-gil
+       (with-bindings {#'pygc/*stack-gc-context* nil}
+         ~@body))))
diff --git a/src/libpython_clj2/python/np_array.clj b/src/libpython_clj2/python/np_array.clj
new file mode 100644
index 0000000..51e09f2
--- /dev/null
+++ b/src/libpython_clj2/python/np_array.clj
@@ -0,0 +1,198 @@
+(ns libpython-clj2.python.np-array
+  "Bindings for deeper intergration of numpy into the tech.v3.datatype system.  This
+  allows seamless usage of numpy arrays in datatype and tensor functionality such as
+  enabling the tech.v3.tensor/ensure-tensor call to work with numpy arrays -- using
+  zero copying when possible.
+
+  All users need to do is call require this namespace; then as-jvm will convert a numpy
+  array into a tech tensor in-place."
+  (:require [libpython-clj2.python.ffi :as py-ffi]
+            [libpython-clj2.python.fn :as py-fn]
+            [libpython-clj2.python.protocols :as py-proto]
+            [libpython-clj2.python.copy :as py-copy]
+            [libpython-clj2.python.bridge-as-jvm :as py-bridge]
+            [libpython-clj2.python.base :as py-base]
+            [libpython-clj2.python.gc :as pygc]
+            [tech.v3.tensor :as dtt]
+            [tech.v3.datatype.protocols :as dtype-proto]
+            [tech.v3.datatype.casting :as casting]
+            [tech.v3.datatype.argops :as argops]
+            [tech.v3.datatype :as dtype]
+            [clojure.set :as set])
+  (:import [tech.v3.datatype NDBuffer]))
+
+
+(def py-dtype->dtype-map
+  (->> (concat (for [bit-width [8 16 32 64]
+                     unsigned? [true false]]
+                 (str (if unsigned?
+                        "uint"
+                        "int")
+                      bit-width))
+               ["float32" "float64"])
+       (map (juxt identity keyword))
+       (into {})))
+
+
+(def dtype->py-dtype-map
+  (set/map-invert py-dtype->dtype-map))
+
+
+(defn obj-dtype->dtype
+  [py-dtype]
+  (when-let [fields (py-proto/get-attr py-dtype "fields")]
+    (throw (ex-info (format "Cannot convert numpy object with fields: %s"
+                            (py-fn/call-attr fields "__str__" nil))
+                    {})))
+  (if-let [retval (->> (py-proto/get-attr py-dtype "name")
+                       (get py-dtype->dtype-map))]
+    retval
+    (throw (ex-info (format "Unable to find datatype: %s"
+                            (py-proto/get-attr py-dtype "name"))
+                    {}))))
+
+
+(defn numpy->desc
+  [np-obj]
+  (py-ffi/with-gil
+    (let [ctypes (py-proto/as-jvm (py-proto/get-attr np-obj "ctypes") {})
+          np-dtype (-> (py-proto/as-jvm (py-proto/get-attr np-obj "dtype") {})
+                       (obj-dtype->dtype))
+          shape (-> (py-proto/get-attr ctypes "shape")
+                    (py-bridge/generic-python-as-list)
+                    vec)
+          strides (-> (py-proto/get-attr ctypes "strides")
+                      (py-bridge/generic-python-as-list)
+                      vec)
+          long-addr (py-proto/get-attr ctypes "data")]
+      {:ptr long-addr
+       :elemwise-datatype np-dtype
+       :shape shape
+       :strides strides
+       :type :numpy
+       :ctypes ctypes})))
+
+
+(defmethod py-proto/pyobject->jvm :ndarray
+  [pyobj opts]
+  (pygc/with-stack-context
+    (-> (numpy->desc pyobj)
+        (dtt/nd-buffer-descriptor->tensor)
+        (dtt/clone))))
+
+
+(defmethod py-proto/pyobject-as-jvm :ndarray
+  [pyobj opts]
+  (py-bridge/bridge-pyobject
+   pyobj
+   Iterable
+   (iterator [this] (py-bridge/python->jvm-iterator pyobj py-base/as-jvm))
+   dtype-proto/PToTensor
+   (as-tensor [item]
+              (-> (numpy->desc item)
+                  (dtt/nd-buffer-descriptor->tensor)))
+   dtype-proto/PElemwiseDatatype
+   (elemwise-datatype
+    [this]
+    (py-ffi/with-gil
+      (-> (py-proto/get-attr pyobj "dtype")
+          (py-proto/as-jvm {})
+          (obj-dtype->dtype))))
+   dtype-proto/PECount
+   (ecount [this] (apply * (dtype-proto/shape this)))
+
+   dtype-proto/PShape
+   (shape
+    [this]
+    (py-ffi/with-gil
+      (-> (py-proto/get-attr pyobj "shape")
+          (py-proto/->jvm {}))))
+
+   dtype-proto/PToNativeBuffer
+   (convertible-to-native-buffer? [item] true)
+   (->native-buffer
+    [item]
+    (py-ffi/with-gil
+      (dtype-proto/->native-buffer
+       (dtype-proto/as-tensor item))))
+
+   dtype-proto/PSubBuffer
+   (sub-buffer
+    [buffer offset length]
+    (py-ffi/with-gil
+      (-> (dtype-proto/as-tensor buffer)
+          (dtype-proto/sub-buffer offset length))))
+
+   dtype-proto/PToNDBufferDesc
+   (convertible-to-nd-buffer-desc? [item] true)
+   (->nd-buffer-descriptor
+    [item]
+    (py-ffi/with-gil
+      (numpy->desc item)))))
+
+
+(defn datatype->ptr-type-name
+  [dtype]
+  (case dtype
+    :int8 "c_byte"
+    :uint8 "c_ubyte"
+    :int16 "c_short"
+    :uint16 "c_ushort"
+    :int32 "c_long"
+    :uint32 "c_ulong"
+    :int64 "c_longlong"
+    :uint64 "c_ulonglong"
+    :float32 "c_float"
+    :float64 "c_double"))
+
+
+(defn descriptor->numpy
+  [{:keys [ptr shape strides elemwise-datatype] :as buffer-desc}]
+  (py-ffi/with-gil
+    (let [stride-tricks (-> (py-ffi/import-module "numpy.lib.stride_tricks")
+                            (py-base/as-jvm))
+          ctypes (-> (py-ffi/import-module "ctypes")
+                     (py-base/as-jvm))
+          np-ctypes (-> (py-ffi/import-module "numpy.ctypeslib")
+                        (py-base/as-jvm))
+          dtype-size (casting/numeric-byte-width elemwise-datatype)
+          max-stride-idx (argops/argmax strides)
+          buffer-len (* (long (dtype/get-value shape max-stride-idx))
+                        (long (dtype/get-value strides max-stride-idx)))
+          n-elems (quot buffer-len dtype-size)
+          lvalue (long ptr)
+          void-p (py-fn/call-attr ctypes "c_void_p" [lvalue])
+          actual-ptr (py-fn/call-attr
+                      ctypes "cast"
+                      [void-p
+                       (py-fn/call-attr
+                        ctypes "POINTER"
+                        [(py-proto/get-attr
+                          ctypes
+                          (datatype->ptr-type-name elemwise-datatype))])])
+
+          initial-buffer (py-fn/call-attr
+                          np-ctypes "as_array"
+                          [actual-ptr (py-copy/->py-tuple [n-elems])])
+
+          retval (py-fn/call-attr stride-tricks "as_strided"
+                                  [initial-buffer
+                                   (py-copy/->py-tuple shape)
+                                   (py-copy/->py-tuple strides)])]
+      ;;Ensure we have metadata that allows the GC to track both buffer
+      ;;desc and retval
+      (vary-meta retval assoc
+                 :nd-buffer-descriptor buffer-desc))))
+
+
+;;Efficient conversion from jvm to python
+(extend-type NDBuffer
+  py-proto/PCopyToPython
+  (->python [item opts]
+    (-> (dtt/ensure-nd-buffer-descriptor item)
+        (descriptor->numpy)))
+  py-proto/PBridgeToJVM
+  (as-python [item opts]
+    (when (dtype-proto/convertible-to-nd-buffer-desc? item)
+      (-> (dtype-proto/->nd-buffer-descriptor item)
+          (descriptor->numpy)))))
diff --git a/src/libpython_clj2/python/protocols.clj b/src/libpython_clj2/python/protocols.clj
new file mode 100644
index 0000000..77e3233
--- /dev/null
+++ b/src/libpython_clj2/python/protocols.clj
@@ -0,0 +1,91 @@
+(ns libpython-clj2.python.protocols
+  "Internal protocols to libpython-clj.  These allow a dual system where raw pointers
+  work with copying pathways and bridged objects work with bridging pathways."
+  (:require [libpython-clj2.python.ffi :as py-ffi]
+            [tech.v3.datatype :as dtype]))
+
+
+(defprotocol PPythonType
+  (get-python-type [item]
+    "Return a keyword that describes the python datatype of this object."))
+
+
+(defn python-type
+  "Return a keyword that describes the python datatype of this object."
+  ([item]
+   (if item
+     (get-python-type item)
+     :none-type))
+  ([item options]
+   (python-type item)))
+
+
+(defmulti pyobject-as-jvm
+  "Convert (possibly bridge) a pyobject to the jvm"
+  python-type)
+
+
+(defprotocol PCopyToPython
+  (->python [item options]
+    "Copy this item into a python representation.  Must never return nil.
+Items may fallback to as-python if copying is untenable."))
+
+
+(defprotocol PBridgeToPython
+  (as-python [item options]
+    "Aside from atom types, this means the object represented by a zero copy python
+    mirror.  May return nil.  This convertible to pointers get converted
+    to numpy implementations that share the backing store."))
+
+
+(defprotocol PCopyToJVM
+  (->jvm [item options]
+    "Copy the python object into the jvm leaving no references.  This not copying
+are converted into a {:type :pyobject-address} pairs."))
+
+
+(defprotocol PBridgeToJVM
+  (as-jvm [item options]
+    "Return a pyobject implementation that wraps the python object."))
+
+
+(defprotocol PPyDir
+  (dir [item]
+    "Get sorted list of all attribute names."))
+
+
+(defprotocol PPyAttr
+  (has-attr? [item item-name] "Return true of object has attribute")
+  (get-attr [item item-name] "Get attribute from object")
+  (set-attr! [item item-name item-value] "Set attribute on object"))
+
+
+(defprotocol PPyCallable
+  (callable? [item] "Return true if object is a python callable object."))
+
+
+(defprotocol PyCall
+  (call [callable arglist kw-arg-map])
+  (marshal-return [callable retval]))
+
+
+(defprotocol PPyItem
+  (has-item? [item item-name] "Return true of object has item")
+  (get-item [item item-name] "Get an item of a given name from an object")
+  (set-item! [item item-name item-value] "Set an item of to a value"))
+
+
+(defmulti pyobject->jvm
+  "Copy a python object to the jvm based on its python type"
+  python-type)
+
+
+(defmulti pyobject-as-jvm
+  "Bridge a python object to the jvm based on its python type"
+  python-type)
+
+
+(defmulti pydatafy
+  "Datafy a python object.  The metadata namespace must be loaded in order
+  to datafy a python object."
+  python-type)
diff --git a/src/libpython_clj/python/windows.clj b/src/libpython_clj2/python/windows.clj
similarity index 83%
rename from src/libpython_clj/python/windows.clj
rename to src/libpython_clj2/python/windows.clj
index d9ad4bf..60f88ec 100644
--- a/src/libpython_clj/python/windows.clj
+++ b/src/libpython_clj2/python/windows.clj
@@ -1,6 +1,6 @@
-(ns libpython-clj.python.windows
-  (:require [libpython-clj.python.interop :refer [run-simple-string]]
-            [clojure.java.shell :refer [sh]]
+(ns libpython-clj2.python.windows
+  "Windows-specific functionality so that the system works with Anaconda."
+  (:require [clojure.java.shell :refer [sh]]
             [clojure.java.io :as io]
             [clojure.string :as s]))
 
@@ -31,7 +31,8 @@
       "path = '" quoted "';\n"
       "os.environ['PATH'] = path;\n")))
 
-(defn setup-windows-conda! [windows-conda-activate-bat]
+(defn setup-windows-conda! [windows-conda-activate-bat
+                            run-simple-string]
   "Setup python PATH environment variable like in anaconda to be able to load native dlls for numpy etc. like anaconda does."
   (let [echo-bat (create-echo-path-bat!)]
     (->> (get-windows-anaconda-env-path
@@ -40,4 +41,3 @@
          generate-python-set-env-path
          run-simple-string)
     (delete-echo-path-bat! echo-bat)))
-
diff --git a/src/libpython_clj2/python/with.clj b/src/libpython_clj2/python/with.clj
new file mode 100644
index 0000000..6b45dce
--- /dev/null
+++ b/src/libpython_clj2/python/with.clj
@@ -0,0 +1,63 @@
+(ns libpython-clj2.python.with
+  "Implementation of the python 'with' keyword"
+  (:require [libpython-clj2.python.ffi :as py-ffi]
+            [libpython-clj2.python.protocols :as py-proto]
+            [libpython-clj2.python.fn :as py-fn]
+            [tech.v3.datatype.ffi :as dt-ffi])
+  (:import [tech.v3.datatype.ffi Pointer]))
+
+
+(defn python-pyerr-fetch-error-handler
+  "Utility code used in with macro"
+  []
+  (py-ffi/check-gil)
+  (throw (ex-info "python error in flight"
+                  (py-ffi/fetch-normalize-exception))))
+
+
+(defn with-exit-error-handler
+  "Utility code used in with macro"
+  [with-var error]
+  (let [einfo (ex-data error)]
+    (if (every? #(contains? einfo %) [:type :value :traceback])
+      (let [{^Pointer ptype :type
+             ^Pointer pvalue :value
+             ^Pointer ptraceback :traceback} einfo
+            suppress-error? (py-fn/call-attr with-var "__exit__"
+                                             [ptype
+                                              pvalue
+                                              ptraceback])]
+        (if (and ptype pvalue ptraceback (not suppress-error?))
+          (do
+            ;;Manual incref here because we cannot detach the object
+            ;;from our gc decref hook added during earlier pyerr-fetch handler.
+            (py-ffi/PyErr_Restore ptype pvalue ptraceback)
+            (py-ffi/check-error-throw))
+          (do
+            (when ptype (py-ffi/Py_DecRef ptype))
+            (when pvalue (py-ffi/Py_DecRef pvalue))
+            (when ptraceback (py-ffi/Py_DecRef ptraceback)))))
+      (do
+        (py-fn/call-attr with-var "__exit__" [nil nil nil])
+        (throw error)))))
+
+
+(defmacro with
+  "Support for the 'with' statement in python:
+  (py/with [item (py/call-attr testcode-module \"WithObjClass\" true fn-list)]
+      (py/call-attr item \"doit_err\"))"
+  [bind-vec & body]
+  (when-not (= 2 (count bind-vec))
+    (throw (Exception. "Bind vector must have 2 items")))
+  (let [varname (first bind-vec)]
+    `(py-ffi/with-gil
+       (let [~@bind-vec]
+         (with-bindings
+           {#'py-ffi/*python-error-handler* python-pyerr-fetch-error-handler}
+           (py-fn/call-attr ~varname "__enter__" nil)
+           (try
+             (let [retval# (do ~@body)]
+               (py-fn/call-attr ~varname "__exit__" [nil nil nil])
+               retval#)
+             (catch Throwable e#
+               (with-exit-error-handler ~varname e#))))))))
diff --git a/src/libpython_clj/require.clj b/src/libpython_clj2/require.clj
similarity index 92%
rename from src/libpython_clj/require.clj
rename to src/libpython_clj2/require.clj
index 606dc86..e60657c 100644
--- a/src/libpython_clj/require.clj
+++ b/src/libpython_clj2/require.clj
@@ -1,20 +1,12 @@
-(ns libpython-clj.require
+(ns libpython-clj2.require
   "Namespace implementing requiring python modules as Clojure namespaces.  This works via
   scanning the module for metadata and dynamically building the Clojure namespace."
   (:refer-clojure :exclude [fn? doc])
-  (:require [libpython-clj.python :as py]
-            [libpython-clj.metadata :as pymeta]
+  (:require [libpython-clj2.python :as py]
+            [libpython-clj2.metadata :as pymeta]
             [clojure.datafy :refer [datafy nav]]
-            ;;Binds datafy/nav to python objects
-            [libpython-clj.metadata :as metadata]
             [clojure.tools.logging :as log]
-            [libpython-clj.python.protocols :as py-proto]
-            [clojure.core.protocols :as clj-proto])
-  (:import [libpython_clj.python.protocols PPyObject PBridgeToJVM]))
-
-;; for hot reloading multimethod in development
-(ns-unmap 'libpython-clj.require 'intern-ns-class)
-(ns-unmap *ns* 'pydafy)
+            [clojure.core.protocols :as clj-proto]))
 
 
 (defn- parse-flags
@@ -130,7 +122,7 @@
         (intern
          (symbol (str *ns*))
          (with-meta (symbol import-name)
-           {:file (metadata/find-file pyobj)
+           {:file (pymeta/find-file pyobj)
             :line 1})
          pyobj)))
 
@@ -270,16 +262,17 @@
 
    (require-python '[operators :refer :*])"
   ([req]
-   (cond
-     (list? req) ;; prefix list
-     (let [prefix-lists (req-transform req)]
-       (doseq [req prefix-lists] (require-python req)))
-     (symbol? req)
-     (require-python (vector req))
-     (vector? req)
-     (do-require-python req)
-     :else
-     (throw (Exception. "Invalid argument: %s" req)))
+   (py/with-gil
+     (cond
+       (list? req) ;; prefix list
+       (let [prefix-lists (req-transform req)]
+         (doseq [req prefix-lists] (require-python req)))
+       (symbol? req)
+       (require-python (vector req))
+       (vector? req)
+       (do-require-python req)
+       :else
+       (throw (Exception. "Invalid argument: %s" req))))
    :ok)
   ([req & reqs]
    (require-python req)
@@ -335,25 +328,25 @@
 
 
 (defmethod pydafy :default [x]
-  (if (metadata/pyclass? x)
-    (metadata/datafy-module x)
+  (if (pymeta/pyclass? x)
+    (pymeta/datafy-module-or-class x)
     (throw (ex-info (str "datafy not implemented for " (pytype x))
                     {:type (pytype x)}))))
 
 
 (defmethod pynav :default [x]
-  (if (metadata/pyclass? x)
-    (metadata/nav-module x)
+  (if (pymeta/pyclass? x)
+    (pymeta/nav-module x)
     (throw (ex-info (str "nav not implemented for " (pytype x))
                     {:type (pytype x)}))))
 
 
 (defmethod pydafy 'builtins.module [m]
-  (metadata/datafy-module m))
+  (pymeta/datafy-module-or-class m))
 
 
 (defmethod pynav 'builtins.module [coll k v]
-  (metadata/nav-module coll k v))
+  (pymeta/nav-module coll k v))
 
 
 (defmethod pydafy 'builtins.dict [x]
@@ -397,6 +390,3 @@
               (datafy [item#] (py-datafy item#))
               clj-proto/Navigable
               (nav [coll# k# v#] (py-nav coll# k# v#))))))
-
-
-(pydatafy PPyObject PBridgeToJVM)
diff --git a/src/libpython_clj/sugar.clj b/src/libpython_clj2/sugar.clj
similarity index 90%
rename from src/libpython_clj/sugar.clj
rename to src/libpython_clj2/sugar.clj
index 5972ca5..6d2fa0f 100644
--- a/src/libpython_clj/sugar.clj
+++ b/src/libpython_clj2/sugar.clj
@@ -1,5 +1,7 @@
-(ns libpython-clj.sugar
-  (:require [libpython-clj.python :as py]))
+(ns libpython-clj2.sugar
+  (:require [libpython-clj2.python :as py]))
+
+(py/initialize!)
 
 
 (let [{{:strs [pyxfn]} :globals}
diff --git a/test/libpython_clj/classes_test.clj b/test/libpython_clj/classes_test.clj
deleted file mode 100644
index 9ce4a32..0000000
--- a/test/libpython_clj/classes_test.clj
+++ /dev/null
@@ -1,52 +0,0 @@
-(ns libpython-clj.classes-test
-  (:require [libpython-clj.python :as py]
-            [clojure.test :refer :all]
-            [clojure.edn :as edn]))
-
-
-(py/initialize!)
-
-
-(deftest new-cls-test
-  ;;The crux of this is making instance functions.  The make-tuple-instance-fn pathway
-  ;;is pretty close to the metal and as such it does no marshalling of parameters by
-  ;;default.  You can turn on marshalling of all parameters adding `arg-converter`
-  ;;optional argument (this may make your life easier) or you can marshal just the
-  ;;parameter you have to (self in the examples below).
-  (let [cls-obj (py/create-class
-                 "Stock" nil
-                 {"__init__" (py/make-tuple-instance-fn
-                              (fn [self name shares price]
-                                ;;Because we did not use an arg-converter, all the
-                                ;;arguments above are raw jna Pointers - borrowed
-                                ;;references.
-                                (py/set-attrs! self {"name" name
-                                                     "shares" shares
-                                                     "price" price})
-                                ;;If you don't return nil from __init__ that is an
-                                ;;error.
-                                nil))
-                  "cost" (py/make-tuple-instance-fn
-                          (fn [self]
-                            (* (py/$. self shares)
-                               (py/$. self price)))
-
-                          ;;Convert self to something that auto-marshals things.
-                          ;;This pathway will autoconvert all arguments to the function.
-                          :arg-converter py/as-jvm
-                          :method-name "cost")
-                  "__str__" (py/make-tuple-instance-fn
-                             (fn [self]
-                               ;;Alternative to using arg-converter.  This way you can
-                               ;;explicitly control which arguments are converted.
-                               (let [self (py/as-jvm self)]
-                                 (pr-str {"name" (py/$. self name)
-                                          "shares" (py/$. self shares)
-                                          "price" (py/$. self price)}))))
-                  "clsattr" 55})
-        new-instance (cls-obj "ACME" 50 90)]
-    (is (= 4500
-           (py/$a new-instance cost)))
-    (is (= 55 (py/$. new-instance clsattr)))
-    (is (= {"name" "ACME", "shares" 50, "price" 90}
-           (edn/read-string (.toString new-instance))))))
diff --git a/test/libpython_clj2/classes_test.clj b/test/libpython_clj2/classes_test.clj
new file mode 100644
index 0000000..600229d
--- /dev/null
+++ b/test/libpython_clj2/classes_test.clj
@@ -0,0 +1,42 @@
+(ns libpython-clj2.classes-test
+  (:require [libpython-clj2.python :as py]
+            [clojure.test :refer :all]
+            [clojure.edn :as edn]))
+
+
+(py/initialize!)
+
+
+(deftest new-cls-test
+  ;;The crux of this is making instance functions to get the 'self' parameter
+  ;;passed in.
+  (let [cls-obj (py/create-class
+                 "Stock" nil
+                 {"__init__" (py/make-instance-fn
+                              (fn [self name shares price]
+                                ;;Because we did not use an arg-converter, all the
+                                ;;arguments above are raw jna Pointers - borrowed
+                                ;;references.
+                                (py/set-attrs! self {"name" name
+                                                     "shares" shares
+                                                     "price" price})
+                                ;;If you don't return nil from __init__ that is an
+                                ;;error.
+                                nil))
+                  "cost" (py/make-instance-fn
+                          (fn [self]
+                            (* (py/py.- self shares)
+                               (py/py.- self price)))
+                          {:name "cost"})
+                  "__str__" (py/make-instance-fn
+                             (fn [self]
+                               (pr-str {"name" (py/py.- self name)
+                                        "shares" (py/py.- self shares)
+                                        "price" (py/py.- self price)})))
+                  "clsattr" 55})
+        new-instance (cls-obj "ACME" 50 90)]
+    (is (= 4500
+           (py/$a new-instance cost)))
+    (is (= 55 (py/py.- new-instance clsattr)))
+    (is (= {"name" "ACME", "shares" 50, "price" 90}
+           (edn/read-string (.toString new-instance))))))
diff --git a/test/libpython_clj2/ffi_test.clj b/test/libpython_clj2/ffi_test.clj
new file mode 100644
index 0000000..f28f83c
--- /dev/null
+++ b/test/libpython_clj2/ffi_test.clj
@@ -0,0 +1,36 @@
+(ns libpython-clj2.ffi-test
+  "Short low level tests used to verify specific aspects of ffi integration"
+  (:require [libpython-clj2.python.ffi :as py-ffi]
+            [libpython-clj2.python :as py]
+            [libpython-clj2.python.protocols :as py-proto]
+            [libpython-clj2.python.fn :as py-fn]
+            [clojure.test :refer [deftest is]]))
+
+
+(py/initialize!)
+
+
+(deftest module-type-name-test
+  (py-ffi/with-gil
+    (let [main-mod (py-ffi/PyImport_AddModule "__main__")
+          mod-type (py-ffi/pyobject-type main-mod)
+          mod-name (py-ffi/pytype-name mod-type)]
+      (is (= "module" mod-name)))))
+
+
+(deftest pydir-basic
+  (py-ffi/with-gil
+   (let [main-mod (py-ffi/PyImport_AddModule "__main__")
+         dirdata (py-proto/dir main-mod)]
+     (is (>= (count dirdata) 7)))))
+
+
+(deftest error-handling
+  (py-ffi/with-gil
+    (is (thrown? Exception (py-ffi/run-simple-string "data = 1 +")))))
+
+
+(deftest clj-fn
+  (py-ffi/with-gil
+    (let [pfn (py-fn/make-tuple-fn #(+ %1 %2))]
+      (is (= 3 (py-fn/call pfn 1 2))))))
diff --git a/test/libpython_clj/fncall_test.clj b/test/libpython_clj2/fncall_test.clj
similarity index 82%
rename from test/libpython_clj/fncall_test.clj
rename to test/libpython_clj2/fncall_test.clj
index aa43dfd..974b697 100644
--- a/test/libpython_clj/fncall_test.clj
+++ b/test/libpython_clj2/fncall_test.clj
@@ -1,17 +1,13 @@
-(ns libpython-clj.fncall-test
-  (:require [libpython-clj.python :as py]
+(ns libpython-clj2.fncall-test
+  (:require [libpython-clj2.python :as py]
             [clojure.test :refer :all]))
 
 
 (py/initialize!)
 
-
-(py/import-as testcode testmod)
-(py/from-import inspect getfullargspec)
-
 (deftest complex-fn-test
   (let [testmod (py/import-module "testcode")
-        testcases (py/$. testmod complex_fn_testcases)]
+        testcases (py/py.- testmod complex_fn_testcases)]
     (is (= (-> (get testcases "complex_fn(1, 2, c=10, d=10, e=10)")
                (py/->jvm))
            (-> (py/$a testmod complex_fn 1 2 :c 10 :d 10 :e 10)
@@ -20,7 +16,6 @@
                (py/->jvm))
            (-> (py/$a testmod complex_fn 1 2 10 11 12 :d 10 :e 10)
                (py/->jvm))))
-
     (is (= (-> (get testcases "complex_fn(1, 2, c=10, d=10, e=10)")
                (py/->jvm))
            (-> (apply py/afn testmod "complex_fn" [1 2 :c 10 :d 10 :e 10])
diff --git a/test/libpython_clj/iter_gen_seq_test.clj b/test/libpython_clj2/iter_gen_seq_test.clj
similarity index 83%
rename from test/libpython_clj/iter_gen_seq_test.clj
rename to test/libpython_clj2/iter_gen_seq_test.clj
index dfa8006..4976885 100644
--- a/test/libpython_clj/iter_gen_seq_test.clj
+++ b/test/libpython_clj2/iter_gen_seq_test.clj
@@ -1,8 +1,8 @@
-(ns libpython-clj.iter-gen-seq-test
+(ns libpython-clj2.iter-gen-seq-test
   "Iterators/sequences and such are crucial to handling lots of forms
   of data and thus they have to work correctly between the languages."
-  (:require [libpython-clj.python :as py]
-            [libpython-clj.require :refer [require-python]]
+  (:require [libpython-clj2.python :as py]
+            [libpython-clj2.require :refer [require-python]]
             [clojure.test :refer :all]))
 
 
diff --git a/test/libpython_clj/python/numpy_test.clj b/test/libpython_clj2/numpy_test.clj
similarity index 79%
rename from test/libpython_clj/python/numpy_test.clj
rename to test/libpython_clj2/numpy_test.clj
index f6856ae..91d409c 100644
--- a/test/libpython_clj/python/numpy_test.clj
+++ b/test/libpython_clj2/numpy_test.clj
@@ -1,10 +1,9 @@
-(ns libpython-clj.python.numpy-test
+(ns libpython-clj2.numpy-test
   (:require [clojure.test :refer [deftest is]]
-            [libpython-clj.python :as py]
+            [libpython-clj2.python :as py]
             [tech.v3.datatype :as dtype]
             [tech.v3.datatype.functional :as dfn]
-            [tech.v3.tensor :as dtt])
-  (:import [com.sun.jna Pointer]))
+            [tech.v3.tensor :as dtt]))
 
 (py/initialize!)
 
diff --git a/test/libpython_clj/python_test.clj b/test/libpython_clj2/python_test.clj
similarity index 79%
rename from test/libpython_clj/python_test.clj
rename to test/libpython_clj2/python_test.clj
index 6a4cb0a..e12766e 100644
--- a/test/libpython_clj/python_test.clj
+++ b/test/libpython_clj2/python_test.clj
@@ -1,16 +1,25 @@
-(ns libpython-clj.python-test
-  (:require [libpython-clj.python :as py :refer [py. py.. py.- py* py**]]
-            [libpython-clj.jna :as libpy]
+(ns libpython-clj2.python-test
+  (:require [libpython-clj2.python :as py :refer [py. py.. py.- py* py**]]
+            ;;support for tensor/numpy integration
+            [libpython-clj2.python.np-array]
+            [libpython-clj2.python.ffi :as py-ffi]
             [tech.v3.datatype :as dtype]
             [tech.v3.datatype.functional :as dfn]
+            [tech.v3.datatype.ffi :as dt-ffi]
             [tech.v3.tensor :as dtt]
-            [tech.v3.jna :as jna]
             [clojure.test :refer :all])
   (:import [java.io StringWriter]
            [java.util Map List]
-           [com.sun.jna Pointer]))
+           [tech.v3.datatype.ffi Pointer]))
+
 
 (py/initialize!)
+;; Useful to see how many times we convert a string to a python object.
+;; (dt-ffi/enable-string->c-stats!)
+;; (.addShutdownHook (Runtime/getRuntime) (Thread.
+;;                                         (fn []
+;;                                           (clojure.pprint/pprint
+;;                                            (dt-ffi/string->c-data-histogram)))))
 
 (deftest stdout-and-stderr
   (is (= "hey\n" (with-out-str
@@ -23,27 +32,27 @@
   (is (thrown? Throwable (py/run-simple-string "import sys\nprint('hey', stderr"))))
 
 (deftest dicts
-  (let [py-dict (py/->python {:a 1 :b 2})]
-    (is (= :dict (py/python-type py-dict)))
-    (is (= 2 (-> (py/call-attr py-dict "__len__")
-                 (py/->jvm))))
-    (is (= {"a" 1 "b" 2}
-           (->> (py/->jvm py-dict)
-                (into {}))))
-    (let [bridge-dict (py/as-jvm py-dict)]
-      (is (instance? Map bridge-dict))
-      (is (= #{"a" "b"} (->> (keys bridge-dict)
-                             set)))
-      (is (= #{1 2} (->> (vals bridge-dict)
-                         set)))
+  (py/with-gil-stack-rc-context
+    (let [py-dict (py/->python {:a 1 :b 2})]
+      (is (= :dict (py/python-type py-dict)))
+      (is (= 2 (-> (py/call-attr py-dict "__len__")
+                   (py/->jvm))))
       (is (= {"a" 1 "b" 2}
-             (into {} bridge-dict))))))
+             (->> (py/->jvm py-dict)
+                  (into {}))))
+      (let [bridge-dict (py/as-jvm py-dict)]
+        (is (instance? Map bridge-dict))
+        (is (= #{"a" "b"} (->> (keys bridge-dict)
+                               set)))
+        (is (= #{1 2} (->> (vals bridge-dict)
+                           set)))
+        (is (= {"a" 1 "b" 2}
+               (into {} bridge-dict)))))))
 
 (deftest lists
   (let [py-list (py/->py-list [4 3 2 1])]
     (is (= :list (py/python-type py-list)))
-    (is (= 4 (-> (py/call-attr py-list "__len__")
-                 (py/->jvm))))
+    (is (= 4 (py/call-attr py-list "__len__")))
     (is (= [4 3 2 1]
            (->> (py/->jvm py-list)
                 (into []))))
@@ -67,14 +76,12 @@
     (py/run-simple-string "item3 = item + item2")
     (is (= 300 (globals "item3")))))
 
+
 (deftest numpy-and-back
   (let [jvm-tens (dtt/->tensor (->> (range 9)
                                     (partition 3))
                                :datatype :float64)]
-    ;;zero-copy can't work on jvm datastructures with current JNA tech.
-    ;;IT would require 'pinning' which isn't yet available.
-    (is (nil? (py/as-numpy jvm-tens)))
-    (let [py-tens (py/->numpy jvm-tens)]
+    (let [py-tens (py/->python jvm-tens)]
       (is (= [[0.0 1.0 2.0] [3.0 4.0 5.0] [6.0 7.0 8.0]]
              (-> (dtt/as-tensor py-tens)
                  dtt/->jvm)))
@@ -192,41 +199,6 @@
         bridged (py/as-jvm dict)]
     (is (= nil (bridged nil)))))
 
-(deftest custom-clojure-item
-  (let [att-map {"clojure_fn" (py/->python #(vector 1 2 3))}
-        my-python-item (py/create-bridge-from-att-map
-                        ;;First is the jvm object that this bridge stands for.  If this
-                        ;;gets garbage collected then the python side will be removed
-                        ;;also.
-                        att-map
-                        ;;second is the list of attributes.  In this case, since this
-                        ;;object isn't iterable or anything, this function will do.
-                        att-map)
-        py-mod (py/import-module "testcode")]
-    (is (= [1 2 3]
-           (py/call-attr py-mod "calling_custom_clojure_fn" my-python-item)))
-
-    ;;Now this case is harder.  Let's say we have something that is iterable and we
-    ;;want this to be reflected in python.  In that case we have to call 'as-python'
-    ;;on the iterator and that 'should' work.
-
-    (let [my-obj (reify
-                   Iterable
-                   (iterator [this] (.iterator [4 5 6])))
-          ;;Note that attributes themselves have to be python objects and wrapping
-          ;;this with as-python makes that function wrap whatever it returns in a
-          ;;bridging python object also.
-          att-map {"__iter__" (py/as-python #(.iterator my-obj))}
-          my-python-item (py/create-bridge-from-att-map
-                          my-obj
-                          ;;second is the list of attributes.  In this case, since this
-                          ;;object isn't iterable or anything, this function will do.
-                          att-map)]
-      (is (= [4 5 6]
-             (vec my-obj)))
-      (is (= [4 5 6]
-             (vec (py/call-attr py-mod "for_iter" my-python-item)))))))
-
 (deftest bridged-dict-to-jvm
   (let [py-dict (py/->py-dict {:a 1 :b 2})
         bridged (py/as-jvm py-dict)
@@ -235,21 +207,21 @@
 
 (deftest calling-conventions
   (let [np (py/import-module "numpy")
-        linspace (py/$. np linspace)]
+        linspace (py/py.. np -linspace)]
 
     (is (dfn/equals [2.000 2.250 2.500 2.750 3.000]
                     (dtt/as-tensor (linspace 2 3 :num 5))))
     (is (dfn/equals [2.000 2.250 2.500 2.750 3.000]
-                    (dtt/as-tensor (py/c$ linspace 2 3 :num 5))))
+                    (dtt/as-tensor (py/$c linspace 2 3 :num 5))))
     (is (dfn/equals [2.000 2.250 2.500 2.750 3.000]
-                    (dtt/as-tensor (py/a$ np linspace 2 3 :num 5))))))
+                    (dtt/as-tensor (py/$a np linspace 2 3 :num 5))))))
 
 (deftest syntax-sugar
   (py/initialize!)
   (let [np (py/import-module "numpy")]
-    (is (= (str (py/$. np linspace))
+    (is (= (str (py/py.- np linspace))
            (str (py/get-attr np "linspace"))))
-    (is (= (str (py/$.. np random shuffle))
+    (is (= (str (py/py.. np -random -shuffle))
            (str (-> (py/get-attr np "random")
                     (py/get-attr "shuffle"))))))
 
@@ -337,8 +309,8 @@ class Foo:
       (py.. (**update {:c 3}))
       (py.. (**update [[:d 4]] {:e 5})))
 
-    (is (= d {"a" 1 "b" 2 "c" 3 "d" 4 "e" 5})))
-  )
+    (is (= d {"a" 1 "b" 2 "c" 3 "d" 4 "e" 5}))))
+
 
 (deftest infinite-seq
   (let [islice (-> (py/import-module "itertools")
@@ -378,13 +350,13 @@ class Foo:
                     (dtt/as-tensor ary-data)))))
 
 (deftest false-is-always-py-false
-  (let [py-false (libpy/Py_False)
+  (let [py-false (py-ffi/py-false)
         ->false (py/->python false)
         as-false (py/as-python false)]
-    (is (= (Pointer/nativeValue (jna/as-ptr py-false))
-           (Pointer/nativeValue (jna/as-ptr ->false))))
-    (is (= (Pointer/nativeValue (jna/as-ptr py-false))
-           (Pointer/nativeValue (jna/as-ptr as-false))))))
+    (is (= (dt-ffi/->pointer py-false)
+           (dt-ffi/->pointer ->false)))
+    (is (= (dt-ffi/->pointer py-false)
+           (dt-ffi/->pointer as-false)))))
 
 (deftest instance-abc-classes
   (let [py-dict (py/->python {"a" 1 "b" 2})
diff --git a/test/libpython_clj/require_python_test.clj b/test/libpython_clj2/require_python_test.clj
similarity index 93%
rename from test/libpython_clj/require_python_test.clj
rename to test/libpython_clj2/require_python_test.clj
index 42afbea..89954b3 100644
--- a/test/libpython_clj/require_python_test.clj
+++ b/test/libpython_clj2/require_python_test.clj
@@ -1,8 +1,8 @@
-(ns libpython-clj.require-python-test
-  (:require [libpython-clj.require :as req
+(ns libpython-clj2.require-python-test
+  (:require [libpython-clj2.require :as req
              :refer [require-python pydafy pynav]
              :reload true]
-            [libpython-clj.python :as py]
+            [libpython-clj2.python :as py]
             [clojure.test :refer :all]
             [clojure.datafy :refer [datafy nav]]))
 
@@ -15,7 +15,7 @@
                   :as pymath])
 
 (deftest base-require-test
-  (let [publics (ns-publics (find-ns 'libpython-clj.require-python-test))]
+  (let [publics (ns-publics (find-ns 'libpython-clj2.require-python-test))]
     (is (not (contains? publics 'sin)))
     (is (= 10.0 (double (floor 10.1))))
     (is (thrown? Throwable (require-python '[math :refer [blah]])))))
@@ -47,6 +47,7 @@
     (is (= #{:b} (parse-flags #{:a :b} '[:b :a false])))
     (is (= #{:b :a} (parse-flags #{:a :b} '[:b :a false :a true])))))
 
+
 (require-python '[builtins :as python]
                 '[builtins.list :as python.pylist])
 
@@ -76,7 +77,7 @@
   (is csv.DictWriter/writerows))
 
 (deftest test-req-transform
-  (let [req-transform #'libpython-clj.require/req-transform]
+  (let [req-transform #'libpython-clj2.require/req-transform]
 
     (is (= (req-transform 'a) 'a))
 
@@ -115,7 +116,7 @@
   (is (set? (datafy (python/frozenset [1 2 3])))))
 
 (deftest import-python-test
-  (is (= :ok (libpython-clj.require/import-python))))
+  (is (= :ok (libpython-clj2.require/import-python))))
 
 (require-python '[socket :bind-ns true])
 (require-python 'socket.SocketIO)
@@ -145,4 +146,3 @@
           "Methods have line numbers")
       (is (string? file)
           "Methods have file paths"))))
-
diff --git a/test/libpython_clj/stress_test.clj b/test/libpython_clj2/stress_test.clj
similarity index 74%
rename from test/libpython_clj/stress_test.clj
rename to test/libpython_clj2/stress_test.clj
index 73b5356..aae3893 100644
--- a/test/libpython_clj/stress_test.clj
+++ b/test/libpython_clj2/stress_test.clj
@@ -1,9 +1,8 @@
-(ns libpython-clj.stress-test
+(ns libpython-clj2.stress-test
   "A set of tests meant to crash the system or just run the system out of
   memory if it isn't setup correctly."
-  (:require [libpython-clj.python :as py]
-            [libpython-clj.jna :as libpy]
-            [libpython-clj.python.object :as pyobject]
+  (:require [libpython-clj2.python :as py]
+            [libpython-clj2.python.class :as py-class]
             [clojure.test :refer :all]
             [clojure.edn :as edn]))
 
@@ -56,11 +55,6 @@ def getmultidata():
     (gd-fn)))
 
 
-;;Ensure that failure to open resource context before tracking for stack
-;;related things causes immediate failure.  Unless you feel like being pedantic,
-;;this isn't necessary.  The python library automatically switches to normal gc-only
-;;mode if a resource context is open.
-
 (deftest forever-test
   (doseq [items (take 10 (partition 999 (get-data)))]
     ;;One way is to use the GC
@@ -115,31 +109,31 @@ def getmultidata():
 (deftest new-cls-stress-test
   (dotimes [iter 100]
     (py/with-gil-stack-rc-context
-      (let [test-cls (py/create-class "testcls" nil
-                                      {"__init__" (py/make-tuple-instance-fn
-                                                   (fn [self name shares price]
-                                                     (py/set-attr! self "name" name)
-                                                     (py/set-attr! self "shares" shares)
-                                                     (py/set-attr! self "price" price)
-                                                     nil))
-                                       "cost" (py/make-tuple-instance-fn
-                                               (fn [self]
-                                                 (let [self (py/as-jvm self)]
-                                                   (* (py/$. self shares)
-                                                      (py/$. self price)))))
-                                       "__str__" (py/make-tuple-instance-fn
-                                                  (fn [self]
-                                                    (let [self (py/as-jvm self)]
-                                                      ;;Self is just a dict so it converts to a hashmap
-                                                      (pr-str {"name" (py/$. self name)
-                                                               "shares" (py/$. self shares)
-                                                               "price" (py/$. self price)}))))
-                                       "testvar" 55}
-                                      )
+      (let [test-cls (py-class/create-class
+                      "testcls" nil
+                      {"__init__" (py-class/make-tuple-instance-fn
+                                   (fn [self name shares price]
+                                     (py/set-attr! self "name" name)
+                                     (py/set-attr! self "shares" shares)
+                                     (py/set-attr! self "price" price)
+                                     nil))
+                       "cost" (py-class/make-tuple-instance-fn
+                               (fn [self]
+                                 (let [self (py/as-jvm self)]
+                                   (* (py/py.- self shares)
+                                      (py/py.- self price)))))
+                       "__str__" (py-class/make-tuple-instance-fn
+                                  (fn [self]
+                                    (let [self (py/as-jvm self)]
+                                      ;;Self is just a dict so it converts to a hashmap
+                                      (pr-str {"name" (py/py.- self name)
+                                               "shares" (py/py.- self shares)
+                                               "price" (py/py.- self price)}))))
+                       "testvar" 55})
             new-inst (test-cls "ACME" 50 90)]
         (is (= 4500
                (py/$a new-inst cost)))
-        (is (= 55 (py/$. new-inst testvar)))
+        (is (= 55 (py/py.- new-inst testvar)))
 
         (is (= {"name" "ACME", "shares" 50, "price" 90}
                (edn/read-string (.toString new-inst))))))))
diff --git a/test/libpython_clj/sugar_test.clj b/test/libpython_clj2/sugar_test.clj
similarity index 93%
rename from test/libpython_clj/sugar_test.clj
rename to test/libpython_clj2/sugar_test.clj
index 00adf24..eee36c2 100644
--- a/test/libpython_clj/sugar_test.clj
+++ b/test/libpython_clj2/sugar_test.clj
@@ -1,7 +1,7 @@
-(ns libpython-clj.sugar-test
-  (:require [libpython-clj.sugar :as pysug :reload true]
+(ns libpython-clj2.sugar-test
+  (:require [libpython-clj2.sugar :as pysug :reload true]
             [clojure.test :refer :all]
-            [libpython-clj.python :as py]))
+            [libpython-clj2.python :as py]))
 
 (deftest test-pyxfn
   (let [{{:strs [addem addx addxkwargs stringify addbang]} :globals}
@@ -76,4 +76,3 @@ def addbang(xs):
                (conj result input)))
             []
             (range 4))))))
-