Planet Python

Subscribe to Planet Python feed
Planet Python -
Updated: 17 hours 35 min ago

Ned Batchelder: Try it: function/class coverage report

Mon, 2024-04-15 16:02

I’ve added experimental function and class coverage reports to I’d like feedback about whether they behave the way you want them to.

I haven’t made a PyPI release. To try the new reports, install coverage from GitHub. Be sure to include the hash:

$ python3 -m pip install git+

Then run coverage and make an HTML report as you usually do. You should have two new pages, not linked from the index page (yet). “htmlcov/function_index.html” is the function coverage report, and the classes are in “htmlcov/class_index.html”.

I had to decide how to categorize nested functions and classes. Inner functions are not counted as part of their outer functions. Classes consist of the executable lines in their methods, but not lines outside of methods, because those lines run on import. Each file has an entry in the function report for all of the lines outside of any function, called “(no function)”. The class report has “(no class)” entries for lines outside of any classes.

The result should be that every line is part of one function, or the “(no function)” entry, and every line is part of one class, or the “(no class)” entry. This is what made sense to me, but maybe there’s a compelling reason to do it differently.

The reports have a sortable column for the file name, and a sortable column for the function or class. Where functions or classes are nested, the name is a dotted sequence, but is sorted by only the last component. Just like the original file listing page, the new pages can be filtered to focus on areas of interest.

You can look at some sample reports:

It would be helpful if you could give me feedback on the original issue about some questions:

  • Is it useful to have “(no function)” and “(no class)” entries or is it just distracting pedantry? With the entries, the total is the same as the file report, but they don’t seem useful by themselves.
  • Does the handling of nested functions and classes make sense?
  • Should these reports be optional (requested with a switch) or always produced?
  • Is it reasonable to produce one page with every function? How large does a project have to get before that’s not feasible or useful?
  • And most importantly: do these reports help you understand how to improve your code?

This is only in the HTML report for now, but we can do more in the future. Other ideas about improvements are of course welcome. Thanks!

Categories: FLOSS Project Planets

Real Python: Build a Blog Using Django, GraphQL, and Vue

Mon, 2024-04-15 10:00

Are you a regular Django user? Do you find yourself wanting to decouple your back end and front end? Do you want to handle data persistence in the API while you display the data in a single-page app (SPA) in the browser using a JavaScript framework like React or Vue?

If you answered yes to any of these questions, then you’re in luck. This tutorial will take you through the process of building a Django blog back end and a Vue front end, using GraphQL to communicate between them.

Projects are an effective way to learn and solidify concepts. This tutorial is structured as a step-by-step project so you can learn in a hands-on way and take breaks as needed.

In this tutorial, you’ll learn how to:

  • Translate your Django models into a GraphQL API
  • Run the Django server and a Vue application on your computer at the same time
  • Administer your blog posts in the Django admin
  • Consume a GraphQL API in Vue to show data in the browser

You can download all the source code you’ll use to build your Django blog application by clicking the link below:

Get Your Code: Click here to download the free sample code that you’ll use to build a blog using Django, GraphQL, and Vue.

Demo: A Django Blog Admin, a GraphQL API, and a Vue Front End

Blog applications are a common starter project because they involve create, read, update, and delete (CRUD) operations. In this project, you’ll use the Django admin to do the heavy CRUD lifting and you’ll focus on providing a GraphQL API for your blog data.

You’ll use Vue.js 3 and its composition API for the front end of your blog. Vue lets you create dynamic interfaces pretty smoothly, thanks to its reactive data binding and easy-to-manage components. Plus, since you’re dealing with data from a GraphQL API, you can leverage the Vue Apollo plugin.

Here’s a demonstration of the completed project in action:

Next, you’ll make sure you have all the necessary background information and tools before you dive in and build your blog application.

Project Overview

For this project, you’ll create a small blogging application with some rudimentary features:

  • Authors can write many posts.
  • Posts can have many tags and can be either published or unpublished.

You’ll build the back end of this blog in Django, complete with an admin for adding new blog content. Then you’ll expose the content data as a GraphQL API and use Vue to display that data in the browser.

You’ll accomplish this in several high-level steps. At the end of each step, you’ll find a link to the source code for that stage of the project.

If you’re curious about how the source code for each step looks, then you can click the link below:

Get Your Code: Click here to download the free sample code that you’ll use to build a blog using Django, GraphQL, and Vue.


You’ll be best equipped for this tutorial if you already have a solid foundation in some web application concepts. You should understand how HTTP requests and responses and APIs work. You can check out Python & APIs: A Winning Combo for Reading Public Data to understand the details of using GraphQL APIs vs REST APIs.

Because you’ll use Django to build the back end for your blog, you’ll want to be familiar with starting a Django project and customizing the Django admin. If you haven’t used Django much before, you might also want to try building another Django-only project first. For a good introduction, check out Get Started with Django Part 1: Build a Portfolio App.

And because you’ll be using Vue on the front end, some experience with JavaScript will also help. If you’ve only used a JavaScript framework like jQuery in the past, the Vue introduction is a good foundation.

Familiarity with JSON is also important because GraphQL queries are JSON-like and return data in JSON format. You can read about Working with JSON Data in Python for an introduction.

Read the full article at »

[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

Categories: FLOSS Project Planets

Real Python: Write Pythonic and Clean Code With namedtuple

Mon, 2024-04-15 08:00

In this quiz, you’ll test your understanding of Python’s namedtuple().

By working through this quiz, you’ll revisit how to create namedtuple classes, identify and take advantage of the cool features of namedtuple, use namedtuple instances to write Pythonic code, and decide when to use a namedtuple over a similar data structure.

[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

Categories: FLOSS Project Planets Python Meeting Düsseldorf - 2024-04-17

Mon, 2024-04-15 04:00

The following text is in German, since we're announcing a regional user group meeting in Düsseldorf, Germany.


Das nächste Python Meeting Düsseldorf findet an folgendem Termin statt:

17.04.2024, 18:00 Uhr
Raum 1, 2.OG im Bürgerhaus Stadtteilzentrum Bilk
Düsseldorfer Arcaden, Bachstr. 145, 40217 Düsseldorf

Programm Bereits angemeldete Vorträge
  • Marc-André Lemburg:
    Advanced parsing structured data with Python's new match statement
  • Jens Diemer:
    Anbindung von Tinkerforge in Home Assistant
  • Charlie Clark:
    Eine kleine Datenanalyse
  • Detlef Lannert:
    Überblick über CLI-Frameworks
Weitere Vorträge können gerne noch angemeldet werden. Bei Interesse, bitte unter melden. Startzeit und Ort

Wir treffen uns um 18:00 Uhr im Bürgerhaus in den Düsseldorfer Arcaden.

Das Bürgerhaus teilt sich den Eingang mit dem Schwimmbad und befindet sich an der Seite der Tiefgarageneinfahrt der Düsseldorfer Arcaden.

Über dem Eingang steht ein großes "Schwimm’ in Bilk" Logo. Hinter der Tür direkt links zu den zwei Aufzügen, dann in den 2. Stock hochfahren. Der Eingang zum Raum 1 liegt direkt links, wenn man aus dem Aufzug kommt.

>>> Eingang in Google Street View

⚠️ Wichtig: Bitte nur dann anmelden, wenn ihr absolut sicher seid, dass ihr auch kommt. Angesichts der begrenzten Anzahl Plätze, haben wir kein Verständnis für kurzfristige Absagen oder No-Shows. Einleitung

Das Python Meeting Düsseldorf ist eine regelmäßige Veranstaltung in Düsseldorf, die sich an Python Begeisterte aus der Region wendet.

Einen guten Überblick über die Vorträge bietet unser PyDDF YouTube-Kanal, auf dem wir Videos der Vorträge nach den Meetings veröffentlichen.

Veranstaltet wird das Meeting von der GmbH, Langenfeld, in Zusammenarbeit mit Clark Consulting & Research, Düsseldorf:


Das Python Meeting Düsseldorf nutzt eine Mischung aus (Lightning) Talks und offener Diskussion.

Vorträge können vorher angemeldet werden, oder auch spontan während des Treffens eingebracht werden. Ein Beamer mit HDMI und FullHD Auflösung steht zur Verfügung.

(Lightning) Talk Anmeldung bitte formlos per EMail an


Das Python Meeting Düsseldorf wird von Python Nutzern für Python Nutzer veranstaltet.

Da Tagungsraum, Beamer, Internet und Getränke Kosten produzieren, bitten wir die Teilnehmer um einen Beitrag in Höhe von EUR 10,00 inkl. 19% Mwst. Schüler und Studenten zahlen EUR 5,00 inkl. 19% Mwst.

Wir möchten alle Teilnehmer bitten, den Betrag in bar mitzubringen.


Da wir nur 25 Personen in dem angemieteten Raum empfangen können, möchten wir bitten, sich vorher anzumelden.

Meeting Anmeldung bitte per Meetup

Weitere Informationen

Weitere Informationen finden Sie auf der Webseite des Meetings:


Viel Spaß !

Marc-Andre Lemburg,

Categories: FLOSS Project Planets

Zato Blog: Service-oriented API task scheduling

Mon, 2024-04-15 04:00
Service-oriented API task scheduling 2024-04-15, by Dariusz Suchojad

An integral part of Zato, its scalable, service-oriented scheduler makes it is possible to execute high-level API integration processes as background tasks. The scheduler runs periodic jobs which in turn trigger services and services are what is used to integrate systems.

Integration process

In this article we will check how to use the scheduler with three kinds of jobs, one-time, interval-based and Cron-style ones.

What we want to achieve is a sample yet fairly common use-case:

  • Periodically consult a remote REST endpoint for new data
  • Store data found in Redis
  • Push data found as an e-mail attachment

Instead of, or in addition to, Redis or e-mail, we could use SQL and SMS, or MongoDB and AMQP or anything else - Redis and e-mail are just example technologies frequently used in data synchronisation processes that we use to highlight the workings of the scheduler.

No matter the input and output channels, the scheduler works always the same - a definition of a job is created and the job's underlying service is invoked according to the schedule. It is then up to the service to perform all the actions required in a given integration process.

Python code

Our integration service will read as below:

# -*- coding: utf-8 -*- # Zato from zato.common.api import SMTPMessage from zato.server.service import Service class SyncData(Service): name = 'api.scheduler.sync' def handle(self): # Which REST outgoing connection to use rest_out_name = 'My Data Source' # Which SMTP connection to send an email through smtp_out_name = 'My SMTP' # Who the recipient of the email will be smtp_to = '' # Who to put on CC smtp_cc = '' # Now, let's get the new data from a remote endpoint .. # .. get a REST connection by name .. rest_conn = self.out.plain_http[rest_out_name].conn # .. download newest data .. data = rest_conn.get(self.cid).text # .. construct a new e-mail message .. message = SMTPMessage() message.subject = 'New data' message.body = 'Check attached data' # .. add recipients .. = smtp_to = smtp_cc # .. attach the new data to the message .. message.attach('', data) # .. get an SMTP connection by name .. smtp_conn =[smtp_out_name].conn # .. send the e-mail message with newest data .. smtp_conn.send(message) # .. and now store the data in Redis. self.kvdb.conn.set('', data)

Now, we just need to make it run periodically in background.

Mind the timezone

In the next steps, we will use the Zato Dashboard to configure new jobs for the scheduler.

Keep it mind that any date and time that you enter in web-admin is always interepreted to be in your web-admin user's timezone and this applies to the scheduler too - by default the timezone is UTC. You can change it by clicking Settings and picking the right timezone to make sure that the scheduled jobs run as expected.

It does not matter what timezone your Zato servers are in - they may be in different ones than the user that is configuring the jobs.

Endpoint definitions

First, let's use web-admin to define the endpoints that the service uses. Note that Redis does not need an explicit declaration because it is always available under "self.kvdb" in each service.

  • Configuring outgoing REST APIs

  • Configuring SMTP e-mail

Now, we can move on to the actual scheduler jobs.

Three types of jobs

To cover different integration needs, three types of jobs are available:

  • One-time - fires once only at a specific date and time and then never runs again
  • Interval-based - for periodic processes, can use any combination of weeks, days, hours, minutes and seconds for the interval
  • Cron-style - similar to interval-based but uses the syntax of Cron for its configuration


Select one-time if the job should not be repeated after it runs once.


Select interval-based if the job should be repeated periodically. Note that such a job will by default run indefinitely but you can also specify after how many times it should stop, letting you to express concepts such as "Execute once per hour but for the next seven days".


Select cron-style if you are already familiar with the syntax of Cron or if you have some Cron tasks that you would like to migrate to Zato.

Running jobs manually

At times, it is convenient to run a job on demand, no matter what its schedule is and regardless of what type a particular job is. Web-admin lets you always execute a job directly. Simply find the job in the listing, click "Execute" and it will run immediately.

Extra context

It is very often useful to provide additional context data to a service that the scheduler runs - to achieve it, simply enter any arbitrary value in the "Extra" field when creating or an editing a job in web-admin.

Afterwards, that information will be available as self.request.raw_request in the service's handle method.


There is nothing else required - all is done and the service will run in accordance with a job's schedule.

Yet, before concluding, observe that our integration service is completely reusable - there is nothing scheduler-specific in it despite the fact that we currently run it from the scheduler.

We could now invoke the service from command line. Or we could mount it on a REST, AMQP, WebSocket or trigger it from any other channel - exactly the same Python code will run in exactly the same fashion, without any new programming effort needed.

More blog posts
Categories: FLOSS Project Planets

Real Python: The Real Python Podcast – Episode #200: Avoiding Error Culture and Getting Help Inside Python

Fri, 2024-04-12 08:00

What is error culture, and how do you avoid it within your organization? How do you navigate alert and notification fatigue? Hey, it's episode #200! Real Python's editor-in-chief, Dan Bader, joins us this week to celebrate. Christopher Trudeau also returns to bring another batch of PyCoder's Weekly articles and projects.

[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

Categories: FLOSS Project Planets

Pythonicity: GraphQL root fields

Thu, 2024-04-11 20:00
There is no such thing as a “root field”.

There is a common - seemingly universal - misconception that GraphQL root fields are somehow special, in both usage and implementation. The better conceptual model is that there are root types, and all types have fields. The difference is not just semantics; it leads to actual misunderstandings.

Multiple queries

A common beginner question is “can there be multiple queries in a request”. The question would be better phrased as “can multiple fields on the root query type be requested”. The answer is of course, because requesting multiple fields on a type is normal. The implementation would have to go out of its way to restrict that behavior on just the root type. The only need for further clarity would be to introduce aliases for duplicate fields.

Flat namespace

GraphQL types share a global namespace, causing conflicts when federating multiple graphs. Nothing can be done about that unless GraphQL adopts namespaces.

But many APIs design the root query type to have unnecessarily flat fields. One often sees a hierarchy of types and fields below the root, but the top-level fields resemble a loose collections of functions. Verbs at the top level; nouns the rest of the way down. This design choice appears to be in a feedback loop with the notion of “root fields”.

Even the convention of calling the root query type Query demonstrates a lack of specificity. In a service-oriented architecture, a particular service might be more narrowly defined.


Top-level mutation fields are special in one aspect: they are executed in order. This has resulted in even flatter namespaces for mutations,

mutation { createUser # executed first deleteUser }

This is not necessary, but seems widely believed that it is. Nested mutations work just fine.

mutation { user { create # executed in arbitrary order delete } }

If the underlying reason is truly execution order, the client could be explicit instead.

mutation { created: user { # executed first create } deleted: user { delete } }

There is no reason it has to influence API design.

Static methods

At the library level, the effect is top-level resolvers are implemented as functions (or static methods), whereas all other resolver are methods. This may lead to redundant or inefficient implementations, is oddly inconsistent, and is contrary to the documentation.

A resolver function receives four arguments:

obj The previous object, which for a field on the root Query type is often not used.

Sure, “often not used” by the developer of the API. That does not mean “should be unset” by the GraphQL library, but that is what has happened. Some libraries even exclude the object parameter entirely. In object-oriented libraries like strawberry, the code looks unnatural.

import strawberry @strawberry.type class Query: @strawberry.field def instance(self) -> bool | None: return None if self is None else isinstance(self, Query) schema = strawberry.Schema(Query) query = '{ instance }' schema.execute_sync(query).data {'instance': None}

Strawberry allows omitting self for this reason, creating an implicit staticmethod.

Root values

Libraries which follow the reference javascript implementation allow setting the root value explicitly.

schema.execute_sync(query, root_value=Query()).data {'instance': True}

Strawberry unofficially supports supplying an instance, but it has no effect.

schema = strawberry.Schema(Query()) schema.execute_sync(query).data {'instance': None}

And of course self can be of any type.

schema.execute_sync(query, root_value=...).data {'instance': False}

Moreover, the execute functions are for internal usage. Each library will vary in how to configure the root in a production application. Strawberry requires subclassing the application type.

import strawberry.asgi class GraphQL(strawberry.asgi.GraphQL): def __init__(self, root): super().__init__(strawberry.Schema(type(root))) self.root_value = root async def get_root_value(self, request): return self.root_value Example

Consider a more practical example where data is loaded, and clearly should not be reloaded on each request.

@strawberry.type class Dictionary: def __init__(self, source='/usr/share/dict/words'): self.words = {line.strip() for line in open(source)} @strawberry.field def is_word(self, text: str) -> bool: return text in self.words

Whether Dictionary is the query root - or attached to the query root - it should be instantiated only once. Of course it can be cached, but again there is a more natural way to write this outside the context of GraphQL.

@strawberry.type class Query: dictionary: Dictionary def __init__(self): self.dictionary = Dictionary()

Caching, context values, and root values are all clunky workarounds compared to the consistency of letting the root be Query() instead of Query. The applications which do not require this feature would never notice the difference.

The notion of “root fields” behaving as “top-level functions” has resulted in needless confusion, poorer API design, and incorrect implementations.

Categories: FLOSS Project Planets

PyCharm: Django Learning Resources

Thu, 2024-04-11 06:53
Are you new to Django development? Are you already familiar with it and want to expand your knowledge? PyCharm has Django learning resources for everyone.  In this article, you’ll find a compilation of all the Django-related resources created by the experts at PyCharm to help you navigate through them all. From creating a new Django […]
Categories: FLOSS Project Planets

Test and Code: 217: Podcasting / SaaS / Work Life Balance - Justin Jackson

Thu, 2024-04-11 03:36

If you've ever thought about starting a podcast or a SaaS project, you'll want to listen to this episode.
Justin is one of the people who motivated me to get started podcasting.
He's also running a successful SaaS company,, which hosts this podcast.


  • Podcasting
  • Building new SaaS (software as a service) products
  • Balancing work, side hustle, and family
  • Great places to snowboard in British Columbia

BTW. This episode was recorded last summer before I switched to
I'm now on Transistor for most of a year now, and I love it.

Links from the show:

Sponsored by

  • An Email Delivery Platform that developers love. 
  • An email-sending solution with industry-best analytics, SMTP, an email API, SDKs for major programming languages, and 24/7 human support. 
  • Try for Free at MAILTRAP.IO

Sponsored by PyCharm Pro

The Complete pytest Course

  • For the fastest way to learn pytest, go to
  • Whether your new to testing or pytest, or just want to maximize your efficiency and effectiveness when testing.
<p>If you've ever thought about starting a podcast or a SaaS project, you'll want to listen to this episode.<br> <br>Justin is one of the people who motivated me to get started podcasting. <br>He's also running a successful SaaS company, <a href=""></a>, which hosts this podcast.</p><p>Topics:</p><ul><li>Podcasting</li><li>Building new SaaS (software as a service) products</li><li>Balancing work, side hustle, and family</li><li>Great places to snowboard in British Columbia</li></ul><p>BTW. This episode was recorded last summer before I switched to <a href=""></a>.<br>I'm now on Transistor for most of a year now, and I love it.</p><p>Links from the show:</p><ul><li><a href=""></a> - excellent podcast hosting, Justin is a co-founder</li><li><a href="">How to start a podcast in 2024</a></li><li>Podcasts from Justin<ul><li><a href="">Build your SaaS</a> - current</li><li><a href="">Build &amp; Launch</a> - an older one, but great</li><li><a href="">MegaMaker</a> - from 2021 / 2022</li></ul></li></ul> <br><p><strong>Sponsored by</strong></p><ul><li>An Email Delivery Platform that developers love. </li><li>An email-sending solution with industry-best analytics, SMTP, an email API, SDKs for major programming languages, and 24/7 human support. </li><li>Try for Free at <a href="">MAILTRAP.IO</a></li></ul><p><strong>Sponsored by PyCharm Pro</strong></p><ul><li>Use code PYTEST for 20% off PyCharm Professional at <a href=""></a></li><li>Now with Full Line Code Completion</li><li>See how easy it is to run pytest from PyCharm at <a href=""></a></li></ul><p><strong>The Complete pytest Course</strong></p><ul><li>For the fastest way to learn pytest, go to <a href=""></a></li><li>Whether your new to testing or pytest, or just want to maximize your efficiency and effectiveness when testing.</li></ul>
Categories: FLOSS Project Planets

Spyder IDE: Spyder 6 will get a new installer for all platforms and a standalone application for Linux!

Wed, 2024-04-10 20:00

For the last several years, Spyder has offered standalone installers for Windows and macOS which isolate Spyder's runtime environment from users' development environments. This provides a more stable user experience than traditional conda or pip installation methods. However, these standalone installers did not allow implementing desirable features, such as automatic incremental updates or installing external Spyder plugins like Spyder-Notebook and Spyder-Unittest. Additionally, these standalone applications were limited to Windows and macOS.

Our new installers will provide a more consistent experience for users across all platforms, including Linux, while maintaining the benefits of an isolated runtime environment for Spyder. Additionally, they are fully compatible with incremental updates and external plugin management. Look for future announcements about these and other features!

So, what will you see with these new installers? If you are a Windows user, you will continue to have a graphical interface guiding you through the installation process, and will likely not notice any difference from the previous experience.

If you are a macOS user, you will now have a .pkg package installer instead of a .dmg disk image. Rather than drag-and-drop the application to the Applications folder, the .pkg installer provides a graphical interface that will guide you through the installation process with more flexibility.

If you are a Linux user, you will have an interactive shell script guiding you through the installation process. This ensures it is compatible with as many distributions and desktop environments as possible.

In all cases, you will not need to have Anaconda installed, nor do you need an existing Python environment; in fact, you don't even need a preexisting Python installation! These installers are completely self-contained. Spyder will continue to include popular packages such as NumPy, SciPy, Pandas and Matplotlib so you can start coding out-of-the-box. However, you will still be able to use Spyder with your existing conda, venv,, and other Python installers and environments as before. Furthermore, only Spyder and its critical dependencies will be updated on each new release, which will make getting the latest version a lean and frictionless process.

The Spyder team is really excited about these new installers and the new features they will make possible, and we hope you enjoy them too!

Categories: FLOSS Project Planets

Data School: Should you discretize continuous features for Machine Learning? 🤖

Wed, 2024-04-10 11:12

Let&aposs say that you&aposre working on a supervised Machine Learning problem, and you&aposre deciding how to encode the features in your training data.

With a categorical feature, you might consider using one-hot encoding or ordinal encoding. But with a continuous numeric feature, you would normally pass that feature directly to your model. (Makes sense, right?)

However, one alternative strategy that is sometimes used with continuous features is to "discretize" or "bin" them into categorical features before passing them to the model.

First, I&aposll show you how to do this in scikit-learn. Then, I&aposll explain whether I think it&aposs a good idea!

How to discretize in scikit-learn

In scikit-learn, we can discretize using the KBinsDiscretizer class:

When creating an instance of KBinsDiscretizer, you define the number of bins, the binning strategy, and the method used to encode the result:

As an example, here&aposs a numeric feature from the famous Titanic dataset:

And here&aposs the output when we use KBinsDiscretizer to transform that feature:

Because we specified 3 bins, every sample has been assigned to bin 0 or 1 or 2. The smallest values were assigned to bin 0, the largest values were assigned to bin 2, and the values in between were assigned to bin 1.

Thus, we&aposve taken a continuous numeric feature and encoded it as an ordinal feature (meaning an ordered categorical feature), and this ordinal feature could be passed to the model in place of the numeric feature.

Is discretization a good idea?

Now that you know how to discretize, the obvious follow-up question is: Should you discretize your continuous features?

Theoretically, discretization can benefit linear models by helping them to learn non-linear trends. However, my general recommendation is to not use discretization, for three main reasons:

  1. Discretization removes all nuance from the data, which makes it harder for a model to learn the actual trends that are present in the data.
  2. Discretization reduces the variation in the data, which makes it easier to find trends that don&apost actually exist.
  3. Any possible benefits of discretization are highly dependent on the parameters used with KBinsDiscretizer. Making those decisions by hand creates a risk of overfitting the training data, and making those decisions during a tuning process adds both complexity and processing time. As such, neither option is attractive to me!

For all of those reasons, I don&apost recommend discretizing your continuous features unless you can demonstrate, through a proper model evaluation process, that it&aposs providing a meaningful benefit to your model.

Going further

&#x1F517; Discretization in the scikit-learn User Guide

&#x1F517; Discretize Predictors as a Last Resort from Feature Engineering and Selection (section 6.2.2)

This post is drawn directly from my upcoming course, Master Machine Learning with scikit-learn. If you&aposre interested in receiving more free lessons from the course, please join the waitlist below:

Categories: FLOSS Project Planets

Real Python: Pydantic: Simplifying Data Validation in Python

Wed, 2024-04-10 10:00

Pydantic is a powerful data validation and settings management library for Python, engineered to enhance the robustness and reliability of your codebase. From basic tasks, such as checking whether a variable is an integer, to more complex tasks, like ensuring highly-nested dictionary keys and values have the correct data types, Pydantic can handle just about any data validation scenario with minimal boilerplate code.

In this tutorial, you’ll learn how to:

  • Work with data schemas with Pydantic’s BaseModel
  • Write custom validators for complex use cases
  • Validate function arguments with Pydantic’s @validate_call
  • Manage settings and configure applications with pydantic-settings

Throughout this tutorial, you’ll get hands-on examples of Pydantic’s functionalities, and by the end you’ll have a solid foundation for your own validation use cases. Before starting this tutorial, you’ll benefit from having an intermediate understanding of Python and object-oriented programming.

Get Your Code: Click here to download the free sample code that you’ll use to help you learn how Pydantic can help you simplify data validation in Python.

Python’s Pydantic Library

One of Python’s main attractions is that it’s a dynamically typed language. Dynamic typing means that variable types are determined at runtime, unlike statically typed languages where they are explicitly declared at compile time. While dynamic typing is great for rapid development and ease of use, you often need more robust type checking and data validation for real-world applications. This is where Python’s Pydantic library has you covered.

Pydantic has quickly gained popularity, and it’s now the most widely used data validation library for Python. In this first section, you’ll get an overview of Pydantic and a preview of the library’s powerful features. You’ll also learn how to install Pydantic along with the additional dependencies you’ll need for this tutorial.

Getting Familiar With Pydantic

Pydantic is a powerful Python library that leverages type hints to help you easily validate and serialize your data schemas. This makes your code more robust, readable, concise, and easier to debug. Pydantic also integrates well with many popular static typing tools and IDEs, which allows you to catch schema issues before running your code.

Some of Pydantic’s distinguishing features include:

  • Customization: There’s almost no limit to the kinds of data you can validate with Pydantic. From primitive Python types to highly nested data structures, Pydantic lets you validate and serialize nearly any Python object.

  • Flexibility: Pydantic gives you control over how strict or lax you want to be when validating your data. In some cases, you might want to coerce incoming data to the correct type. For example, you could accept data that’s intended to be a float but is received as an integer. In other cases, you might want to strictly enforce the data types you’re receiving. Pydantic enables you to do either.

  • Serialization: You can serialize and deserialize Pydantic objects as dictionaries and JSON strings. This means that you can seamlessly convert your Pydantic objects to and from JSON. This capability has led to self-documenting APIs and integration with just about any tool that supports JSON schemas.

  • Performance: Thanks to its core validation logic written in Rust, Pydantic is exceptionally fast. This performance advantage gives you swift and reliable data processing, especially in high-throughput applications such as REST APIs that need to scale to a large number of requests.

  • Ecosystem and Industry Adoption: Pydantic is a dependency of many popular Python libraries such as FastAPI, LangChain, and Polars. It’s also used by most of the largest tech companies and throughout many other industries. This is a testament to Pydantic’s community support, reliability, and resilience.

These are a few key features that make Pydantic an attractive data validation library, and you’ll get to see these in action throughout this tutorial. Up next, you’ll get an overview of how to install Pydantic along with its various dependencies.

Installing Pydantic

Pydantic is available on PyPI, and you can install it with pip. Open a terminal or command prompt, create a new virtual environment, and then run the following command to install Pydantic:

Shell (venv) $ python -m pip install pydantic Copied!

This command will install the latest version of Pydantic from PyPI onto your machine. To verify that the installation was successful, start a Python REPL and import Pydantic:

Python >>> import pydantic Copied!

If the import runs without error, then you’ve successfully installed Pydantic, and you now have the core of Pydantic installed on your system.

Adding Optional Dependencies

You can install optional dependencies with Pydantic as well. For example, you’ll be working with email validation in this tutorial, and you can include these dependencies in your install:

Shell (venv) $ python -m pip install "pydantic[email]" Copied!

Pydantic has a separate package for settings management, which you’ll also cover in this tutorial. To install this, run the following command:

Shell (venv) $ python -m pip install pydantic-settings Copied!

With that, you’ve installed all the dependencies you’ll need for this tutorial, and you’re ready to start exploring Pydantic. You’ll start by covering models—Pydantic’s primary way of defining data schemas.

Read the full article at »

[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

Categories: FLOSS Project Planets

ListenData: How to Open Chrome using Selenium in Python

Wed, 2024-04-10 04:05

This tutorial explains the steps to open Google Chrome using Selenium in Python.

To read this article in full, please click hereThis post appeared first on ListenData
Categories: FLOSS Project Planets

Seth Michael Larson: CPython release automation, more Windows SBOMs

Tue, 2024-04-09 20:00
CPython release automation, more Windows SBOMs AboutBlogNewsletterLinks CPython release automation, more Windows SBOMs

Published 2024-04-10 by Seth Larson
Reading time: minutes

This critical role would not be possible without funding from the Alpha-Omega project. Massive thank-you to Alpha-Omega for investing in the security of the Python ecosystem! CPython source and docs builds are automated in GitHub Actions

While I was away on vacation, the CPython Developer-in-Residence Łukasz was able to dry-run, review, and merge my pull request to automate source and docs builds in GitHub Actions on the python/release-tools repository.

The automation was successfully used for the CPython 3.10.14, 3.11.9, 3.12.3, and 3.13.0a6.

This work being merged is exciting because it isolates the CPython source and docs builds from individual release manager machines preventing those source tarballs from being unintentionally modified. Having builds automated is a pre-requisite for future improvements to the CPython release process, like adding automated uploads to or machine/workflow identities for Sigstore. I also expect the macOS installer release process to be automated on GitHub Actions. Windows artifact builds are already using Azure Pipelines.

Release Managers have requested the process be further automated which is definitely possible, especially for gathering the built release artifacts and running the test suite locally.

Windows Software Bill-of-Materials coming for next CPython releases

I've been collaborating with Steve Dower to get SBOM documents generated for Windows Python artifacts. Challenges of developing new CI workflows aside we now have SBOM artifacts generating as expected. Next step is to automate the upload of the SBOM documents to so they've automatically available to users.

During the development of Windows SBOMs I also noticed our CPE identifier for xz was incorrect partially due to difficulties using the CPE Dictionary search (they only allow 3+ character queries!) This issue doesn't impact any existing published SBOM since xz-utils is only bundled with Windows artifacts, not source artifacts.

Thoughts on xz-utils

I've been a part of many discussions about xz-utils, both active and passive, there are many thoughts percolating around our community right now. To capture where I'm at with many of these discussions I wanted to write down my own thoughts:

  • "Insider threats" and other attacks on trust are not unique to open source software. We don't get to talk about other incidents as often because the attack isn't done in the open.
  • Insider threats are notoriously difficult to defend against even with ample resources. Volunteer maintainers shouldn't be expected to defend against patient and well-resourced attackers alone.
  • Multiple ecosystems took action immediately, including Python, to either remove compromised versions of xz or confirm they were not using affected versions. This sort of immediate response should only be expected with full-time staffing (thanks Alpha-Omega!), but I know that volunteers were involved in the broader response to xz.
  • Many folks, myself included, have remarked that this could have just as easily been them. Reviewing the "Jia Tan" commits I can't say with certainty that I would have caught them in code review, especially coming from a long-time co-maintainer.

How has the nature of open source affected the response to this event?

  • Security experts were able to review source code, commits, conversations, and the accounts involved immediately. We went from the public disclosure and alerts to having a timeline of how the malicious account was able to gain trust within a few hours.
  • We get to learn how the attack transpired in detail to hopefully improve in the future.

Things to keep in mind when working on solutions:

  • Blaming open source software or maintainers is rarely the answer, and it definitely isn't the answer here.
  • There isn't a single solution to this problem. We need both social and technical approaches, not exclusively one or the other. Instead of pointing out how certain solutions or ways of supporting OSS "wouldn't have thwarted this exact attack", let's focus on how the sum of contributions and support are making attacks on open source more difficult in general.
  • We need better visibility into critical languishing projects. xz likely wasn't on anyone's list of critical projects before this event (when it should have been!) It isn't sustainable to figure out which projects are on critical and need help by waiting for the next newsworthy attack or vulnerability.

I also reflected on how my own work is contributing to one of many solutions to problems like this. I've been focusing on reproducible builds, hardening of the CPython release process, and have been working closely with Release Managers to improve their processes.

As I mentioned above, being full-time staff means I can respond quickly to events in the community. The "all-clear" message for CPython, PyPI, and all Python packages was given a few hours after xz-utils backdoor was disclosed to the Python Security Response Team.

I've also been working on Software Bill-of-Materials documents. These documents would not have done anything to stop an attack similar to this, but would have helped users of CPython detect if they were using a vulnerable component if the attack affected CPython.

Other items
  • I'm attending SOSS Community Day and OSS Summit NA in Seattle April 15th to 19th. If you're there and want to chat reach out to me! I spent time this week preparing to speak at SOSS Community Day.
  • Added support for Python 3.13 to Truststore.
  • Triaged reports to the Python Security Response Team.

That's all for this week! 👋 If you're interested in more you can read last week's report.

Thanks for reading! ♡ Did you find this article helpful and want more content like it? Get notified of new posts by subscribing to the RSS feed or the email newsletter.

This work is licensed under CC BY-SA 4.0

Categories: FLOSS Project Planets

PyCoder’s Weekly: Issue #624 (April 9, 2024)

Tue, 2024-04-09 15:30

#624 – APRIL 9, 2024
View in Browser »

Install and Execute Python Applications Using pipx

In this tutorial, you’ll learn about a tool called pipx, which lets you conveniently install and run Python packages as standalone command-line applications in isolated environments. In a way, pipx turns the Python Package Index (PyPI) into an app marketplace for Python programmers.

Why Do Python Lists Multiply Oddly?

In Python you can use the multiplication operator on sequences to return a repeated version of the value. When you do this with a list containing an empty list you get what might be unexpected behavior. This article explains what happens and why.

Saga Pattern Made Easy

The Saga pattern lets you manage state across distributed transactions. But it’s difficult to build and maintain. Download this free technical guide to learn how to Automate Sagas Pattern with Temporal, the open source durable execution platform →

Inline Run Dependencies in pipx 1.4.2

PEP 723 adds the ability to specify dependencies within a Python script itself. The folks who write pipx have added an experimental feature that takes advantage of this future language change. This article shows you how the new feature looks and what pipx does with it.

PEP 738 Accepted: Adding Android as a Supported Platform


PEP 742 Accepted: Narrowing Types With TypeIs


Django Bugfix Release Issued: 5.0.4


Discussions What Is the Most Useless Project You Have Worked On?


Articles & Tutorials Enforcing Conventions in Django Projects With Introspection

This post talks about the importance of naming conventions in your code, but takes it to the next level: use scripts to validate that conventions get followed. By using introspection you can write rules for detecting code that doesn’t follow your conventions. Examples are for Django fields but the concept works for any Python code.

Leveraging Docs and Data to Create a Custom LLM Chatbot

How do you customize a LLM chatbot to address a collection of documents and data? What tools and techniques can you use to build embeddings into a vector database? This week on the show, Calvin Hendryx-Parker is back to discuss developing an AI-powered, Large Language Model-driven chat interface.

“Real” Anonymous Functions for Python

The topic of multi-line lambdas, or anonymous functions akin to languages like JavaScript, comes up with some frequency in the Python community. It popped up again recently. This article talks about the history of the topic and the current reasoning against it.

How to Set Up Pre-Commit Hooks

Maintaining code quality can be challenging no matter the size of your project or the number of contributors. Pre-commit hooks make it a little easier. This article provides a step-by-step guide to installing and configuring pre-commit hooks on your project.
STEFANIE MOLIN • Shared by Stefanie Molin

Fix Python Code Smells With These Best Practices

A code smell isn’t something that is necessarily broken, but could be a sign of deeper problems. This post teaches you how to identify and eliminate seven Python code smells with practical examples.

New Open Initiative for Cybersecurity Standards

The PSF has joined with the Apache Software Foundation, the Eclipse Foundation, and other open source groups to form a group dedicated to cybersecurity initiatives in the open source community.

10 Reasons I Stick to Django Rather Than FastAPI

FastAPI is an excellent library and is quite popular in the Python community. Regardless of his respect for it, David still sticks with Django. This post discusses his ten reasons why.

My Accessibility Review Checklist

Ensuring accessibility in your software is important, removing boundaries that limit some people from participating. This checklist is valuable for helping you determine whether your web code meets the accepted Web Content Accessibility Guidelines.

Python Deep Learning: PyTorch vs Tensorflow

PyTorch vs Tensorflow: Which one should you use? Learn about these two popular deep learning libraries and how to choose the best one for your project.

Python Project-Local Virtualenv Management Redux

Hynek talks about his Python tooling choices and how they’ve changed over the years, with a focus on environment management tools like uv and direnv.

Trying Out Rye

Hamuko decided to try out rye. This post goes into detail about what worked and what didn’t for them.

Projects & Code drawpyo: Programmatically Generate Charts


best-python-cheat-sheet: The Best* Python Cheat Sheet


rebound: Multi-Purpose N-Body Code


Compatibility Layer Between Polars, Pandas, cuDF, and More!

GITHUB.COM/MARCOGORELLI • Shared by Marco Gorelli

Reduce the Size of GeoJSON Files

GITHUB.COM/BEN-N93 • Shared by Ben Nour

Events Weekly Real Python Office Hours Q&A (Virtual)

April 10, 2024

Python Atlanta

April 11 to April 12, 2024

DFW Pythoneers 2nd Saturday Teaching Meeting

April 13, 2024

Inland Empire Python Users Group Monthly Meeting

April 17, 2024

Data Ethics

April 17, 2024

Python Meeting Düsseldorf

April 17, 2024

Happy Pythoning!
This was PyCoder’s Weekly Issue #624.
View in Browser »

[ Subscribe to 🐍 PyCoder’s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]

Categories: FLOSS Project Planets

Python Software Foundation: Announcing Python Software Foundation Fellow Members for Q4 2023! 🎉

Tue, 2024-04-09 12:59

The PSF is pleased to announce its fourth batch of PSF Fellows for 2023! Let us welcome the new PSF Fellows for Q4! The following people continue to do amazing things for the Python community:

Jelle Zijlstra

Thank you for your continued contributions. We have added you to our Fellow roster online.

The above members help support the Python ecosystem by being phenomenal leaders, sustaining the growth of the Python scientific community, maintaining virtual Python communities, maintaining Python libraries, creating educational material, organizing Python events and conferences, starting Python communities in local regions, and overall being great mentors in our community. Each of them continues to help make Python more accessible around the world. To learn more about the new Fellow members, check out their links above.

Let's continue recognizing Pythonistas all over the world for their impact on our community. The criteria for Fellow members is available online: If you would like to nominate someone to be a PSF Fellow, please send a description of their Python accomplishments and their email address to psf-fellow at Quarter 1 nominations are currently in review. We are accepting nominations for Quarter 2 2024 through May 20, 2024.

Are you a PSF Fellow and want to help the Work Group review nominations? Contact us at psf-fellow at

Categories: FLOSS Project Planets

Python Insider: Python 3.12.3 and 3.13.0a6 released

Tue, 2024-04-09 11:16

It’s time to eclipse the Python 3.11.9 release with two releases, one of which is the very last alpha release of Python 3.13:


Python 3.12.3

300+ of the finest commits went into this latest maintenance release of the latest Python version, the most stablest, securest, bugfreeest we could make it.


Python 3.13.0a6

What’s that? The last alpha release? Just one more month until feature freeze! Get your features done, get your bugs fixed, let’s get 3.13.0 ready for people to actually use! Until then, let’s test with alpha 6. The highlights of 3.13 you ask? Well:

(Hey, fellow core developer, if a feature you find important is missing from this list, let Thomas know. It’s getting to be really important now!)  
We hope you enjoy the new releases!

Thanks to all of the many volunteers who help make Python Development and these releases possible! Please consider supporting our efforts by volunteering yourself, or through contributions to the Python Software Foundation or CPython itself.

Thomas “can you tell I haven’t had coffee today” Wouters
on behalf of your release team,

Ned Deily
Steve Dower
Pablo Galindo Salgado
Łukasz Langa

Categories: FLOSS Project Planets

Mike Driscoll: Anaconda Partners with Teradata for AI with Python packages in the Cloud

Tue, 2024-04-09 10:04

Anaconda has announced a new partnership with Teradata to bring Python and R packages to Teradata VantageCloud through the Anaconda Repository.

But what does that mean? This new partnership allows engineers to:

  • Rapidly deploy and operationalize AI/ML developed using open-source Python and R packages.
  • Unlock innovation and the full potential of data at scale with a wide variety of Python and R functionality on VantageCloud Lake.
  • Flexibly use packages and versions of their choice for large-scale data science, AI/ML and generative AI use-cases.
  • Securely work with Python/R models into VantageCloud Lake with no intellectual property (IP) leakage.

Teradata VantageCloud Lake customers can download Python and R packages from the Anaconda Repository at no additional cost. Python packages are available immediately, and R packages will be released before the end of the year.

For more information about Teradata ClearScape Analytics, please visit

Learn more about partnering with Anaconda here.

The post Anaconda Partners with Teradata for AI with Python packages in the Cloud appeared first on Mouse Vs Python.

Categories: FLOSS Project Planets

Real Python: Generating QR Codes With Python

Tue, 2024-04-09 10:00

From restaurant e-menus to airline boarding passes, QR codes have numerous applications that impact your day-to-day life and enrich the user’s experience. Wouldn’t it be great to make them look good, too? With the help of this video course, you’ll learn how to use Python to generate beautiful QR codes for your personal use case.

In its most basic format, a QR code contains black squares and dots on a white background, with information that any smartphone or device with a dedicated QR scanner can decode. Unlike a traditional bar code, which holds information horizontally, a QR code holds the data in two dimensions, and it can hold over a hundred times more information.

In this video course, you’ll learn how to:

  • Generate a basic black-and-white QR code
  • Change the size and margins of the QR code
  • Create colorful QR codes
  • Rotate the QR code
  • Replace the static background with an animated GIF

[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

Categories: FLOSS Project Planets

Python Bytes: #378 Python is on the edge

Tue, 2024-04-09 04:00
<strong>Topics covered in this episode:</strong><br> <ul> <li><a href=""><strong>pacemaker</strong></a> - For controlling time per iteration loop in Python.</li> <li><a href="">PyPI suspends new user registration to block malware campaign</a></li> <li><a href=""><strong>Python Project-Local Virtualenv Management Redux</strong></a></li> <li><a href="">Python Edge Workers at Cloudflare</a></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="378">Watch on YouTube</a><br> <p><strong>About the show</strong></p> <p>Sponsored by us! Support our work through:</p> <ul> <li>Our <a href=""><strong>courses at Talk Python Training</strong></a></li> <li><a href=""><strong>The Complete pytest Course</strong></a></li> <li><a href=""><strong>Patreon Supporters</strong></a></li> </ul> <p><strong>Connect with the hosts</strong></p> <ul> <li>Michael: <a href=""><strong></strong></a></li> <li>Brian: <a href=""><strong></strong></a></li> <li>Show: <a href=""><strong></strong></a></li> </ul> <p>Join us on YouTube at <a href=""><strong></strong></a> to be part of the audience. Usually Tuesdays at 11am PT. Older video versions available there too.</p> <p>Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to <a href="">our friends of the show list</a>, we'll never share it.</p> <p><strong>Brian #1:</strong> <a href=""><strong>pacemaker</strong></a> - For controlling time per iteration loop in Python.</p> <ul> <li>Brandon Rohrer</li> <li>Good example of a small bit of code made into a small package.</li> <li>With speedups to dependencies, like with uv, for example, I think we’ll see more small projects.</li> <li>Cool stuff <ul> <li>Great README, including quirks that need to be understood by users. <ul> <li>“If the pacemaker experiences a delay, it will allow faster iterations to try to catch up. Heads up: because of this, any individual iteration might end up being much shorter than suggested by the pacemaker's target rate.”</li> </ul></li> <li>Nice use of <a href="">time.monotonic()</a> <ul> <li>deltas are guaranteed to never go back in time regardless of what adjustments are made to the system clock.</li> </ul></li> </ul></li> <li>Watch out for <ul> <li>pip install pacemaker-lite <ul> <li>NOT pacemaker</li> <li>pacemaker is taken by a package named PaceMaker with a repo named pace-maker, that hasn’t been updated in 3 years. Not sure if it’s alive. </li> </ul></li> <li>No tests (yet). I’m sure they’re coming. ;) <ul> <li>Seriously though, Brandon says this is “a glorified snippet”. And I love the use of packaging to encapsulate shared code. Realistically, small snippet like packages have functionality that’s probably going to be tested by end user code.</li> <li>And even if there are tests, users should test the functionality they are depending on.</li> </ul></li> </ul></li> </ul> <p><strong>Michael #2:</strong> <a href="">PyPI suspends new user registration to block malware campaign</a></p> <ul> <li><a href="">Incident Report for Python Infrastructure</a></li> <li><a href="">PyPi Is Under Attack: Project Creation and User Registration Suspended — Here’s the details</a> <ul> <li>I hate medium, but it’s the best details I’ve found so far</li> </ul></li> </ul> <p><strong>Brian #3:</strong> <a href=""><strong>Python Project-Local Virtualenv Management Redux</strong></a></p> <ul> <li>Hynek</li> <li>Concise writeup of how Hynek uses various tools for dealing with environments</li> <li>Covers (paren notes are from Brian) <ul> <li>In project .venv directories</li> <li>direnv for handling .envrc files per project (time for me to try this again)</li> <li>uv for pip and pip-compile functionality</li> <li>Installing Python via</li> <li>Using a .python-version-default file (I’ll need to play with this a bit) <ul> <li>Works with GH Action setup-python. (ok. that’s cool)</li> </ul></li> <li>Some fish shell scripting</li> <li>Bonus tip on using requires-python in .pyproject.toml and extracting it in GH actions to be able to get the python exe name, and then be able to pass it to Docker and reference it in a Dockerfile. (very cool)</li> </ul></li> </ul> <p><strong>Michael #4:</strong> <a href="">Python Edge Workers at Cloudflare</a></p> <ul> <li>What are <a href="">edge workers</a>?</li> <li>Based on workers using Pyodide and WebAssembly</li> <li>This new support for Python is different from how Workers have historically supported languages beyond JavaScript — in this case, we have directly integrated a Python implementation into <a href="">workerd</a>, the open-source Workers runtime.</li> <li>Python Workers can import a subset of popular Python <a href="">packages</a> including <a href="">FastAPI</a>, <a href="">Langchain</a>, <a href="">numpy</a></li> <li>Check out the <a href="">examples repo</a>.</li> </ul> <p><strong>Extras</strong> </p> <p>Michael:</p> <ul> <li><a href="">LPython follow up</a> from Brian Skinn</li> <li><a href="">Featured on Python Bytes badge</a></li> <li><a href="">A little downtime</a>, thanks for the understanding <ul> <li>We were rocking a <a href="">99.98% uptime</a> until then. :)</li> </ul></li> </ul> <p><strong>Joke:</strong> </p> <ul> <li><a href="">C++ is not safe for people under 18</a></li> <li>Baseball joke</li> </ul>
Categories: FLOSS Project Planets