#539: Catching up with the Python Typing Council - podcast episode cover

#539: Catching up with the Python Typing Council

Mar 06, 20261 hr 2 minEp. 539
--:--
--:--
Download Metacast podcast app
Listen to this episode in Metacast mobile app
Don't just listen to podcasts. Learn from them with transcripts, summaries, and chapters for every episode. Skim, search, and bookmark insights. Learn more

Episode description

You're adding type hints to your Python code, your editor is happy, autocomplete is working great. But then you switch tools and suddenly there are red squiggles everywhere. Who decides what a float annotation actually means? Or whether passing None where an int is expected should be an error? It turns out there's a five-person council dedicated to exactly these questions -- and two brand-new Rust-based type checkers are raising the bar. On this episode, I sit down with three members of the Python Typing Council -- Jelle Zijlstra, Rebecca Chen, and Carl Meyer -- to learn how the type system is governed, where the spec and the type checkers agree and disagree, and get the council's official advice on how much typing is just enough.

Episode sponsors

Sentry Error Monitoring, Code talkpython26
Agentic AI Course
Talk Python Courses

Links from the show Guests
Carl Meyer: github.com
Jelle Zijlstra: jellezijlstra.github.io
Rebecca Chen: github.com

Typing Council: github.com
typing.python.org: typing.python.org
details here: github.com
ty: docs.astral.sh
pyrefly: pyrefly.org
conformance test suite project: github.com
typeshed: github.com
Stub files: mypy.readthedocs.io
Pydantic: pydantic.dev
Beartype: github.com
TOAD AI: github.com
PEP 747 – Annotating Type Forms: peps.python.org
PEP 724 – Stricter Type Guards: peps.python.org
Python Typing Repo (PRs and Issues): github.com

Watch this episode on YouTube: youtube.com
Episode #539 deep-dive: talkpython.fm/539
Episode transcripts: talkpython.fm

Theme Song: Developer Rap
🥁 Served in a Flask 🎸: talkpython.fm/flasksong

---== Don't be a stranger ==---
YouTube: youtube.com/@talkpython

Bluesky: @talkpython.fm
Mastodon: @talkpython@fosstodon.org
X.com: @talkpython

Michael on Bluesky: @mkennedy.codes
Michael on Mastodon: @mkennedy@fosstodon.org
Michael on X.com: @mkennedy

Transcript

You're adding type-ins to your Python code. Your editor is happy, autocomplete is working great, but then you switch tools and suddenly there are red squigglies everywhere. Who decides what a float annotation actually means or whether passing none where an int is expected should be an error? It turns out there's a five-person council dedicated to exactly these questions and two brand new Rust-based type checkers are raising the bar as well.

On this episode, I sit down with three of the members of the Python Typing Council, Yela Zylstra, Rebecca Chen, and Carl Meyer to learn about how the type system is governed, where the spec and type checkers agree and disagree, and I get the council's official advice on how much typing is just enough. This is Talk Python To Me, episode 539, recorded January 27th, 2026. Talk Python To Me, yeah, we ready to roll. Upgrading the code, no fear of getting old.

Async in the air, new frameworks in sight, geeky rap on deck. Quart crew, it's time to unite. We started in Pyramid, cruising old school lanes, had that stable base, yeah, sir. Welcome to Talk Python To Me, the number one Python podcast for developers and data scientists. This is your host, Michael Kennedy. I'm a PSF fellow who's been coding for over 25 years. Let's connect on social media. You'll find me and Talk Python on Mastodon, Bluesky, and X. The social links are all in your show notes.

You can find over 10 years of past episodes at talkpython.fm. And if you want to be part of the show, you can join our recording live streams. That's right, we live stream the raw, uncut version of each episode on YouTube. Just visit talkpython.fm/youtube to see the schedule of upcoming events. Be sure to subscribe there and press the bell so you'll get notified anytime we're recording. This episode is brought to you by Sentry. Don't let those errors go unnoticed.

Use Sentry like we do here at Talk Python. Sign up at talkpython.fm/sentry. And it's brought to you by our Agentic AI programming for Python course. Learn to work with AI that actually understands your code base and build real features. Visit talkpython.fm/agentic-ai. Della, Rebecca, and Carl, welcome to all of you type-loving Pythonistas. Awesome to have you here on the show. Thanks for being here.

We're going to talk Python typing, especially from the perspective of the Python Typing Council, which honestly, I am a huge fan of Python typing. It's still something I learned about not too long ago. So I'm going to be learning along with everyone else, what it is you all do and so on. So I'm really excited to be diving into this.

I think since types came to Python, I think it's made it a little bit more rigorous, you know, for all those people out there like, oh, it's not a real language without any form of static typing. We can't use it on real projects. I don't know how true that was, but certainly it's less true now. You know, you can pick per project. So it's super cool. Before we get into all that, though, let's just go around for a quick introductions. Jala, welcome to the show. Awesome to have you here.

Who are you? Hi, yeah. Jala, I've been on the Python Typing Council since the beginning. I helped set it up a couple of years ago. Outside of the typing work, I currently work at OpenAI, where I work on developer productivity, which means things like running CI for people and helping, generally helping people be productive. I've been working with Python for more than a decade. Started out because my previous job was mostly in Python and then got more and more involved with the language.

So let me get this right. At OpenAI, you're basically helping developers there have better developer tooling and common packages and workflows and stuff like that. Is that kind of the story? That's right. Mostly around things that happen in CI, like running tests efficiently, figuring out the right tests to run, getting the right CI workers out. That sounds very exciting. Right in the epicenter of all the big tech stuff these days. Super cool. Rebecca, hello. Welcome. Hey, thanks for having me.

I'm Rebecca. I've been on the Typing Council also for about three years, I think, since the, less than three, since the beginning. But my day job, I work at Meta on Python typing, gone Pyrefly, which is a new type checker and language server written in Rust, still in beta. Prior to that, I was at Google for eight years, also on the Python team. I just, I really like Python. Yeah, super neat. I'm a big fan of both Pyrefly and ty, which will both have representatives here, I know.

And I think it's just a super exciting time for Python types. And certainly that's one of the reasons. So very cool. Carl, welcome back. Thank you. Great to be here. Yeah, Carl Meyer. I currently work at Astral, where I work on ty, which is a Python type checker and language server written in Rust, also in beta. And yeah, I guess, how did I get into typing? Or I've been on the Typing Council, not since the beginning. I think it's been a year and a half.

And yeah, I got into Python typing at the time in 2016, 2017. I was working at Instagram. And that was in the very early days of Python typing. The PEP44, PEP43, the early Python typing PEPs had recently come out within the last couple of years. And one of the co-authors of some of those PEPs, Lukash Lange, was actually sitting at a desk right next to me at the time. And at some point, we started to think that we should try this Python typing stuff on the Instagram server monolith.

And so I took that on as a side project. And then it eventually became the main project. And then it took like three years. So a lot of Python typing experience there. There absolutely is. You know, I think a couple of things I'd like to touch on there. First of all, Instagram, is it maybe the biggest Django deployment in the world? It's certainly one of the bigger ones, right? And I think a lot of people don't necessarily know that a core chunk of Instagram is actually Python, right?

I mean, I don't know if we have any way to know how big the Django deployments in the wild might be. But it's certainly a big one. Yeah, it's definitely a big one. There were some talks about dismissing the garbage collector from the Instagram folks. That wasn't you giving the talk, but at PyCon. So that was pretty interesting. But I think actually that work that you're talking about, especially with Lukash, really kind of opened a lot of people's eyes about Python typing, right?

He gave a couple of PyCon talks, showed, you know, real metrics of how much of the code base is typed, how much it's changed, like error detection, that kind of stuff. So let me ask you, do you feel like it would be different? Would it have gone different now if tools like TY and Pyrefly existed back then? Is Python typing different now than it was then? Certainly, yes. I mean, there's been, the type system has gotten more complex over time. So it is both more expressive and more complex.

And yeah, we have more type checkers available now. I do agree that it's more complicated, and I don't know how to feel about that. It is more expressive, but I feel like it's starting to get, I mean, we're not at C++ ATL, like templates of templates of templates, but still, it's getting more serious. But I guess one of the really nice parts is that you can just take as much as you want of the complexity, and you can just leave the rest, right?

That's part of the magic of Python typing, is that it's a gradual typing system. That's a choice people get to make. It can be none, it can be quite a bit, and anywhere in between. So I guess that's probably one of the decisions. Let's talk about the typing council. So when did the typing council come along, and did the typing council exist to create all of these PEPs and make this happen, or was it afterwards? Like, what's the history of the typing council and its purpose, folks? We'll run it.

Yeah, it postdates most of the PEPs. So initially, the text system was created just through the regular PEP process. It means that something gets submitted, first still to Guido as the BDFL, later to the steering council. Meant that it's very hard to make changes to, like, this specification.

Like, anytime you want to make change something about how the type systems would work, we had to go through this PEP procedure, talk to the steering council, who are very busy people, who deal with a lot of other aspects of the language other than typing. So Shantanu and I came up with this idea of creating a separate council to specifically in charge of typing, that would be in a specification where we can make small changes ourselves without having to go through this whole PEP process.

And this way, when all the type checkers agreed that something needs to go a certain way and it's not exactly what's in the PEPs, we can change it and have a place to record that and people can refer to it and new type checkers can also try to follow those decisions. Very interesting. I didn't realize that it was sort of, was there to allow for small changes to be made to make that much easier. But of course that makes sense because the PEP process is, it's pretty serious and drawn out.

And we've seen even small language changes have quite passionate folks, I guess we should say. So yeah, yeah, very nice. Do you have any examples of the types of changes that y'all have, that have happened over the years that maybe were typing council only?

One was the specification where how overloads work, which is perhaps not really a small change, but one of the most complicated features in the type system really is the overloads, where you can give multiple signatures for a function and type checkers sort of select which one to use based on the arguments when the function is called. And when it was initially created, from what I recall, there just wasn't really a specification. It's just like you use the signatures in a way that makes sense.

And Eric Trout, who's currently on the council, came up with a pretty specific procedure for exactly how overload should work to make it so that type checkers have, well, sort of users can understand how it works and sort of type checkers can have something to work towards to make sure that they work infinite overloads in the same way. Maybe a smaller example that is an example of something that would have been too small for a pep and hard to accomplish before the typing council existed.

And this is actually a change that I pushed through before the, before I was on the typing council, but the typing council approved it, was a clarification around the interpretation of data class fields. If a final annotation is applied to a data class field, does that mean, so if you apply a final annotation to a regular class attribute, since it can't be changed, that implies that it's a class variable.

And there was a question of if that should be the interpretation with the data class or not. So we discussed that and made a clarification to the spec. I've never really thought about final being applied to a class field, but I've always used them sort of just for constants. But, you know, maybe people out there don't know, like typing dot final bracket type, right? That's kind of the way you can do constants in Python, right? Constants for the type checker.

Nothing in the runtime will stop you from editing it. That's... Not there. Not there. I have some examples coming up and I'm interested to hear your thoughts on it, but for sure it's, there is this tension, right? I mean, I think that's probably worth touching on as well is this is a tension for Python in general is you can write all the types you want and then when you run your code, it just doesn't care.

There's a few instances, Pydantic, FastAPI, a few others, but generally speaking, it's there for the editors and the type checkers and the linters and not for runtime, right? Yeah, that's right. There's many exceptions to that. There's a product like mypyC, which comes with mypy that's used those types to compile your code into more efficient machine codes. Maybe there's going to be more products like that in the future. I don't know. But yes, in general, it's separate from the runtime.

Sort of a similar model to TypeScript where TypeScript gets compiled into JavaScript and types just go away. Here, we don't do a compilation step, but still the same idea of the types just not influencing the runtime. Although we do make them available for introspection via done annotations attributes, which is what has enabled the projects like Pydantic and other sort of runtime checkers to make use of type annotations at runtime also.

Yeah, I don't know if the typing council was around for this, but there was proposed, I don't remember the exact details, but something to the effect of for type checking, not actually doing some of the full imports or something along those lines, right, where the runtime behavior would have made it hard for tools like Pydantic and others to get that. And there was some kind of compromise, right? I don't remember the details here. Anyone does?

Yeah, what happened was that there was going to be a change. That's what the from future import annotations import does, that changes all annotations into raw strings. So the default behavior before recently was that annotations that are regular codes.

If you write devf return to ints and you import the module, it just looks up the name ints and puts that in an annotations dictionary, which makes introspection easy, but it made a test on costs on performance because memory usage sometimes was high and also made things harder to use sometimes because if you use a name that's not defined yet at runtime, you get an error.

That often comes up if you have like a class that has a reference in an adaptation to the class itself or circular dependency classes. Right. The circular imports because you want to say this class is created by that thing and it returns one, but you know, somehow you've got to import the other one and that's such a hassle. Yeah, it's, yeah, even out in the audience we have, Tom says, circular imports. Oh, yeah, for sure. What about lazy imports?

Like that just recently got accepted and will be in 3.15. Which I'm super excited about because I think it'll make app startup a lot faster for many use cases. But does that have knock-on effects for typing? Not that directly because I think for a type checker lazy imports mostly just look like regular imports. I guess I should maybe leave that for the people who are actually working on type checkers and being written right now. Yeah, Rebecca, do you see this making any difference for you?

Lazy imports? To be honest, it's not something we've looked at too carefully yet. 3.15 seems a little more in the future, but I don't think it's likely to make a huge difference. Carl? I've thought about it briefly and I think that it, I think the type checkers really won't need to care. Maybe there will be some edge cases that will come up that I haven't thought of, but it shouldn't be a big deal. Yeah, that's what I thought as well.

The one variation that I can certainly see is if you have a, if you have something specified in a type, like say for a field of a class or a Pydantic model or something that would otherwise not trigger the lazy import to become imported, would potentially having types specify cause more importing to happen sooner in the runtime? Yeah, there's actually an issue related to this that I think we may need to resolve before 3.15, but I don't know how yet.

If you use a type in a data class annotation that's lazy imported, actually creating a data class will delay by the import. It will try to resolve the import and actually make it not lazy. This is because data classes doesn't really need to look at all of the annotations in your class, but it looks at them enough to trigger reification of the import. I shared this with some of the people on the lazy imports team, but we haven't yet come up with a good way around it.

I think this might end up being a bit of a food gun, so I feel like we should ideally find a workaround, but I don't know what it would be yet. I don't know that it's wrong that it converts it to an eager import, which it needs to know what it is potentially, right? It actually doesn't. Data classes just need to know whether it is classed for or not. I think that's pretty much all. I guess there's an init for also, but it doesn't really need to know anything else.

So in theory, it should be possible to just say, hey, it is not classed for, so don't bother importing it. Okay, so that's for data classes, but say if I specify a parameter type on a function. Yeah, then it should be fine. I guess, again, unless something is, if it does annotate, so if you have something like a decorator that looks at annotations in your function, it might reify those imports. There is one other potentially interesting thing for type checkers.

It's already difficult for type checkers to figure out when like a submodule should be considered to be an attribute of the parent module because the way this happens in Python is that any import of a submodule anywhere will attach that submodule as an attribute on the parent module, but that at runtime, that could literally happen anywhere. It could happen in totally unrelated code outside of the module and a type checker probably won't be able to see that.

So type checkers already have sort of complex sets of rules around where they look for these submodule imports and when they consider a submodule import to be reliably happening enough that it should, that the type checker should consider this submodule to exist as an attribute. And lazy imports may make that even, we'll add one more wrinkle to those sets of heuristics in that we'll have to decide if you have a lazy import of a submodule and you're done to init.py, it's lazy.

So should the type checker consider that submodule to be imported or not be imported? It'll be another case where there's no clear right answer and we'll just have to make a decision one way or the other. This portion of Talk Python is brought to you by Sentry. I've been using Sentry personally on almost every application and API that I've built for Talk Python and beyond over the last few years. They're a core building block for keeping my infrastructure solid. They should be for yours as well.

Here's why. Sentry doesn't just catch errors. It catches all the stuff that makes your app feel broken. The random slowdown, the freeze you can't reproduce, that bug that only shows up once real users hit it. And when something goes wrong, Sentry gives you the whole chain of events in one place. Errors, traces, replays, logs, dots connected. You can see what's led to the issue without digging through five different dashboards.

Sear, Sentry's AI debugging agents builds on this data, taking the full context, explaining why the issue happened, pointing to the code responsible, drafts a fix, and even flags if your PR is about to introduce a new problem. The workflow stays simple. Something breaks, Sentry alerts you, the dashboard shows you the full context, Sear helps you fix it and catch new issues before they ship. It's totally reasonable to go from an error occurred to fixed in production in just 10 minutes.

I truly appreciate the support that Sentry has given me to help solve my bugs and issues in my apps, especially those tricky ones that only appear in production. I know you will too if you try them out. So get started today with Sentry. Just visit talkpython.fm slash Sentry and get $100 in Sentry credits. Please use that link. It's in your podcast player show notes. If you're signing up some other way, you can use our code talkpython26, all one word, talkpython26, to get $100 in credits.

Thank you to Sentry for supporting the show. Yeah, there's some variations across type checkers, which we'll get to later. I think, though, before we move off this, there's actually off introducing the typing council. I think we should point out that there's two other folks who couldn't be here who are also on the typing council, Eric Trout and Yuka Letts, the docilo? Sorry, Yuka. But I want to make sure that we point out there's actually five people, not just the three of you, right?

How do you get on the council? Is there an election? Do you just apply? I think these are filled by the members themselves. So when somebody declares the intention to leave the council, we basically ask for people who are interested and then make a selection. Generally, we try to get people who have experience in the type system. We try to get a good cross representation of people working on different type checkers. We have Carl and Rebecca here who work on two type checkers, TY and Pyrefly.

Yuka works on Pyrites, which are two of the most lightly used type checkers. So we try to get representation of people working on those parts of the ecosystem. That's really cool that it's got a bias towards finding people actually doing the work. So let's talk about the specification project at typing.python.org. What is this here? I'll talk a bit about it. I guess it's a specification for how the type system used to work.

The way it started was that, Yela, you basically took all the typing peps and like stapled them together, right, to make like one long doc. And since then, we've been iterating on it, filling in parts that were missing like overload evaluation and making other changes as well. Yeah, it's tricky, right? Because traditionally, the typing system is kind of defined across a series of peps. And so what is the document that tells you how it works, right?

Yeah, that made it hard because often there's peps built on top of each other. So then in the extreme, you might see like one thing in one pep and then there's another pep that adds an aspect of it, another one that adds another aspect. And overall it makes it very hard to follow. One of the things I did recently was rewrite the typed dict another.

Ended up rewriting the whole thing to basically put all those features together in a coherent whole rather than just having them all copy-pasted one after the other. Okay, so if somebody really wants a good understanding of the Python typing system, they go to typing.python.org.

You know, one thing I think maybe is worth touching on, it's just kind of out of the blue a bit, but I think it's a really interesting aspect of the Python typing system is the, what is it called, the numerical tower or the number tower, where it's like, if I have a number, I could specify it as an int, or I could specify it as a float, and those kinds of things, but do you really need to say it's an int pipe float, or a union of int and float, if it could be either, right?

And the, what is it called? It's the numerical tower, right? Yeah, there are different towers too. In Python, there's also this thing called a numbers module that you have there, that's just basically ignored by the type system. It's been useful for some people, I feel like in general that module just hasn't worked out very well as being very useful.

I think the interesting aspect is that you know, that you can say it's a float, and that's basically equivalent to union of integer and float, and so on, right? I think the typing numbers in Python is pretty interesting. I think every type checker has a different interpretation of what a float annotation actually means. It's an area of some lack of clarity in the spec. Yeah, a lot of contentiousness.

If we could go back in time, I would, like, knowing what I know now, I probably advocate for things being done differently because, like, beginning, you know, like, there were multiple things, like, with similar flavor. Like, there was also one where you could give a parameter a non-none annotation and default it to none for convenience, and we've largely, like, moved away from stuff like that in favor of explicitness.

Yeah, what the current spec says is that basically if you have a function that takes a float, you're also allowed to pass an int. That's not really enough. It doesn't tell you how these things work in all cases, and we've had some attempts to try to come up with a way to specify that special case in a way that makes more sense, at least makes more sense to me. It's been very contentious. People have very strong opinions about this. I guess non-obvious is what I'd like to say, really, honestly.

So I'd like to get the official counsel's thoughts on this. When is typing too much typing? I made the joke about C++, ATL, if you've ever worked with that, it's like a template class where templated classes are part of the concrete type of the template. It's just off the hook. There's certainly places where typing can be too much, and a lot of the purity of Python or the readability of Python is the fact that it's got so few symbols.

And so adding types adds context, but it also makes it a little harder to read. When is too much typing? When do you recommend typing? Rebecca, I'll let you go first, but what are your thoughts on how much typing should I use in Python? I'll give you what is my official stance, which is that if you want your type checker to work well, you should type annotate your API boundaries.

So parameters and turns in public functions, public class attributes, things like that, and even things that seem true trivial, like, oh, this function returns none, better to annotate it because, you know, someone else might be depending on your library and consuming that type of information. I will say personally, what I tend to do is I annotate things that I think are non-trivial because I want to see that as documentation.

And if something, you know, a function that does return none, to be honest, I will probably forget to annotate it half the time because I'll be like, I honestly don't need to see it. One of the interesting features of the Pyrefly VS Code extension, that's the only one I can speak of at the moment, and Carl, you've got to tell me if the TY one does this as well, is it will sort of overlay its belief of what types are.

Like, if there's, you say x equals a function return value and it knows what the function returns, it'll have a gray, like, colon int, if it returned an int or something. So you can kind of read the code and see what the types are without actually putting it into the text of the code. It's only within the editor. Does ty do something like that, Carl? Yes, we also have inlay type ins. Yeah, inlay type ins, that's what it's called.

So, yeah, I don't know, that also brings an interesting challenge, not a challenge, like a wrinkle to the recommendation of should I put types on, like, the return value because I want to know that's a list of user, not a list of user IDs or whatever, for example, like a list of UUID. But if, it's going to show up anyway in the editor, maybe I don't have to write that, right? And so that becomes sort of somewhere where you could debate again, I think.

However, I do 100% agree with you, Rebecca, that put it on your API boundaries. If, like, this is the place that people get into some part of your code and they don't know or want to know about the inside of it, having types there is really helpful both for editors, for type checkers, and just for reading code, and even for AI, which is a crazy world. Yeah. Carl? What are your thoughts here? How much typing is too much typing? What's the guidelines here? I think I agree with Rebecca's answer.

I mean, that one place you definitely want to have explicit type annotations is that API boundaries, the public API of a library, etc. In terms of what's too much typing, I mean, there are certainly patterns that have historically been used in Python that we still can't express well in the type system, or that require extremely complex type annotations to express well, and I think there it becomes a judgment call.

If it's like a core, widely used API, you may get a lot of benefit from some very complex and verbose annotations, and so then it's worth sort of going through that pain and the pain of adding them and of reading them in order to get that additional typing coverage everywhere you use that API. If it's much less frequently used code that's highly dynamic, maybe it's not worth it in that case. I think there's a lot of judgment calls here. What about like one-off scripts?

You know, I'm going to write this thing to just move this data from here to there, and once it's moved, I don't need it again. It's done with that old system, we're going to the new one. Maybe less typing. Yeah, I think that's what's useful for you. Often I feel like one-off scripts are not really one-off like maybe you want to move some similar data later, and then it's useful if you can understand your code again, if you want to read what you did.

You thought you didn't need it again, and all of a sudden at six months old, you don't understand it, and the types that help a lot, right? Yeah. Yellow, what's your advice? What Cara and Rebecca said makes sense to me too. I think types have advantages in terms of documenting humor readers, what is going on, and in terms of catching mistakes that otherwise would not be caught until runtime perhaps. They have costs in maybe making your code harder to read if there's too much going on.

So add types as long as those benefits outweigh the costs. Yeah. I mean, do you recommend to anyone that they just 100% go full like C++, C# on it, and just type it every single thing? Is there an advantage like for static type checkers, you know, like mypy type stuff you can run across and get that? I mean, you could do that with Powerfly or TY and the CLI as well, but you know, thinking more mypy is like kind of being real strict on some of that stuff.

Personally, I do tend to annotate almost all like function parameters and if class attributes, if I make a class, sometimes it's not as necessary, like you don't really need to annotate your tests perhaps, or you don't need to annotate internal functions as much, but for my own coding, I usually find it helpful to do that.

But sometimes I see people annotating even local variables where it's very obvious to type check if the type is and they can just infer it reliably, and then it really just adds noise and you shouldn't do it. Yeah, exactly. If you've got a function that's annotated with a return value and you say x equals the function call, then the type checkers can infer that and you're just causing extra noise, I guess. So suppose you all want to change something.

What's the process of actually going through and making some changes? Mostly sort of two levels of this. Well, maybe there's even three levels. The first one is if it's something that's so small that's just like a wording clarification or something, we just make a PR to the repo and a few of us look at it and we change it.

The second level is when it's sort of a smaller change that doesn't really introduce a new feature and then we make a PR to the typing spec repo and we formally have all of us sign off on it. That's what happens like what Carl mentioned earlier of the final change in data classes. It's had to merge this one, yeah, add Carl. I love it. This repo itself doesn't have anything. It's the Python typing repo where the decisions are made. The typing council just has like some documentation.

Yeah. And then the third level is peps, like really big new changes. You can still write a PEP and then we make a recommendation and the steering council makes a decision eventually. So if I wanted to suggest something, I could come up here and I could open up an issue, maybe start a conversation on typing, Python slash typing. And you can make a pull request to change the spec.

Okay. And so the pull request would not be to change the code, like how Python maybe interprets code that has this new thing, but to suggest that the spec has it, which then would start a process that ultimately might make CPython understand it, right? Well, CPython itself probably doesn't do anything with it. I guess most of the things that go directly here are changes to how to interpret things that are already in CPython. This portion of Talk Python To Me is brought to you by us.

I want to tell you about a course I put together that I'm really proud of, Agentic AI Programming for Python Developers. I know a lot of you have tried AI coding tools and come away thinking, well, this is more hassle than it's worth. And honestly, all the vibe coding hype isn't helping. It's a smokescreen that hides what these tools can actually do.

This course is about agentic engineering, applying real software engineering practices with AI that understands your entire code database, runs your tests, and builds complete features under your direction. I've used these techniques to ship real production code across Talk Python, Python Bytes, and completely new projects. I migrated an entire CSS framework on a production site with thousands of lines of HTML in a few hours, twice.

I shipped a new search feature with caching and async in under an hour. I built a complete CLI tool for Talk Python from scratch, tested, documented, and published to PyPI in an afternoon. Real projects, real production code, both Greenfield and legacy. No toy demos, no fluff. I'll show you the guardrails, the planning techniques, and the workflows that turn AI into a genuine engineering partner. Check it out at talkpython.fm slash agentic dash engineering.

That's talkpython.fm slash agentic dash engineering. The link is in your podcast player's show notes. If it's adding something new, it will usually need to go through a path, except if it's something very small. Let's talk about that for a minute. We got two representatives here of the newer breed of tools. What's the story for inconsistencies across interpretations of the spec? I know that there's slight variations.

I've also, you know, not putting either of you on the spotlight, but like using, say, PyCharm and like writing code. So it's type checkers happy and then using something like Pyright. And so it has a real different interpretation of what you should let slide and what you shouldn't. I feel like Pyright is more, much more focused on like enforcing the nullability and or the lack thereof. And it warns of inconsistencies there where PyCharm doesn't seem to care as much.

I don't know which one I like better, but I know they're different. And if I write code in one that I open the other, I'm like, huh, why is it upset? It seemed like it was fine. How do you all navigate this? Yeah. One thing useful to say about the spec there is that the spec covers a lot of things. In particular, it tends to cover sort of the details of more advanced type system features.

But there's a lot of very fundamental stuff about how a type checker works in terms of how it does inference and how it does type narrowing. And even in some cases, like you mentioned, you know, what it chooses to emit errors on that isn't really covered by the spec, partly maybe because we haven't gotten to it and also partly intentionally in that there may be room in some of those cases for different type checkers to work differently if they're serving different needs.

Like if PyCharm is primarily concerned about being a useful kind of IDE and providing go-to definition and that sort of thing, maybe emitting lots of warnings or errors and all kinds of things where your code might be doing something wrong isn't as high a priority. And another type checker might have a different priority. One thing I do want to mention is that it may not seem like it, but things are already much better than they used to be.

Like previously, I worked on a different type checker called PyType. And at that time, it was, you know, sort of the Wild West. Like we want to know how other type checkers like do something. Well, you know, like open up the mypy playground, open up the PyRite playground, see what tells you. Now we at least have spec and conformance tests. Yeah, that's really cool. How much would you say that your two type checkers maybe bring in mypy as well? Like how much do they agree versus disagree?

You know, like you only see the differences. You don't see in which ways that they are the same as a consumer of them so much, right? You're like, why is this one squiggly when it wasn't squiggly before? But how similar or different are they? I don't know how we would quantify that. I think there's a lot that is the same just because it's based on how Python actually works.

We're both trying to model the same language and then there's certainly also plenty of differences or things that we handle differently. So Rebecca, do you have a better way to quantify that? Yeah, I agree. it's hard to quantify, I suppose, talk a bit abstractly about various type checkers philosophies. With Pyrefly, we really try to do a lot of type inference. So that's a way in which we intentionally diverge a bit from mypy.

But other than that deliberate decision, if we see ways in which we are accidentally different, we do try to fix that because otherwise people would have a hard time running multiple type checkers or migrating. Yeah, differences obviously cause pain for users who are using multiple type checkers or writing libraries that need to support multiple type checkers.

So like Rebecca said, it's like if we are different from other type checkers, we want to be sure that there's a good reason for that difference. The difference should be because of philosophical choice, not just you happen to have chosen slightly differently, right? Yeah, and it's not just people who run different type checkers.

Like you pointed out, Carl, a lot of times it is if I have a library and then different people want to consume that library, then their type checker may or may not warn them about how my library declares its types and so on. I'll give you a real quick example. I have a, I can't remember which one it was, I have three or four different open source libraries that I've created that somehow work with creating, basically passing data to templates in web apps, right?

So one is like I want to use the Chameleon web template framework, but with fast API or with Flask or there's some other variations like partials and so on. I can't remember which one, but it doesn't really matter. One of them decorated a Flask. I think it was a Flask, especially makes it irrelevant. A Flask endpoint and PyRite was really upset. Like the error message filled the entire page of how it was inconsistent with what it expected for the definition of the Flask view method.

I'm like, no one is going to call this. Like what does it even matter what this type is? It still runs fine. The runtime is fine. You know, it's no problem with this decorator. It worked fine, but something about the way that the Flask at get returned the type versus what my thing returned varied in like a really slight way. I didn't care, but somebody was using some editor that used PyRite and they're like, you have to help fix this. I can't take all these warnings.

They're huge and they're everywhere. Like, okay, I'll go fix it. Right. And I went and I put way more effort than was justified into a function type that no one ever calls just to make the errors on some type checker I didn't use go away. Right. And that's the kind of thing where it becomes just a headache. I don't know. I wish I remember. I probably got that written down in an issue somebody filed, but it was, it was a gnarly error.

And, or if you're working on an open source project, you know, you can't make everybody use the same editor that wants to contribute on a big project. And so you might run into this variation as well. So there's a lot of cases. Yeah. It can be really difficult to make these decisions about what kind of, what sorts of errors people want their type checker to catch or what's too pedantic.

You want your type checker to catch non-obvious errors, not just the obvious ones that you probably would have seen by looking at the code yourself. But then there'll be cases where somebody says, well, I don't care. That's too pedantic. And it is difficult to make everyone happy. Who decides what the right signature of a flask view and point should be like if you the framework can call it. It should be okay. There's not.

Just because it had a decorator before, that doesn't mean that's the official structure. But anyway, I do think one of the bigger philosophical differences has to do around this concept of nullability. Do you guys call it nullability or none ability? Like nullability comes from the other languages. And by that, I mean, I can specify that I have an integer. And in the Python type system, it cannot be set to none, even though in the runtime it can.

It has to be a concrete int type unless you make it a optional int or an int pipe none or one of those type things, right? And how strong that gets enforced seems to be one of the biggest difference of opinions that I've seen around. Like, how do you all think about that?

That's interesting to me that that's your experience because my experience has been that that's actually an area where everyone seems to agree as far as I can tell that these are is an important source of bugs and it's better to catch them. So I think all of the type checkers, maybe you said PyCharm doesn't. I don't think PyCharm does that. I'm pretty sure it doesn't because I agree that it's an important thing to check, but it's also a point of a lot of friction.

And by that, I mean, let's suppose I'm going to have a class that I need to create an instance of and then put values into. And I know once I put the values into it, let's say it has a user ID, I know for certain that that's going to be an integer, right? So I'd like to say user ID colon int because everywhere I use that object later, if it's a function that takes an int and I specify it as optional int, I will get a type check warning every single call site when I try to pass that.

But I know from the semantics of the behavior that it's going to always be an int unless it's not initialized, right? And like in this short period where I want to create it. So I can't set the type to int. I have to set the optional int until I've loaded it. And, but there's like this, I don't know, that's, that's the part where I see a lot of it show up is inconsistencies and then warnings all over the place.

So I'm like, well, but that function is actually checking if it's none and it'll return null, you know, none or something like that. So I totally agree with you. It's just somewhere I've seen the most inconsistencies across maybe PyCharm versus others. mypy also has a legacy mode for not checking none things called non-strict optional. We're trying to get rid of that from mypy because yeah, strict optional, like being strict about it is the more sensible thing to do.

But it's possible that you've seen that too. Yeah, I agree. So what you mentioned is maybe sort of a special case of the case where you pass something to a class and there's initialization that changes the types. Doesn't necessarily have to deal with none. It could also just be like the attribute doesn't exist at all beforehand or something. Yeah, we don't have a good solution for that. Maybe there's room for something to support that use case better. I don't know what it would look like.

In some cases, there's ways you can, these things can sometimes nudge you towards a different design that is actually safer and will avoid errors. Like in the kind of case you're talking about, you know, is it actually necessary that an uninitialized object and an initialized one are represented by the same type? Or is there a way to adjust the API so that those are actually different types than you solve the problem and your code is safer or so?

I'm thinking like you submit a web form and before you parse it, you've got to create the instance to set the values. And I don't know. It's not worth diving into, but I do find this differentiation between like the strict enforcement of none versus not none. I think it's powerful and I do think you all are right that it does catch a lot of errors. It's just, it's just a difference and it's just an interesting, interesting choice. But I didn't get a concrete answer from the official counsel.

Nullable or noneable? What is it? I feel like you just don't really even talk about it as a term mostly. It's, yeah, none is special in the type system in like how you represent it, but it's not really special in other ways. You don't have a term for int pipe none? Int or none.

Historically, the term was optional, although I think that term has problems and we're sort of moving away from it because specifically one problem is that optional can mean you don't have to pass it in, like I say, as a function parameter. Let's talk a little bit about TypeShed. I think TypeShed is pretty interesting. Maybe people don't know too much about it.

So I'm sure you all are familiar with this project that you can basically add type information that the libraries didn't bother to include for you, right? What are thoughts on TypeShed? How much do you all lean on this to sort of round out missing types? There are two parts to TypeShed, right? There's the standard library type stubs, which I think are invaluable. Like all the type checkers use those. And I mean, will the standard library itself ever have inline types? Who knows?

This might be around forever. And then there are also the third party stubs. And I think that's what you're describing. They're libraries that for whatever reason don't ship with stubs themselves. Those are in TypeShed. And I think it's been like for a while, there's sort of been a question of like what we want to do with like TypeShed's third party stubs, right? Because like ideally like libraries would ship with their own types, but there are various obstacles to that.

The obstacles that I know of used to be like, we want this to run on Python 2 and Python 3. Or we want it to run on Python 3.3 still. But it's been a long time since any non-type supporting version of Python was a real, you know, a supported type of thing, right? I mean, even 3.9 became deprecated. So on one hand, I feel like they could be merged in, but there's also a lot of other areas that are maybe we don't, they're not common, right? Like other libraries, like pick some, let's say Pyramid.

I don't think the Pyramid web framework really ever got types added to it. Somebody could go and create a typeshed stub or a types underscore pyramid you could pip install and then we'll add the types, right? I certainly see it being really valuable for third party things that are just not going to get the type attention they need. Yeah, I think typeshed is great. I've spent a lot of time on improving it. As Rebecca said, especially with a standard library, it's irreplaceable.

For third party libraries, I think it's become less needed over time. It used to be that very few third party libraries had any types. Now that's obviously changed. A lot of libraries ship their own types, but still there are quite a few types of libraries left where there aren't inline types and typeshed can provide useful types. I think typeshed also provides a service because it has a really great framework for testing these types.

We have tools like step tests and various type checkers that help to make sure these types are good and meet a high standard. So yeah, I think they're still useful for many libraries. Yeah, I was just looking at the types dash flask and I guess it must be, must be gone because now type flask must have it internally. So it's kind of an interim sort of thing. That's pretty cool. In general, typeshed has the policy that we remove the snaps from typeshed if they are in the library itself.

I find these super valuable because if there's a library I want to work with and it just doesn't have types for whatever reason, you can install stuff from here and all of a sudden your editor's way happier. I mean, I know we, you all agreed on like the API boundaries and I did as well. It's like that's one of the really cool things. The other thing that really makes me excited about types is if I hit dot in my editor, I get a meaningful list of real information about what I'm working on.

And so adding, adding these types of things are pretty interesting. I want to ask you all about sort of these rogue, rogue tools that do stuff with Python typing that maybe y'all didn't intend. Like we all mentioned Pydantic, we've got Typer and FastAPI, but even a little farther out there is a bear type. Are you familiar with bear type? Yeah. Bear type's interesting. You can import, they have fun. They have fun with their, their import names and stuff.

But basically you can put a, either a decorator onto some sort of call site or something, or you can just do it to an entire package or entire modules rather. So just run bear type dot claw import bear type this. And then it actually turns into runtime type checks. Good idea, bad idea. Interesting. What do you all think? So un-Pythonic, you won't even open the webpage. People should feel free to write whatever code helps them make like better software.

I haven't really used bear type much myself, but it's clearly useful for some people. And I think generally in designing a type system, we should try to accommodate all users who do useful things to the type system. And that includes things like Pydentic or bear type. It's pretty fast. It's not as big of a hit as you would, you would imagine. They, let me see, what are they, somewhere they had a really fun, fun saying in here, but here we go.

Bear type brings Rust C++ inspired zero cost abstractions into the lawless world of dynamically typed Python by enforcing type safety at the granular level of functions and methods against type hints standardized by the Python community. order one, non-amortized worst case time with negligible constant factors. Like, how about that? No, it's a pretty neat library and it's pretty fast. I honestly, I've never used it in production.

Having type hints and squigglies in the editors or in the linters has always been enough for me, but I can see using this if it's really critical and you're having issues, maybe you want to catch some runtime errors. I don't know. It's not quite an endorsement, but it sure is like a, huh, that's different.

I definitely think that the extent to which type checkers may have a different understanding of your code from what happens at runtime and there isn't anything built in to catch that is sometimes a pain point. And so the desire to have your type annotations, to find out at runtime if your type annotations are telling you a lie, it makes a lot of sense why people would like that. I mean, it's something used to from other languages where the type checker is built into the compiler. Right.

You get like a runtime type cast, like cannot. We kind of get that if you try to parse a thing, you know, like put the int param around a string and it's not really a parsable as an int. But for like real type information, I think personally I would use this as like I might apply types, type checking to a module for debugging and development for a minute and just see what happens and then turn it back off. You know, I don't know that I'd just ship production code that way.

But anyway, I got a couple more questions. We're getting shorter on time here. What was one of the harder questions that you all, harder decisions you all had to address on the council? I think the most contentious one was PEP 724, if I remember the number correctly. It was around a feature called type guards, which is around user-defined type narrowing functions.

Initially, they find that in a way that later was found to be somewhat problematic and we basically came up with a better set of proposed semantics that maybe we should have done the first time around. And what this PEP proposed, and as you can see, I sponsored it, is that we basically changed the meaning of the existing type guards under certain conditions. What is a type guard? A type guard is a function, like there's a good example there, the isiterable.

It's a function that tells you how to narrow something. So in this example, there's an isiterable type guard, which narrows an object to an iterable of anything. And then inside the func there, you can see if isiterable file, it knows that it's an iterable. And in this case, yeah, I guess it just narrows exactly to iterable any. That's one of the ways that type guards works. I see.

And the type that returns kind of communicates to the type system, like that this function ensures that this, the thing that came in as an arbitrary object, in fact, is one of these. Okay. Interesting. Yeah. So that was a tricky one, huh? Any other standout, Rebecca or Carl? Well, the current discussion around what is the meaning of a float annotation, still unresolved, contentious topic. Okay. Gotcha.

I mean, this on PEP724 is also what came to my mind immediately as well, because this was challenging discussion because, you know, like there were very conflicting considerations at play. It's like, what semantics did we want in the long term? And what did we want the type system to look like, you know, say like 10 years from now versus backwards compatibility and what the migration story would look like? It was quite tricky.

I guess that's something you will always have to be cognizant of is like every change, even if it's an improvement, has to justify the fact that now you have challenges with the version history over time. I'm thinking like dict of string comma int with a capital or lowercase d. I've got people, I did a YouTube video showing something with the lowercase version because I was using something super modern like Python 3.11. And I got a message like, hey, Michael, you don't know how to write Python.

Your code is broken. This code that you wrote just doesn't even run. I don't know how this is. I'm like, what version of Python is in? 3.8. Nope. You can't use 3.8 for that. You're going to need to get a newer one. You know what I mean? But like those are complexities that git added to Python because of that. Now you've got two ways to specify what a dict is. There's a preferred new way, but there's still the old way and it just, it sort of piles up.

And it's very hard to ever actually get rid of the old way, even if there's no good reason to use it anymore. Exactly. Once it's there, it's written in ink pretty much, right? Like we have five or six different ways to format strings. Maybe with t-strings at six now. They're all going to still be there, right? So every change, every decision you make is not just a matter of, is it the right decision, right? It's the, is it worth it? I'm sure. Yeah. I don't know. How do you all balance that?

Like that's tricky. With things like the dict chains, at least we sort of know we're moving towards better states and there's two things, but they mean exactly the same thing. So the confusion is not as bad. The problem with type cards is that we're going to change how some existing thing works, like what it meant.

And I think there are good reasons that maybe that's the right thing to do, but the, it would also have been pretty confusing for people if their existing types suddenly started meaning something completely different. Absolutely. Hence float. Okay. What's coming next? Like 3.15, 3.16, do you all have things that are in the works that you think are going to come or debates that are brewing? For 3.15, the, there's a type dict feature coming, extra items.

you can already use it in tapping extensions if you want to use it, but it will be in CPath as of 2.15. It's likely we'll have a small thing I added called disjoint basis, which is very technical, but helps type narrowing in some cases. Yeah. I think those are the things that are likely to make it. There's, we can only speculate about what else people can propose. We're sort of bound by what people actually write up as peps.

We have to wait for Google to write the peps before we can approve them. I think there's PEP 747 for type form, which I think is not, I think we recommended its acceptance, but I don't think the steering council accepted it yet or it hasn't been accepted formally. I think that's on their plate. Yeah. Yeah. So that's also pretty likely to make it into 3.15.

This is one example of a case that will be pretty useful to people working with type annotations at runtime because it'll allow you to, it's sort of a meta thing where you can annotate, have a type annotation that describes another type annotation. So that's useful if you're, if you're writing code that works with type annotations. Make the peidantics of the world very happy.

I am actually pretty excited about type form because, you know, I feel like there's a gap and we can express in the type system and we're good. And there are cases in the existing type system, like for instance, the cast function and some other cases where something takes any type expression as an argument. We actually don't have a good way to annotate that today and this will provide a nice way to express that. Let me pull up one thing really quick. Quick shout out to Will McGuggan here.

He just released his Toad project, which is the new, takes textual and rich and all that kind of stuff and applies it to like, what if we had a better Claude Code type of experience, which is pretty interesting. So the reason I'm bringing this up is, you know, final question. What about, do you all even worry about the role of like how types interact with AI and agentic coding tools?

I know that if you have some code that has types on it and you give it to an AI, it's got a better chance of understanding what's happening than if you give it purely untyped code and say, tell me about this, right? It doesn't even know necessarily what's being passed to it. But is that anything you'll think about or what are your thoughts on this? Certainly think about it some.

I mean, I think overall my feeling is that these coding agents seem to do better than more kind of the tighter feedback loops you can give them to work with. And so typing is another useful source of feedback where you can say, add type annotations and make sure the type checker passes and seems so it still seems pretty useful in that world.

Yeah, you can easily write rules that say when you are done on anything I've asked you to do, always run ty or always run Pyrefly and make sure that there's no more, no new errors or at least or ideally zero errors, right? But nothing has been introduced. Yeah, pretty interesting. You other folks, Rebecca, Yela? Yeah, I guess in general, I think typing will remain useful for AI. We are probably rapidly moving to a world where a large proportion of all code is written by AI.

Not everybody likes that opinion, Yael. Not everybody likes that. I guess I, maybe my current line of work makes me think that's more likely to happen. You don't have to like the fact it's going to be night soon, but it's going to be night. You know what I mean?

Like there's the, I just think there's so much momentum on this, at least in the next five years or something, that it's going to be really, it's, it's a truth of how many people are writing code regardless of whether individuals want to write code that way. You know what I mean? So I think it's a consideration. Yeah. Yeah. I forgot that you worked at OpenAI. So of course, I should pull up a codex example or something, shouldn't I? Yeah. Codex is great. Use it.

No, but I mean, do you have any further insight into like the role of types and coding agents? I know that's not exactly what you work on, right? You're more at the lower. As Carl said, types can also be helpful for AI to understand code better and to get a better feedback loop. I feel like the very big AI, the board is like humans. And if AI makes, sorry, if typing makes humans better at writing understanding this code, they're probably also big AI better at it.

It's the locality of information. You can read the function and know everything you need to know about what's going into it without bouncing around and trying to understand blocks of code and like what might've been created that's getting impacted. It's good for humans and also good for AI. Right. Rebecca. Because I don't have much need to, and I'll say I am maybe a little more skeptical than most of my coworkers about the quality of AI generated code.

But that means I think I am particularly gung-ho about, you know, like get AI to use types, type checkers, keep the guardrails there. I think that'll be very important. Yeah, if it's going to make a mistake, don't let it at least like make the type system become disconnected and not working. Like it has to keep the types hanging together as a minimum bar, right? And you can easily set that up as an automation. Yeah. Interesting to think of it as guardrails rather than an accelerant.

But yeah, 100% it is. All right, folks. I think that's it for all the time that we have. Thank you. Thank you for being here. Final thoughts before we go. Carl, I'll let you go first. Final thoughts for people out there interested in Python typing. Yeah. Well, first of all, thanks for having us on the podcast. Really appreciate it. And thoughts for people out there. I guess if you have ideas of how Python typing could be improved, discuss.python.

Python.org is a good place to bring up ideas and discuss them with the typing community and see what positive changes we can make. Rebecca. First, thank you, Michael. This is a lot of fun. Last thoughts? Hey, so, you know, like we'll look at the typing council and sometimes think, oh, you know, like the PEP has like governance in its name, but I wouldn't say we're really a governing body or anything.

It's like people who are using the type system, like users, they're the ones who come up with, you know, like all the best ideas, propose them, discuss them. And we're just here to sort of be like, hey, you know, like we have some background and like how type checkers work and maybe some of the history and we can provide input. But I just encourage people, if there's a change you want to see in the type system, you know, like propose it yourself. It's very friendly and open community.

Yeah. Now people who have listened know a little bit more about how to do so. Awesome. Thanks. Jale, final word. Yeah. Also, again, thank you for having me here. It's been great talking to all of you. I guess what I want to say is similar to what Karin Rebecca just said. If you want to have something changed to the type system, I'd really encourage you to sign up for discuss.python.org, make a proposal, go through the process.

It can be somewhat daunting, perhaps, especially if you have to create a PEP, but it is doable. There are several recent typing PEPs have just been community members who saw something they wanted to improve, proposed a PEP, and saw it to completion. If there's something you want to see in the type system, then you can do it too. Thank you all for keeping Python typing going strong. Really appreciate your time on the show. See you all later.

Bye. Bye. This has been another episode of Talk Python To Me. Thank you to our sponsors. Be sure to check out what they're offering. It really helps support the show. Take some stress out of your life. Get notified immediately about errors and performance issues in your web or mobile applications with Sentry. Just visit talkpython.fm slash Sentry and get started for free. Be sure to use our code talkpython26. That's talkpython, the numbers two, six, all one word.

And it's brought to you by our Agentic AI programming for Python course. Learn to work with AI that actually understands your code base and build real features. Visit talkpython.fm slash agentic dash AI. If you or your team needs to learn Python, we have over 270 hours of beginner and advanced courses on topics ranging from complete beginners to async code, Flask, Django, HTMX, and even LLMs. Best of all, there's no subscription in sight. Browse the catalog at talkpython.fm.

And if you're not already subscribed to the show on your favorite podcast player, what are you waiting for? Just search for Python in your podcast player. We should be right at the top. If you enjoy that geeky rap song, you can download the full track. The link is actually in your podcast blur show notes. This is your host, Michael Kennedy. Thank you so much for listening. I really appreciate it. I'll see you next time. Voyager. Voyager.

And we ready to roll Upgrading the code No fear of getting whole We tapped into that modern vibe Overcame each storm Talk Python To Me IceSync is the norm

Transcript source: Provided by creator in RSS feed: download file
For the best experience, listen in Metacast app for iOS or Android