When we talk about scaling software, threading and async get all the buzz.
And while they are powerful, using asynchronous queues can often be much more effective.
You might think this means creating a Celery server and maybe running RabbitMQ or Redis as well.
What if you wanted this async ability and many more message exchange patterns like PubSub,
but you wanted to do zero of that server work, none of it?
Then you should check out ZeroMQ.
ZeroMQ is to queuing what Flask is to web apps,
a powerful and simple framework for you to build just what you need.
You're almost certain to learn some new networking patterns and capabilities in this episode with our guest,
Min Reagan-Kelly.
He's here to discuss ZeroMQ for Python,
as well as how ZeroMQ is central to the internals of Jupyter Notebooks.
This is Talk Python to Me, episode 306, recorded February 11th, 2021.
Welcome to Talk Python to Me, a weekly podcast on Python, the language, the libraries, the ecosystem,
and the personalities.
This is your host, Michael Kennedy.
Follow me on Twitter where I'm @mkennedy,
and keep up with the show and listen to past episodes at talkpython.fm,
and follow the show on Twitter via at Talk Python.
This episode is brought to you by Linode and Mido.
If you want to host some Python in the cloud,
check out Linode and use our code to get $100 credit.
If you've ever wanted to work with Python's data science tools like Jupyter Notebooks and Pandas,
but you wanted to do it like you worked with Excel,
with a spreadsheet and a visual designer,
check out Mido.
One quick announcement before we get to the interview.
We'll be giving away five tickets to attend PyCon US 2021.
This conference is one of the primary sources of funding for the PSF,
and it's going to be held May 14th to 15th online.
And because it's online this year, it's open to anyone around the world.
So we decided to run a contest to help people,
especially those who have never been part of PyCon before,
attend it this year.
Just visit talkpython.fm/PyCon 2021
and enter your email address,
and you'll be in the running for an individual PyCon ticket.
compliments of Talk Python.
These normally sell for about $100 each.
And if you're certain you want to go,
I encourage you to visit the PyCon website,
get a ticket,
and that money will go to support the PSF and the Python community.
If you want to be in this drawing,
just visit talkpython.fm/PyCon 2021.
Enter your email address.
You'll be in the running to win a ticket.
Now let's get on to that interview.
Min, welcome to Talk Python to me.
Thanks. Thanks for having me.
Yeah, it's really good to have you here.
I'm excited to talk about building applications with zero MQ.
It's definitely one of those topics that I think lives in this realm of asynchronous programming
in ways that I think a lot of people don't initially think of.
Like you think of async programming,
like, okay, well, I'm going to do threads.
And if threads don't work,
maybe I'll do multi-processing.
And then those are my options, right?
But Qs and other types of intermediaries are really interesting for creating powerful design patterns
that let you scale your apps and do all sorts of interesting things, right?
Yeah.
Zero MQ has definitely given me a new way of thinking about how different components
of a distributed application can talk to each other.
Absolutely.
So we're going to dive all into that, which is going to be super fun.
But before we do, maybe just tell us a bit about yourself.
How do you get started in programming in Python?
I got started mostly during college where I was studying physics
and what essentially amounted to a computational physics degree.
And that's where I met, I had a professor, Brian Granger,
who's one of the heads of the now Jupyter project.
I started working with him as an undergrad back in 2006,
working on what would ultimately become the first IPython parallel,
the interactive parallel computing toolkit in IPython.
Oh, that's really cool.
What university was that at?
That was at Santa Clara University.
Uh-huh.
Okay.
Nice.
Yeah, it's really cool.
I mean, you were working on physics,
but you were also like at the heart of Jupyter.
And I guess IPython at the time, right?
IPython notebooks is what they were called, yeah?
Yeah, coming up on 15 years that I've been working on IPython and Jupyter.
Nice.
And so you basically, did you do programming before you got into your physics
or did you just learn that to get going with your degree?
So I did it for fun in college,
but I played around with calculators and stuff in high school,
but nothing more complicated than printing out a Fibonacci sequence.
Nice.
So were you a TI person?
Were you an HP person?
What kind of world were you?
Yeah, TI all the way.
Yeah, me too, obviously.
That reverse Polish notation, that was just wrong.
Yeah.
Although I did, in college, we did implement reverse Polish calculators
because it's easier.
Yeah.
Yeah, cool.
It's easy to write a parser for that.
Yeah, it's easier on the creator and not on the user, which I feel like computers used to be way more that way, right?
Like, oh, it's easier for us to make it this way.
Well, if you put a little more work, it'll be easier for the millions of people that use it.
Well, maybe not millions in the early, early days, but yeah.
Very interesting.
So what kind of stuff were you doing with IPython in the early days?
What kind of stuff were you studying with your physics?
In grad school, I was studying computational plasma physics simulation.
So it was particle simulations of plasmas, which are, you know, what's going on in stars.
I wasn't studying stars, but that's one of the main plasmas that people know about.
And so I was doing particle simulations of just studying a system, seeing what happens.
And we were working on an interactive scale simulation code that was written in C++.
It was a fairly nice and welcoming C++ physics simulation as far as academic C++ physics simulations go.
Yeah.
But essentially, my first year of grad school, I wrote a paper where I had to do a simulation run for five days,
look at the results, and then change some input parameters and run it again for five days,
and do that for a few dozen iterations, and then wrote a paper about that.
And then ultimately, my PhD thesis was about wrapping the same code in Python
so that I could have programmatic, like, self-steering, actually by this time driven in a notebook.
Doing what had been manual, like change it, run it again, doing that automatically,
just by wrapping the C++ code in a Python API, I could do it much more efficiently and actually take something that took multiple iterations of five days,
I could actually get the same result in a little over half a day, I think.
Wow.
That's really cool.
So you would run basically a little small simulation with C, and then you would get the inputs into Python and go,
okay, well, now how do we adjust it? How do we change that and go from there?
It was the same simulation, but I had to tune the inputs a little bit differently in order to,
there were some details of the physics that basically I had to turn some knobs way up because I wasn't constantly watching it.
Yeah, I see.
And then it would say like, oh, that number was too big, turn it down, do it again, and then look and see if it was too big again.
Whereas once I had a knob that could be turned while the simulation was running, which is the feature that was missing,
I could say ramp up the current in this case.
So I was studying the limit of current that you can get through a diode.
Right.
And so ramp up the current, and then once I see evidence that it's too high, ramp it back down,
and then I could change, you know, lower the slope so that I would slowly approach an equilibrium and find the limit.
Whereas when I had to do it with this long, I had to basically wait until it's done to change the input,
I had to dramatically over-inject the current, which made the simulations extra slow because there were way too many particles.
Yeah, yeah.
And the spatial resolution had to be really fine in order to resolve something called a virtual cathode,
and I didn't have to do nearly as much.
Basically, because I could do this live feedback, the simulations themselves could be quicker
because I didn't have to provide enough information that I could get the right answer out a long time later.
That's super cool, and obviously it makes a huge difference.
You know, I feel like we have a similar background in how we got into programming.
Like, I was doing a math degree, and my math research led me into doing C++ on Silicon Graphics, mainframes, computers, and stuff like that.
You talk about letting these things run overnight.
Like, everyone would just kick off these things, and then they would come back in the morning to see what happened.
And, you know, interesting sort of random sidebars.
Like, we came in one morning, and we all tried to log into our little workstations or whatever,
and, like, the system just wouldn't respond.
There was just something wrong with it.
And we were like, well, what the heck is going on?
You know, like, this is like a quarter-million-dollar computer.
It should let us log in.
What is wrong with it, right?
And it was in there running really loud, right?
It's like this huge, loud machine.
And it turned out that one of the grad students, not me, had started a job that was—they were trying to figure out what was going on,
and they were logging a lot because they were having problems with their code.
And it was in this tight loop that ran all night.
And because it was, like, a somewhat small group, they had no disk limits or permission restrictions for the large part.
So it literally used up every byte on the server, and then it just wouldn't do anything at all.
And, I mean, that's the—the reason I bring this up is, like, that's the challenge of these things where you, like,
start it and let it run for days and then figure out what's happening.
You're like, oh, it just used up the entire hard drive of this giant machine.
Whoops.
It broke it for everyone, right?
Yeah.
And so this live aspect or more self-guided is a really great idea.
It's really—it's a big step ahead.
Maybe more so than people initially, like, hear it as, right?
It's a big deal.
Yeah, and I think it was—that's part of what got me really excited about the programmatic—
giving—when you have access to something in Python and you have an environment like iPython or Jupyter or whatever,
being able to interact with something, what is usually—
what you might think of as an offline physics simulation that runs for hours or days,
if you have a Python API to it, you can steer it while it's going.
Like, you can turn the knobs on your experiment while it's running.
You know, sometimes there are reasons why that's not actually a good idea, but it's really powerful to not—
one of the costs, you know, that I had in my experiment was that in order to change the inputs, I had to start over.
Like, physically, I didn't have to.
Like, if it were a physical machine where I were measuring inputs and outputs, I could just, you know, turn the current down and see the results.
But the code didn't support that.
The frustrating thing about it was the way the physics was written did support that.
The C++ code did support that.
We just hadn't written an interface that allowed me as the user to do it.
And so the Python wrapping work was surprisingly little.
It was, you know, expose Python APIs to—or C++ APIs to Python with Cython.
Yeah, that's cool.
And then I could turn all the knobs that were already there in the code.
I just was now allowed to turn them.
It sounds to me, thinking back on the timeline there, that must have been pretty early days in the data science, computational science world of Python, right?
Like, NumPy was probably just about out around then.
By the time I was doing that work, that would have been around 2012.
Oh, okay.
So, yeah, it was more established at that point.
I was thinking back to, like, early, like, 2005 or something.
That, yeah, not quite, right?
Yeah, not quite.
That was when I was still, as a little undergrad, was still able to help make building NumPy—I was able to submit patches to make building NumPy on Mac a little easier.
That's cool.
Before it became, like, completely polished and, like, so widely used.
I suspect it's really nerve-wracking to contribute to those kinds of things now.
It was a nice community, and it was exciting to be able to say, like, I have a problem.
There's probably other people who have a similar problem.
Let me see if I can fix it and then submit a patch.
Yeah, that's fantastic.
Cool.
So you're carrying on with your scientific computation work these days, right?
What do you go to now?
Now my job is actually working on Jupyter and Python-related stuff.
So I am a senior research engineer at Simula Research Lab in Oslo, Norway, where I've been since 2015.
And I'm the head of the Department of Scientific Computing and Numerical Analysis.
So I'm in a department where people are doing physical simulations of the brain and studying PEs and things like that.
But what I do is I work on JupyterHub.
Yeah.
So we made JupyterHub as a tool for deploying while I was still at Berkeley.
We made it as a tool for deploying—for people who want to deploy Jupyter Notebooks.
We say it's for if you're a person who has computers and you have some humans and you want to help those humans use those computers with Jupyter.
That's what JupyterHub is for.
Initially, it was scoped to be an extremely small project because we didn't have any maintenance burden to spare.
The target of that was research groups like mine in grad school of a few people, maybe small classes,
who have one server in their office and they just want to make it easier for people to log in and use Jupyter on there.
Right.
This is when people—
Right, right.
People had all their SSH tunnel reverse—SSH to my server with the opener reverse tunnel so I can point localhost and it's actually over there.
So we were trying to make life easier for those folks.
They were running the servers locally and they're like, how do I get access to my computation over there but on my machine?
That kind of stuff, right?
Yeah, it's like if I'm a person who's already comfortable with Jupyter but now I have access to a computer that's over there, how do I run my notebook stuff over there?
And there were a few solutions to that involving SSH tunnels or whatever.
And JupyterHub was aimed at those folks.
But it turned out that's not the user community that's really been the most excited about it.
With things like Zero to JupyterHub, the JupyterHub on Kubernetes started by UVPanda and now led by a whole bunch of wonderful folks, we're shifted into a larger scale than initially designed for.
It definitely seems like JupyterLab and the predecessors to it have really probably exploded beyond the scale that people initially expected.
I mean, it's just the de facto way of doing things if you're doing computation these days, it seems.
I for certain folks definitely seem to like it.
We try to respond to feedback and just build things that people are going to use.
Nice.
So if you're running JupyterLab for such a big group, I mean, there's got to be crazy amounts of computation and, you know, super computer type stuff there.
What does your whole setup look like there?
What are you running for your folks at the research lab?
I run JupyterHub instances for like a summer school and they'll run a workshop and I'll spin up a cluster for running JupyterHub for doing some physics simulations.
Usually it's for teaching.
Okay.
So I don't operate, I don't operate hubs for research.
Usually I usually operate them for teaching.
I see.
Yeah.
So maybe the computation there, it's like, yeah, not nearly as high as we're trying to model the universe and let that go.
Yeah.
Often those folks, the computations are handed off somewhere.
Right.
But yeah, like the folks at NERSC in Berkeley are doing some really exciting stuff with running JupyterHub on a supercomputer.
Yeah.
Cool.
Are you doing like Kubernetes or Docker or are you just setting up VMs or how do you handle that side?
Thanks to the work of all the folks contributing to Zero to JupyterHub, deploying JupyterHub with Kubernetes, I think, is the easiest to deploy and maintain as long as you have access to a managed Kubernetes.
I still wouldn't recommend deploying Kubernetes itself to anybody.
It seems fairly complicated to run and maintain that side of things.
Yeah.
But if you have a turnkey solution, the one I use the most is the, is GKE, the Google managed Kubernetes.
Yeah.
And Zero to JupyterHub will let you just step through and say, give me a cluster and run JupyterHub on it.
And then all I really need to do is for the workshop is build the user images.
Nice.
Very cool.
You also do some stuff with Binder.
Is that right?
I help operate Binder.
So mybinder.org is a service built on top of JupyterHub that takes JupyterHub as a service for running notebooks on Kubernetes.
In Binder's case, it's running on Kubernetes.
And Binder ties in another Jupyter project called Repo to Docker that says, look at a repo, build a Docker image with the contents of that.
Hopefully that can run everything.
So like if it finds a requirement set txt.
Yeah, exactly.
It's got something that specifies its dependencies or something like that.
Yeah.
And there's a bunch of things that we support.
And the idea is to automate existing best practices, right?
So find anything that people are already using to specify environments and then install those and then build an image.
And then what Binder does is, so I built an image, then send that over to JupyterHub to say launch a notebook server with that image.
Right.
Yeah.
You can see like right here just on the main site, just put in a GitHub repo and maybe a branch or a tag or something and then click go.
And then it spins up literally a Jupyter notebook that you can play with.
This portion of Talk Python To Me is sponsored by Linode.
Simplify your infrastructure and cut your cloud bills in half with Linode's Linux virtual machines.
Develop, deploy, and scale your modern applications faster and easier.
Whether you're developing a personal project or managing large workloads, you deserve simple, affordable, and accessible cloud computing solutions.
As listeners of Talk Python To Me, you'll get a $100 free credit.
You can find all the details at talkpython.fm/Linode.
Linode has data centers around the world with the same simple and consistent pricing regardless of location.
Just choose the data center that's nearest to your users.
You'll also receive 24-7, 365 human support with no tiers or handoffs regardless of your plan size.
You can choose shared and dedicated compute instances or you can use your $100 in credit on S3 compatible object storage, managed Kubernetes clusters, and more.
If it runs on Linux, it runs on Linode.
Visit talkpython.fm/Linode or click the link in your show notes.
Then click that create free account button to get started.
When people go to GitHub, they can oftentimes see the Jupyter Notebook.
When I first saw that, I'm like, how in the world is GitHub computing this stuff for me to see?
I'm like, maybe that is super computationally expensive.
Here's the answer.
How do they know the data and the dependencies?
The reality is they've just taken what's stored in the Notebook, right?
That is the last run and it's there.
But if you want to play with it, you can't do that on GitHub.
But you can on mybinder.org, right?
That turns that into an interactive Notebook.
It adds interactivity to the sharing that you already have with NVMe or GitHub.
Yeah, very cool project.
I didn't know that much about it, but I had Tim Head on the show a while ago on episode 256 to talk about that.
And I learned a lot.
So, yeah, quite neat.
Now, let's jump into 0MQ.
So 0MQ is not a Python thing, but it is very good for Python people, right?
It has support for many different languages.
And your work primarily has been to work on making this nice and easy from Python, right?
Yeah, so 0MQ is a C++, a library written in C++ with a C API, which makes using it a little easier.
And it's a very small API, which is part of why it's usable from so many languages,
is that writing bindings for it is relatively easy.
The idea of 0MQ is it's a messaging library.
Naming is a little funky because it comes from the world, right?
The people who create it come from the world of message brokers and message queues.
And 0MQ is a bit tongue-in-cheek in that it's not actually a message queue at all.
It's a messaging library where it's just adding a little bit of a layer of abstraction on the networking in terms of you have some distributed application where things need to talk to each other.
And 0MQ is a tool for building that.
And it's this library.
And then you can write the bindings for that library so that you can use it from any of a variety of languages.
And so Brian Granger and I worked on the Python bindings to use 0MQ from Python, which is called PyZMQ.
Yeah, when I first thought of it, I imagined it is like a server.
Something like Redis or Celery or something like that that you start up.
And then you create queues or something on it.
And then like different things can talk to it.
But I think maybe a better conceptualization of it might be like Flask.
Flask is not a server, but a Flask is a framework that you can put into your Python app and then run it.
And it is the server itself, right?
Yeah.
And so I'd say that 0MQ, the best way I would describe it is it's a fancy socket library.
Yeah.
So you create sockets and you send messages.
And the sockets talk to other sockets.
You send messages, you receive messages.
And then 0MQ is all about what abstractions and guarantees and things it gives around those sockets and messages.
There seems to be a lot of culture and zen about 0MQ.
Like there's a lot of interesting like nomenclature in the way that they talk about stuff over there.
So they talk about the zero in 0MQ.
And the philosophy starts with zero.
Zero is for the zero broker.
It's brokerless.
Zero latency, zero cost.
It's free.
Zero admin.
You don't have like a server type of thing.
But also to a culture of minimalism that permeates the project.
Adding power by removing complexity rather than exposing new functionality.
You want to speak to that just a little bit?
Like your experience with that?
Well, so I can speak to that.
As someone who doesn't work on LibZMQ that much and more somebody who writes bindings for it.
I can speak to that as it's nice that LibZMQ doesn't change that much.
Yeah.
And that part of point of, so, ZMQ has all these features.
They have, we'll talk a little bit about it in a second.
But so it's structured so that there are sockets and there are different kinds of sockets that have different behaviors for building these different kinds of distributed applications.
But in terms of the API, there's just one.
Like a socket has an API.
All sockets have the same API.
So from the standpoint of writing bindings to a library, I just need to say, I know how to wrap the socket APIs.
And then as they add new types, those are just constants that I need to handle.
So I don't need to, oh, there's a new kind of socket.
I need to implement a new Python class for that new kind of socket.
I just need to wrap socket.
So that's really, that's from my perspective in terms of as new features are developed and things in LibZMQ, from my perspective as a binding developer or binding maintainer, that's really nice.
But it also, that also extends to the application layer.
That once you have a socket and you understand sockets, changing the type of socket changes your message pattern.
It doesn't change anything about the APIs you need to use and things like that.
Yeah, so maybe we could talk a little bit about what the application model, I don't necessarily get into programming yet, but like the application model, right?
So we've got contexts, we've got sockets, and we've got messages.
And those are the basic building blocks of what working with this is like.
So if I wanted to create something that could, you know, maybe other applications could talk to it and exchange data, one of my options might be to create a RESTful API that exchanges JSON, right?
Yeah, sure.
There's a lot of challenges with that.
One, it's sort of send request only, right?
I send over my JSON and then it gives me a response, but I can't subscribe to future changes, right?
I got to do something like WebSockets in that world if I want something like that, which I guess might be closer to this.
And then also it's doing the text conversion.
It's probably a little bit slower.
Got to do maybe extra work for async, right?
So there's a lot of things that are maybe similar, but extra patterns, right?
So instead of just request response, you might have pub sub, you might have like multicast, like something comes in and everyone gets notified about it.
Can you talk about some of those differences?
Like maybe compare it to what other more common APIs people might know about?
Yeah.
So the main thing that distinguishes ZRMQ is that you have you, so a context is kind of an implementation detail that you shouldn't need to care about, but you still need to create.
Okay.
Sockets are the main thing that you deal with and you create a, so you create a socket and every socket has a type and that type determines the messaging pattern.
So that means that's where we're getting at these kinds of protocols and messaging patterns.
So with a web server, that's usually a request reply pattern.
So you have clients connect, send a request, and then they get a reply.
Pub sub system might be, you know, some totally different thing.
Maybe in web server land, maybe it's a server side events, you know, an event stream connection.
And with ZRMQ, the difference between those is the socket type.
So if you're creating a publish subscribe relationship, you create a publish socket on one side and you create a subscribe socket on the other side.
If you're doing a request reply, you use a socket called a dealer and a router.
There's a request and a reply socket in ZRMQ, but nobody should ever use them.
They're just a special case of router dealer.
And then there's another one called a pattern called like a ventilator and sink.
So that's like what you'd use in a work queue, for instance, where you've got a source of work and then it sends a message to one destination, but you don't necessarily care which one.
I see.
So maybe you're trying to do a scaled out computing.
Yeah, exactly.
You've got 10 machines that could all do the work and you want to somehow evenly distribute that work, right?
So you're like, all right, well, we're just going to throw it at ZRMQ.
All the things that are available to do work can subscribe.
They need to.
They could even like drop out after doing some work, but then not receive anymore, potentially something like that.
So that's what ZRMQ, one of the main things that ZRMQ does is it takes control over those things like multiple peers and connection events and stuff like that.
Because take, for example, the publish subscribe.
There's two key.
The things to think about with ZRMQ are what happens when you've got more than one peer connected.
Right.
And what happens when you've got nobody to send to.
So in the publish subscribe model, what it does is when you send a message on a socket, it will send it will immediately send that message to everybody who's connected and ready.
So if somebody is not able to keep up, right, there's like a queue that's building up and it's gotten full, it'll just drop, stop, drop messages to that peer until they catch up.
If there are no peers, then it's really fast because it just doesn't send anything and just deletes the memory.
Yeah.
Right.
So with when you're thinking of ZRMQ from Python, sending messages is really the other thing about it is that it's asynchronous.
That send is not actually send doesn't return when the message is on the TCP buffer or whatever.
Send returns when you have handed control of the message to the ZRMQ IO thread.
And this is why ZRMQ has this concept of context.
Contexts are what own the IO threads that actually do all the real work of talking to over the network.
So when you're sending in PyZMQ, you're really just passing ownership of the memory to ZRMQ and it returns immediately.
And so you don't actually know when that message is actually is finally sent and you shouldn't care.
Sometimes you do care and it can get complicated.
Yeah.
But ZRMQ tries to make you not care.
Interesting.
So basically you set up the relationships between the clients and the server through like these different models.
And then you drop off the messages to ZRMQ and it just it deals from there.
Right.
It figures out who gets what and when.
So your application, as soon as it gets the message passed off to the ZRMQ layer, it can go about doing other stuff, right?
Exactly.
So ZRMQ, because it's a C++ library, it's not going to grab the GIL or anything.
So it's a true, even if you're using Python, it's a true multi-threaded application, even if you're only using one Python thread.
Yeah.
You handed that memory off to C++, which is running in the background.
You can do some GIL holding intense operation and ZRMQ will be happily dealing with all the network stuff.
Right.
It's got its own C++ thread, which has nothing to do with the GIL and they go do its own thing, right?
Yeah, it can, there's, there is something that comes up in PyZMQ where it can come back and try to grab the GIL from the IO thread or it used to, it doesn't anymore because of Python does actually need to know when it let go of that memory in order to avoid segfaults.
Yeah.
But that's an implementation detail.
Cool.
So is there anything about reliable messaging here where you could say, I want to make sure that this message gets delivered to every client?
Like if you said, for example, in this PubSub, if some of them fall behind, it can just drop the messages.
Is there a way to say, you know, pile that up and then send it along when it catches up or whatever?
The ZRMQ perspective is that that's an application level problem.
So ZRMQ helps you build the messaging layer to not, so that it doesn't crash.
And that's part of why it drops messages.
And then it basically, it's up to you to say, you know, if you're sending messages and there's a generation counter, for instance, and then you notice I got message five and then I got message eight.
It's up to your application to say, okay, keep a, you know, keep a recent history buffer so that folks can come back with a different pattern, a request reply pattern to say, give me a batch of recent messages that I missed so that I can resume.
But ZRMQ doesn't help you with that.
It handles all the networking stuff, you know, connections.
You don't have to worry about that.
You just have to worry about like, how do we set up a way to ask again or have the application ask if it wants more.
Yeah.
Nice.
David out there on the live stream asks, what alternatives to ZRMQ could have been used to build the Jupyter protocol?
So maybe before we, thanks for the question.
Before we get to that though, maybe let's just talk about like, oh wait, ZRMQ was used to build the Jupyter protocol?
Yeah.
So the Jupyter protocol, which essentially started out in IPython parallel.
So we had this interactive parallel computing networking framework that eventually evolved into, wait, we've got this network protocol for remote computation.
We can build basically a REPL protocol, an interactive shell protocol.
And that ultimately became the Jupyter protocol, which was built with this kind of ZRMQ mindset of, I want to be able to have multiple front ends at the same time.
So let's say, and we had in 2010, I think, Fernando and Brian had a working prototype of real-time collaboration on a terminal.
So you've got, using this protocol, so you've got two people with the terminal, you're typing, you can run code and you can see each other's output.
So we built the Jupyter protocol with multiple connections in mind.
So that means there's this request reply socket.
So the front end sends a request, please run this code.
And the back end sends a reply saying, I ran that, here's the result.
And there's another channel called IOPub where we publish output.
So when you do a print statement or you display a map.plit figure, that's a message that goes on PubSub channel, which means that every connected front end, right?
So you could have multiple JupyterLab instances.
They'll all receive the same message.
Right.
I mean, that sounds like the perfect example of PubSub.
Somebody's making a change.
Well, somebody has triggered the server to make a change, but it doesn't matter who started that change.
Everybody looking at it wants to see the output, right?
This portion of Talk Python to Me is brought to you by Mido.
You feel like you're stumbling around trying to work with pandas within your Jupyter notebooks?
What if you could work with your data frames visually like they were Excel spreadsheets, but have it write the Python code for you?
With Mido, you can.
Mido is a visual front end inside Jupyter notebooks that automatically generates the equivalent Python code within your notebook cells.
Mido lets you generate production ready Python just by editing a spreadsheet, all right with inside Jupyter.
You're sure to learn some interesting Python pandas tricks just by using the visual aspects of that spreadsheet.
You can merge, pivot, filter, sort, clean, and create graphs all in the front end and get the equivalent Python code written right in your notebook.
So stop spending your time Googling all that syntax and try Mido today.
Just visit talkpython.fm/Mido to get early access.
That's talkpython.fm/M-I-T-O or just click their link in the show notes.
So to answer the question of, and this becomes particularly important in the design of IPython parallel, but getting to the question of what alternatives to 0.mq could have been used for the Jupyter protocol.
So when we were designing it, we thought this protocol of talking directly to kernels was going to be the main thing.
It turns out that the main way, you know, in 2020 or 2021, I guess we're in now.
The most kernels talk one-to-one with a notebook web server.
And it's the web server that's the one that's actually spanning out to multiple clients.
Right.
With that being the case, if we had required the notebook server to be the place where we do all this multiplexing and everything, we could have actually built the lower level Jupyter protocol on something much simpler, just an HTTP rest and event stream.
Probably could have worked just fine.
Right.
Maybe just use WebSockets or something on the web server side.
Yeah.
If WebSockets had existed at the time.
Yeah, yeah, yeah.
Those were also years away.
Yeah.
So much of that stuff's easier now and the browser support it and so on.
So yeah, super interesting.
Now, one thing that that makes me think of is, you know, how much, how close are we or some sort of Google Docs, JupyterLab type of thing, right?
I mean, we're not there, right?
I know there's SageMath and there's Google Colab.
There's other systems where this does exist, right?
Where there's sort of, we can all type on the notebook, same cell, same time type of thing.
Is there anything like that, JupyterLab, that I just didn't miss or I missed?
There have been, I think, three prototypes at this point that have been developed and are, and ultimately not finished for various reasons.
There's another one that's picking up again and going strong using YJS, working with QuantStack, I believe.
Okay.
Hopefully soon.
That's not an area of the project where I've done a lot of work.
I helped a little bit with the last one.
Well, I mean, to me, it sounds like that's like just all JavaScript front end craziness and not a whole lot of other stuff, right?
Yeah, the state probably lives on the server, so you need to have a server, whether it's running CRDT or whatever, to synchronize the state.
And the PubSub, yeah, as well for the changes.
Yeah.
Going down this rabbit hole for a minute, also, Nawa asked an interesting question about 0MQ.
What's the story with 0MQ and microservices, right?
And I think microservices, people often set up a whole bunch of little small flask or FastAPI things that talk JSON exchange request response.
But man, like the performance and the multiplexing, all those types of things sound like it actually could be a really awesome non-HTTP-based microservice.
Yeah, I think 0MQ is a really good fit for microservice-based distributed applications.
Because one of the things you do when you're designing with microservices is you're defining the communication relationship and you're scaling axes.
And a nice thing to do with 0MQ is to say is that your application doesn't change when you've got a bunch of peers.
Your application doesn't even need to know when a new peer comes and goes because 0MQ handles that.
So one of the things that's nifty about, that's weird and magical, but also really useful about 0MQ is it abstracts binding and connecting and transports.
So you can have the same application with the same connection pattern and maybe this one binds and maybe, you know, one side binds and one side connects.
So pub binds and sub connects.
But you can also have sub bind and pub connect.
And you can also have your pub bind once and connect three times.
And none of that changes how your application behaves.
It just changes where the connections go.
Right.
Because with microservices, when you're doing HTTP requests, you always got to figure out, okay, well, what's the URL I'm going to?
And sometimes that even gets real tricky with, all right, what is even the URL of the identity server?
What is the URL of the thing that manages the catalog before I even request it?
And then usually that's a single endpoint HTTP request type of thing if it's like an update notification.
So yeah, I can imagine that there's some real interesting things here.
You can have in, you know, a distributed work kind of situation, you can have one or a few sources of work.
And then you have an elastic number of workers that just connect and start receiving messages.
And the way this is in kind of the push-pull pattern.
So pub sub is always send them every message you send, send it to everybody connected who can receive a message.
Whereas push-pull is whenever you send a message, send it to exactly one peer.
And I don't care which one.
And so if more peers are connected, it will load balance across all those peers.
But if only one's connected, it will just keep sending to that one.
And at no point in the sender do you ever need to know how many.
You never get notified that peers are connecting.
You never need to know that there are any peers, that there's one peer, that there's a thousand peers.
It doesn't matter.
So then it's in your distributed application to say, you know, this one's sending with this push-pull pattern, ventilator sync pattern.
And then I just elastically grow my number of workers and shut them down.
And they just connect and close, disconnect and everything.
And it just works.
Interesting.
Okay, so a follow-up question from Nawa that makes me think.
So he asks, he, she, sorry, asks whether it is a good idea to replace REST communication with CRMQ.
And so that leads me to wonder.
You talked about, you send the message and it's sort of fire and forget style, like message sent success.
But so often what I want to do is I need to know what products are offered on sale right now from the sale microservice or whatever.
I need to get the answer back.
These three products.
Thank you.
You know what I mean?
How do I implement something like that where I send a message, but I want the answer?
That's the request reply pattern.
So you would use either the request reply sockets or the router dealer sockets for that kind of pattern.
And that's, you send a message.
So in that case, this does have multi-peer semantics, but usually the requester is connected to one endpoint.
It can be connected to several, in which case it'll load balance its requests.
And the router, so the receiver side, handles requests.
And each request comes in with the message prefix that identifies who that message came from.
And then it can send replies using that identity prefix.
And it will go to whoever sent that request.
And that's how most of the Jupyter protocol is a request reply pattern.
Yeah, I guess so.
That makes sense because you want the answer from the computation or whatever.
You want to know that it's done and so on.
Interesting.
Okay.
Yeah.
Well, that's pretty neat.
The other thing that comes to mind around this is serialization.
So when I'm doing microservices, I make JSON documents.
I know what things go in JSON, right?
Like fundamental types, strings, integers, and so on.
Surprisingly, dates and times can't go into JSON.
That blow.
I still like, this is 2021.
We can't come up with a text representation of what time it is.
Anyway, that's a bit of a pain.
It seems to me like you might be able to exchange more data more efficiently using a binary.
And it's not even going over the HTTP layer, right?
It's going literally over a TCP socket.
Yeah.
Or IPC with BST sockets or UDP.
Even lower level than that.
Yeah.
Not even touching the network stack.
Right.
Yeah.
This gets us to the last piece of XeromQ that we haven't talked about yet.
And that's what is a message.
Right.
They're nice things.
You know, if you've worked with, you know, a lower level socket library, just talking TCP
sockets, you know, you have to deal with like chunks and then figure out when you're done
with your message protocol.
But if you've ever written an HTTP server, you know, you need to find those double blank lines
and all that stuff before you know that you have a request that you can hand off to your
request handler.
Right.
You're doing a whole lot of funky, like parsing the header.
Okay.
The header, it says it's the next hundred bytes of the thing that I'm getting.
And this one is an end.
So I'm at a part like it's, it's gnarly stuff.
I've worked on projects where we did that and it's super fast, but boy, it is a, it's a low
level business.
Something like Flask does for you.
Yeah.
Right.
It, it implements the HTTP protocol and then says, okay, here's a request.
Please send a reply.
And it helps you construct that reply message.
So zero MQ or PyZMQ live at that, at that level of Flask where a message is a, not one
binary blob, but a collection of binary blobs.
And zero MQ always delivers whole messages.
So it's, it's atomic and it's asynchronous and it's messaging, which means you will never
get part of a message.
There's no like, okay, I got the first third of this message, keep it in my own buffer until
I get the, you know, get to the end.
A zero MQ socket does not become readable until an entire message is ready to be read.
Right.
And there may be buffering down the C++ layer, but it's not going to tell you I've received
a thing until it's fully baked.
Got the answer.
Right.
Yeah.
All that stuff still happens.
It's just zero MQ takes care of that.
And then when, when at the PyZMQ level or at the ZMQ API level, when a socket, when you receive
with a socket or in PyZMQ, you say receive multi-part, you get a list of blobs of memory.
And so if you're talking about serialization with Jason, so PyZMQ has a helper function
called send Jason, and it's literally just Jason that dumps thing and then send it.
Yeah.
With a little ensuring UTF eight bytes, I think.
Yeah.
Nice.
So PyZMQ, it's, and this is really turned out to be really important for more important
for IPython parallel than it turned out to be for the Jupyter protocol.
I'm not familiar with IPython parallel.
Tell me about this.
IPython parallel is, so if you're aware of the Jupyter protocol, it's a network protocol
for, I've got somewhere over the network where I want to run code.
And I have this protocol for sending messages.
Please run this code.
Give me a return output.
Show me display stuff.
IPython parallel is a kind of weird parallel computing library based on the fact that,
so I've got a network protocol to talk to, to run code remotely.
Why don't I just wrap that in a little bit to talk to N remote places.
Maybe partition up the work across them or something like that.
The fun thing about ZerumQ is that in a Jupyter notebook, the kernel is the server.
The kernel listens for connections on its various sockets.
And then the notebook server, the web server, or the Qt console, or the terminal is a client,
and it connects to those sockets.
So I Python parallel, because of this fun stuff about ZerumQ not caring about connection,
direction, or count, adds a scheduler layer, and it modifies the kernel, the IPython kernel
that you'd use in a Jupyter notebook.
And the only change it makes is instead of binding on those sockets, it connects to a central scheduler.
And the kernel is otherwise identical.
The message protocol is otherwise identical.
But the connection direction is different because the many-to-one relationship is different.
There's one controller and many engines instead of many clients connecting to one kernel.
And then, again, using kind of some of the magic of the ZerumQ routing identities,
there's a multiplexer in PyZMQ called a monitored queue, where if you have router socket,
so a router socket is one where the first...
So we talked about a ZerumQ message is a sequence of blobs of memory.
So it can just be one.
With a router socket, it's always at least two,
because the first part is the routing identity to tell the underlying ZerumQ
which peer should it actually send to.
Sure, okay.
That's cool.
And we don't have to worry about that, because that's down at the low level, right?
But that's what happens.
When you get a request, you need to remember that first part.
So when you send the reply, the first part of the reply is the ID that came at the request.
Got it, got it.
So it goes back to the right place.
Yeah.
But you can also use that if you know the IDs,
you can send messages to a destination without being in response to a request, right?
What a router really is, is a socket that can route messages based on this identity prefix.
So if you have a bundle of identity prefixes, then you can send messages to anyone at any time.
And that allows us to build a multiplexing scheduler that from one client connected to one scheduler,
just send messages, regular, plain old ZerumQ protocol messages,
but with an identity prefix from the client.
And those will end up at the right kernel just by the magic of ZerumQ routing identities.
Yeah, yeah.
And so this is a substantially different messaging pattern.
So the request reply patterns are all the same, but the connection patterns are totally different.
And the client and the endpoint don't need to know about it at all.
We just have this adapter in the middle.
I feel like to really get the zen of this and take full advantage,
you've got to really think about these messaging patterns and styles a little bit,
because they're fairly different than, oh, this is what I know from web servers.
So the big thing to do with, if you're getting into ZerumQ is to read the, there's something called the guide.
And there'll be a link in the notes.
And if you go to zerumq.org, it'll be prominently linked.
And this goes through kind of the different patterns that ZerumQ thinks about,
the abstractions in ZerumQ, and the different socket types and what they're for.
And the guide will help you.
And there are examples in many languages, including Python.
Yeah, this seems great.
Yeah.
It'll help you build kind of little toy example patterns of here's a publish subscribe application.
Here's a ventilator sync application.
And then it also does things with pictures.
Yes.
That's, I think, the way to internalize what are the ZerumQ concepts and how do I deal with this.
So when it comes to serialization, this is really important for iPython Parallel.
And it also comes up if you're in Jupyter and use the interactive widgets,
if you use the really intense ones that do like 3D visualization,
interactive 3D visualization in the browser that sometimes are streaming a lot of data from the kernel.
Because a ZerumQ, this combines two things, one from PyZMQ and one from ZerumQ itself.
So the ZerumQ concept that a message is actually a collection of frames.
This lets you and another that ZerumQ can be zero copy and PyZMQ supports zero copy.
So anything that supports the Python buffer interface can be sent without copying,
meaning it's still copied over the network, but at no point are there any copies in memory.
So you can send 100 megabyte NumPy array with ZerumQ without copying it.
But then you've got to think about, oh, wait, if I send a NumPy array using the Python buffer interface,
all I got were the bytes.
Where are, you know, where's the D type information?
Like, how do I know this is a 2D array of integers?
Because a message is in Python language is a list of chunk of blobs instead of a single blob.
You can serialize that metadata as like a header and the blob you don't want to copy, the big one, separately.
So you can say like, Jason dumps the, some message metadata that tells you how to interpret the binary blob and then just the binary blob and you don't copy it.
So then you can send as one message, right?
We're not breaking the single message delivery.
You have your metadata that's serialized with message pack.
That comes in as like a frame or something like that in the message.
Yeah.
So one frame is your header.
One frame is the data itself.
And we do this in the Jupyter protocol that the Jupyter protocol has an arbitrary number of buffers on the end.
But then there are three frames that are actually Jason serialized dictionaries.
Very cool.
Yeah.
Looking at the guide here, it says there's 60 diagrams with 750 examples in 28 languages.
That's a big cross product matrix of options in here.
And you can also download it as a PDF to take with you, which, yeah, this looks like a really great place to get started.
Speaking of getting started, let's talk about programming with the Python aspect here.
All right.
So here we'll use PyZMQ and this is a library that you work on as well.
Yeah.
I maintain PyZMQ.
Yeah.
Awesome.
So maybe, you know, it's hard to talk about code, but just give us a sense of what it's like to create a server.
Like in Flask, you know, I say app equals Flask.
Then I decorate app.route on a function.
Like what's the 0MQ Python equivalent of that?
Yeah.
So first you always have to create a context and then use that context as a socket method that creates sockets.
And then you either bind or connect those sockets.
And then you start sending and receiving messages.
So if you're writing a server, which usually means this is the one that binds.
Yeah.
So you'd create a socket.
You'd call socket.bind and give it a URL, maybe a TCP URL or an IPC URL with a path, you know, a local path.
And then you'd go into a loop saying, you know, receive a message, handle that message, send a reply.
Or if it's a publisher.
They often have a while true loop sort of thing, right?
Just while true, wait for somebody to talk to me or while not exit.
Yeah.
That's a simple version.
Or you could be integrated into asyncio or tornado or G event or whatever.
Yeah.
One of the fundamental principles of 0MQ is that it's async all over the place.
What's the async and await story with PyZMQ?
Is there any integration there?
Yeah.
So if you do import zmq.asyncio instead of, if you do import zmq.asyncio as ZMQ, you will have the same thing, but send and receive are awaitable instead.
Oh, that's glorious.
Yeah.
That's really, really fantastic.
So you should be able to scale that to handling lots of concurrent exchanges.
Pretty straightforward, right?
Yeah.
And that's how the, so taking Jupyter as an example again.
So the Jupyter notebook uses the tornado framework, which if you're getting into asyncio, tornado is basically asyncio.
Right.
Yeah.
It's the early days, early take asyncio.
Yeah.
And there we use something called a ZMQ stream, which is a, something inspired by tornadoes IO stream, which is their wrapper around a regular socket.
That's like bytes are coming in, call events when bytes have arrived.
ZMQ stream is a tornado thing that says when you have an on receive method that passes a callback, it says whenever there's a message, call this callback with the, with the message after receiving it.
Yeah.
And so that's actually how the IPython kernel and Jupyter notebook work on the ZMQ side is with these ZMQ stream objects.
Cool.
So the example you talked about is how to create a server, but you know, web version would be use request to do a request.get against the server to be the client that talks to it.
What's the, that version in PysmQ?
In PysmQ, a client looks very much like a server, except instead of bind, you'd call connect.
And instead of receive, you do a send.
Yeah.
And so in a request reply pattern.
Yeah.
So wherever you have a receive on the server side, you have a send on the client side and vice versa.
So in a request reply pattern, the client is doing send a request and then receive to get the reply.
In a server, you're doing receive a request and send the reply.
In PubSub, you're just, you're only sending on the publisher side and on the subscriber side, you're only receiving.
Nice.
And when the way you set this, you basically choose these things is when you go to the context and you create the socket, you tell it what kind of pattern you're looking for.
Is that where you specify that?
Yeah.
So ZMQ has a bunch of constants that identify socket types.
So you'd use, when you create a socket, you always have to give it a single argument that is the socket type.
So it'd be like ZMQ.pub for a publisher socket, ZMQ.sub for a subscriber socket, router, dealer, push, pull.
Yeah.
And that defines the messaging pattern of the underlying sockets.
You also have some JupyterLab examples, which I guess we can link to as well, like some diagrams for that, right?
Yeah.
So the Jupyter protocol has a diagram of, a diagram that we maybe should redesign.
Whoops.
I didn't mean that far.
There we go.
It shows you what, basically what happens when you have one kernel and multiple front ends connected to it with the different socket types that we have in the Jupyter protocol.
So the Jupyter kernel has two router sockets and a pub socket and a fully featured front end would have two dealer sockets or request sockets and a sub socket.
It's just so much is happening below the scenes.
I think getting your mind around these is really neat, but basically ZMQ is handling so much of this for everyone, right?
It's handling all the, so we never care about, there's multiple peers connected.
We don't need to deal with that.
We have no connection events.
Some folks working on different issues that causes headaches for them because there's some aspects of ZMQ, like the not guaranteed pub sub delivery.
It's actually kind of a pain because we actually want all the messages.
Yeah, yeah, of course.
Is there any, you know, around a lot of libraries, there's stuff that like adds layers that does stuff.
So like Flast extensions and stuff like that.
Is there an extension that will like let you do reliable messaging that you can plug in on top of this or anything like that?
Yeah.
So if you look at the ZMQ guide, there are different patterns, some of which are basic uses of sockets.
So there's no reason to build a, another layer of software on, in order to implement that, a simple ventilator pattern.
But if you're talking about things like reliable messaging, there are some patterns in the guide and they, they have names.
And so some people have written those protocols as standalone Python packages that say like implement this scheme on top of ZMQ that might have things like a message replay and, or, you know, election stuff.
Like if you do Kubernetes things, they're often leader elections to allow you to scale and migrate things.
So you can do that with a ZMQ applications.
Yeah.
And some of those reliable messaging things sound amazing.
And I go, yeah, that's going to be great.
But there's other drawbacks to those as well.
Like poison messages.
Like I got to make sure I send this, but the server, the client can't receive it.
So they crash.
So then I try to send it again.
And you're just in these like weird loops and there's a lot of, they all have their challenges.
Yeah.
So another thing that I think would be interesting to touch on for our conversation, which we've spent so much time talking about all the programming patterns and stuff that I don't know.
We have as much time anyway as we imagined, but building PyZMQ for basically to wrap this C library, right?
This is some challenges you've had.
It supports both CPython and PyPI.
Sorry, PyPI.
PyPI.
Yeah.
And whatnot.
So maybe talk about some of the ways you did that.
You had to do this like in the early days when there was Python 2 and 3.
There's a lot of stuff going on, maybe pre-wheels, right?
Yeah.
So a few years pre-wheels.
So with IPython and Jupyter, our target audience is pretty wide, right?
We have a lot of people in education, a lot of students, a lot of people on Windows.
A lot of those people don't even want to be programmers or care about like.
Exactly.
They just want this to work.
And why won't this thing install?
I just need to do this for my class or for my project.
It needs to work, right?
That kind of thing.
Yeah.
And so having a compiled dependency was a big deal for a lot of people.
And so making binary releases as widely installable as possible was really important to us.
And supporting as many Python implementations as possible was also important to us.
So PyzianQ was originally written all in Cython, which is a wonderful library for this when you're interfacing with the C library, especially when you want to do things with the buffer interface.
So when you have a C object and a Python object that represent the same memory, Cython is the best.
And that's a lot of what we do for the zero copy stuff in PyzianQ.
So when we were working on this, wheels didn't exist.
Wheels being the binary version that you get from PyPI now.
Yeah.
So if you pip install something like PyzianQ, it doesn't compile it, right?
You get a wheel and that just unzips it and it's really nice.
But at the time there were only eggs.
And there was a period of time when pip was taking over from easy install, which was eventually wonderful.
But one of the drawbacks was during this time when pip was taking over, there was still no wheels.
And so people had started shifting to pip because easy install did a lot of things that people don't like.
But you had to use easy install if you wanted to get a binary, which means effectively, if you're on Windows, you had to use easy install.
And so we had a really complicated, there actually used to be a big delay, might even still be there, in PyzianQ.
No, this is definitely not.
So there was a big delay in PyzianQ that if you ran setup.py, it would sleep for 10 seconds and show you a big message that says you might want to easy install.
But now we're in a very different world, right?
So even after wheels, it was a couple of years before we had many Linux wheels, right?
There was a while before you were even allowed to make wheels for Linux.
And now we're at a place where we've got wheels for ARM Macs and ARM Linux and a bunch of different Linux versions and Windows and everything.
And it's a really different world.
And a lot of things would be different if we were starting this project now.
It would be easier if you started now, probably, right?
Yeah, it would be a lot easier.
We were getting in on some early stuff.
But one of the wonderful things about Cython is if you're writing Cython, you're really writing C, right?
If you're writing Cython code, it's generating a C program that calls the Python C API.
Right.
You write Python with a little typing stuff.
It turns that into C and then compiles that to machine instructions, right?
Like you're basically projecting C somehow.
Yeah.
You're basically writing C that looks like Python.
The nice thing about C is that with directives and things, you can have one file that actually contains 10 different files.
Because you could just turn off lines when it's compiling.
And that means that it's much easier to write Cython code that supported Python.
At the time, we were supporting Python 2.5 through 3.1.
And with a single code base, we had no 2 to 3, none of that single code base in 2010 supporting Python 2.5 and Python 3.1 and everything in between.
That's quite the accomplishment.
And the tricks were all dealing with PyZMQ.
We were early adopters a lot of the Python concepts, Python 3 concepts of we talk bytes.
We don't talk str.
Right.
Disambiguate.
We use bytes and Unicode everywhere.
We never use the word str.
Yeah.
There's not necessarily that many differences between Python 2 and 3.
Yeah.
But you could make them different, right?
But in a lot of the more modern Python 2, you could still be much closer to what eventually became Python 3, yeah?
So I would say it was either 3.3 or 3.4.
It became the norm to have single code base, single syntax, support 2.7.
So drop support for 2.6, support 2.7.
And I think 3.3 or above, or 3.4 and above, whenever they let you back in.
Because then it became easier.
What's the story with Python 2 now?
Does it still support it?
Just as of December, right?
So Python 2 end of life was last December.
Right.
PyZMQ dropped support.
The latest release requires Python 3.6, actually.
Nice.
Yeah.
I feel like a lot of people are going to 3.6.
Why did you guys choose 3.6?
Is it f-strings or was it something else?
It was actually the typing.
All right.
One of the main complaints about PyZMQ is it's so auto-generated and dynamically defined,
right?
Because we didn't just target multiple versions of Python.
There's also multiple versions of LibZMQ.
And that means that PyZMQ, what constants are defined is different depending on what version
of LibZMQ is linked, which meant that a lot of the way it's written, a lot of static analysis
fails.
So when you're auto-completing based on static analysis, all the constants don't show up.
And so that was an occasional focus.
My auto-complete in PyCharm is not working.
That's why I added the types was to allow static.
Oh, yeah.
That's fantastic.
Does it do anything with what TypeShed does, where it's defining the structure in these stub
files?
So there's some type annotations in the pure Python code, but the relevant part was the stub
files for the compiled part.
Yeah.
And if people haven't seen those stub files, those PYI files, it's a little like a C++ header
thing where it has the definition, but then somewhere else is the implementation.
of it.
It's a little funky, but yeah, it's also useful for adding that in, right?
So you could say, here's the structure, and we'll make that at runtime dynamically, but
this is what you should think of it as, right?
Yeah.
Very cool.
All right, Min, well, you know, I think we just honestly just scratched the surface.
We could go on and on and on.
But at the same time, I want to be respectful of your time.
So maybe we should wrap it up on the main topic there.
So I'll ask you the two questions on the way out.
If you're going to write some code, if you're going to work on PYZMQ or something like that,
what editor would you use?
My favorite editor of all time is TextMate.
Okay.
But for various reasons, I don't use that anymore.
It kind of, it hasn't kept up with activity and things, and I didn't feel sustainable anyway.
So I haven't used it in a long time.
I've tried pretty much everything.
And I'm in a, I'm in constant search of the next TextMate.
So right now I'm actually using Nova, the new editor from Panic.
I was going to say, maybe Nova is your next TextMate.
How about that?
Like, I know if I say Visual Studio Code or PyCharm, people are like, oh yeah, I'm pretty sure I know what that means.
Nova is pretty new.
Tell folks about it.
So Nova is a new text editor from Panic, one of the great Mac developers.
Oh yeah, I use some of their apps, like Transmit for working with S3 files.
They're really nice stuff, yeah.
Yeah, so they do a great job designing things.
And thanks to the recent work of the language server protocol and stuff, new editors are able to, there's a lot more shared infrastructure in editor features.
So it's starting further from zero than it might otherwise be.
But I'm not sure I could recommend it widely to Python developers.
It's a bit of an early adopter situation.
It is supposed to have Python support, but it's not specifically for Python, right?
Yeah, no, it's a general purpose editor.
And the target community is more in Mac developers, Ruby and web stuff.
Yeah, I feel like it's pretty JavaScript friendly.
Yeah.
And maybe Mac developers as well, yeah.
Yeah, and extensions are written in JavaScript.
I've written a couple extensions to use the darker code formatter.
Very cool.
All right, well, that's quite neat.
And I'm glad to hear that's working out for you.
I've wanted to try it, but I just haven't.
And then notable PyPI packages.
I know you picked two that have some relation back to this challenge of building binary stuff and distributing it.
Yeah.
So up until December, there was exactly one computer in the world that could build PyZMQ releases.
And now it's my laptop.
And I finally solved that problem thanks to two wonderful packages.
One is CI build wheel, which is a more generally useful.
If you have compiled Python packages, CI build wheel is a wonderful thing for building and distributing all your wheels on all kinds of platforms.
So now PyZMQ wheels are all built on GitHub actions, and I don't need to do anything other than tag a release.
And it all happens magically.
Nice.
The other one that I wanted to highlight that probably fewer people know about that's related to CI build wheel, because when you build a Python package that has an external dependency, there's an extra step to say, I built PyZMQ.
I linked it against LibZMQ over here.
But if somebody else installs that wheel as it is, it's not going to work because it's going to say, like, I don't have LibZMQ.
So for a long time, there's a Mac thing called Delocate and a Linux one called Audit Wheel that say, look at the binaries in there and find them on your system, bring them in and update the linking to make sure they load.
So the wonderful thing that I just found is someone created something called Delve Wheel, which is Audit Wheel or Delocate, but for Windows.
And I don't understand anything about Windows.
You go grab the DLLs and all that kind of stuff that have to be there and, you know, put it in the right location.
Perfect.
Yeah.
And so for a long, long time, for, yeah, I guess 10 years plus, PyZMQ built LibZMQ on Windows as an extension.
So it actually took the XerunQ sources and then just said, hey, I have a Python, this is a Python extension.
Don't, you don't need to worry about the fact that it's actually a C++ library.
Just pass it to disutils and compile it as an extension.
And there were a lot of issues with that.
You never got up good optimized results, but it worked most of the time.
And that was the point.
And it was a wonderful contribution from Brandon Rhodes that was a huge step in making PyZMQ installable.
A lot more of the time was this bundling LibZMQ as an extension.
But thankfully, finally got to the point where I almost never do that anymore.
Nice.
That's beautiful.
Thanks to Delve Wheel.
So Delve Wheel is my big one.
Yeah.
The CI Build Wheel one.
It makes me happy to see the macOS Apple Silicon.
Got a little checkbox there.
That's what I'm doing or recording from on my machine over here.
Got the Mac Mini one.
And man, that is a sweet device.
But you're a little bit back in like, oh, we don't have wheels for your system.
Sorry.
Yeah, I have.
So I have two Mac Silicon wheels.
One that I just finished last week with CI Build Wheel.
That's a universal wheel.
And then I have another like Mac ARM wheel targeting macOS 11 plus basically just for Homebrew Python
on Homebrew Python 3.9 on ARM Macs.
That's pretty specific.
But yes, that's a thing I have actually.
It's not an insignificant target.
And that one I build on my wife's new laptop.
Very cool.
So that's the only one that's not built on CI yet.
Right.
Awesome.
Well, thank you so much for sharing all the stuff.
And of course, all your work.
I feel like I have a lot to go learn, but it's exciting stuff to be able to think about
new networking ways of doing stuff with networking and Python.
So thanks so much for that.
And, you know, final call to action.
People want to get started with this stuff.
What would you tell them to do?
I'd say read the guide.
So if you're interested in ZeroMQ, think about building distributed applications and things,
read the ZeroMQ guide, the whole thing.
And I think it'll give you some new ideas.
Even if you don't use ZeroMQ, it'll give you some new, good new ideas for how to think
about this kind of application.
Right.
Like these design patterns that maybe are not so common, but like PubSub or whatever.
Yeah.
Awesome.
Well, thanks again for being on the show.
It was great to chat with you.
Yeah.
Thanks so much.
You bet.
Bye.
This has been another episode of Talk Python to Me.
Our guest on this episode was Min Reagan Kelly.
And it's been brought to you by Linode and Mido.
Simplify your infrastructure and cut your cloud bills in half with Linode's Linux virtual machines.
Develop, deploy, and scale your modern applications faster and easier.
Visit talkpython.fm/Linode and click the create free account button to get started.
Do you feel like you're stumbling around trying to work with pandas within your Jupyter notebooks?
What if you could work with data frames visually just like they were an Excel spreadsheet, but
have it write the Python code for you?
With Mido, you can.
Check them out at talkpython.fm/Mido.
M-I-T-O.
Want to level up your Python?
We have one of the largest catalogs of Python video courses over at Talk Python.
Our content ranges from true beginners to deeply advanced topics like memory and async.
And best of all, there's not a subscription in sight.
Check it out for yourself at training.talkpython.fm.
Be sure to subscribe to the show, open your favorite podcast app, and search for Python.
We should be right at the top.
You can also find the iTunes feed at /itunes, the Google Play feed at /play,
and the direct RSS feed at /rss on talkpython.fm.
We're live streaming most of our recordings these days.
If you want to be part of the show and have your comments featured on the air,
be sure to subscribe to our YouTube channel at talkpython.fm/youtube.
This is your host, Michael Kennedy.
Thanks so much for listening.
I really appreciate it.
Now get out there and write some Python code.
I'll see you next time.
Thank you.