Planet Python

Subscribe to Planet Python feed
Planet Python - http://planetpython.org/
Updated: 15 hours 30 min ago

TechBeamers Python: 20 Challenging Developer Pseudo Code Questions and Answers

Sat, 2024-02-10 04:17

Welcome to an advanced tutorial designed for developers, focusing on challenging algorithmic pseudo-code questions. It doesn’t matter if you are a C#, .NET, Python, C, or Java developer. In this guide, we will explore questions that will push your problem-solving abilities and encourage creative thinking. We’ve categorized them into four sections: Advanced Logic, Complex String […]

The post 20 Challenging Developer Pseudo Code Questions and Answers appeared first on TechBeamers.

Categories: FLOSS Project Planets

TechBeamers Python: Exploring Python Sets vs. Lists: When to Use Each

Fri, 2024-02-09 12:21

Welcome to this tutorial on Python Sets vs Lists. As a programmer, understanding the differences between sets and lists is essential for writing efficient and clean code. In this tutorial, we’ll dive into the characteristics of sets and lists, explore their use cases, and help you make informed decisions when choosing between them. The Difference […]

The post Exploring Python Sets vs. Lists: When to Use Each appeared first on TechBeamers.

Categories: FLOSS Project Planets

TechBeamers Python: Understanding Python Generators vs. List Comprehensions

Fri, 2024-02-09 11:38

This tutorial discusses and compares Python Generators vs List Comprehensions. If you’ve been coding in Python for a while, you’ve likely encountered these two powerful features. They both play crucial roles in making your code more efficient and readable. In this tutorial, we’ll explore what Generators and List Comprehensions are, how they differ, and when […]

The post Understanding Python Generators vs. List Comprehensions appeared first on TechBeamers.

Categories: FLOSS Project Planets

PyBites: Leveraging typing.Protocol: Faster Error Detection And Beyond Inheritance

Fri, 2024-02-09 10:52
Introduction

Two weeks ago I wrote an article about ABCs and interface enforcement. Shortly after I learned that you can do this as well with protocols.

Python 3.8 introduced quite a groundbreaking feature that further advanced the language’s capabilities in type checking: the typing.Protocol which allows Python developers to define and enforce interface contracts in their code.

Unlike traditional dynamic typing, which Python is well-known for, typing.Protocol brings a layer of static type checking that allows for more explicit, readable, and robust code by defining expected behaviors and structures.

This feature not only enhances code quality and developer productivity but also aligns Python more closely with the benefits seen in statically-typed languages, all while maintaining Python’s expressive and dynamic nature.

In this article, we delve into the potential of typing.Protocol through the lens of refactoring the Pybites Search feature, showcasing the immediate benefits of early error detection.

Before we dive in, in case you missed last article, let’s first clarify what “enforcing an interface” actually means …

What is interface enforcement?

Interface enforcement in object-oriented programming (OOP) is a design principle where you define a contract or a blueprint for what methods and properties should be present in a class.

This contract ensures that all classes implementing the interface adhere to a specific structure, promoting consistency and predictability across your codebase.

For example, if you define an interface for a Search functionality, every class implementing this interface must provide the specific methods defined, such as match_content.

This approach allows programmers to design components that can interact seamlessly, knowing the expected behaviors and data types, thereby reducing errors and improving code maintainability.

The ABC / Abstract Method Way

Remember how we implemented PyBites Search originally using Abstract Base Classes (ABCs) to enforce a consistent interface for search functionality:

from abc import ABC, abstractmethod class PybitesSearch(ABC): @abstractmethod def match_content(self, search: str) -> list: # >= 3.9 you can use list over typing.List ...

While ABCs served us well here, the interface enforcement only happens at runtime.

Static type checking vs runtime checks

Static type checking offers several advantages over runtime checks, primarily by shifting error detection earlier in the development process.

By identifying type mismatches and potential bugs during coding or at compile time, developers can address issues before they manifest in a running application, leading to more stable and reliable software.

Static type checking also enhances code readability and documentation, as type annotations provide clear expectations for variables, arguments, and return types.

This explicitness improves developer collaboration and facilitates easier maintenance and debugging of the codebase.

Moreover, static type checking can lead to performance optimizations since the interpreter or compiler can make certain assumptions about the types, potentially streamlining execution.

If you are new to Python type hints, check out our article.

Detecting Errors Earlier

Although ABCs and abstract methods enforce an interface, one limitation is that errors can slip through into production.

What if we can detect interface contract breaches earlier?

Enter typing.Protocol! This new feature allows for static type checking, ensuring that not only our class implements the enforced interface, it also does it with the right method signatures.

Here’s how we could refactor Pybites Search to use this (code):

from typing import Protocol class PybitesSearchProtocol(Protocol): def match_content(self, search: str) -> list[str]: """Implement in subclass to search Pybites content""" ... class CompleteSearch: def match_content(self, search: str) -> list[str]: # Implementation of search method return ["result1", "result2"] class IncompleteSearch: # Notice that we don't implement match_content here pass def perform_search(search_tool: PybitesSearchProtocol, query: str) -> None: results = search_tool.match_content(query) print(results) # Static type checking will pass for CompleteSearch perform_search(CompleteSearch(), "Python") # Static type checking will fail for IncompleteSearch perform_search(IncompleteSearch(), "Python")

In this refactored version, PybitesSearchProtocol defines an interface using typing.Protocol.

Unlike ABCs, classes like CompleteSearch and IncompleteSearch don’t need to inherit from PybitesSearchProtocol to be considered compliant.

Instead, compliance is determined by whether a class implements the required methods, in this case, match_content.

When the perform_search function is called with an instance of CompleteSearch, static type checkers like mypy will confirm that CompleteSearch satisfies the PybitesSearchProtocol because it implements match_content.

However, passing an instance of IncompleteSearch, which lacks the match_content method, will result in a type-checking error.

This approach, using typing.Protocol, offers a more flexible way of enforcing interfaces, particularly useful in scenarios where rigid class hierarchies are undesirable or unnecessary.

It aligns well with Python’s dynamic nature while still leveraging the benefits of static type checking to ensure code correctness.

Static Type Checking

Now let’s see how this protocol is enforced as I try to implement.

Step 1: Missing Method Implementation

Initially, when a class like IncompleteSearch does not implement the required match_content method.

class IncompleteSearch: pass

Failing to implement a required method could lead to AttributeError exceptions at runtime, disrupting user experience and potentially halting application functionality.

Running mypy, it catches this error:

$ mypy script.py script.py:24: error: Argument 1 to "perform_search" has incompatible type "IncompleteSearch"; expected "PybitesSearchProtocol" [arg-type] Found 1 error in 1 file (checked 1 source file) Step 2: Incorrect Method Signature

Next we implement the method but with an incorrect signature:

class IncompleteSearch: def match_content(self): pass

Implementing a method with the wrong signature may result in a TypeError that is only caught when the specific code path is executed, risking data inconsistencies or application crashes in production environments.

Mypy catches this too:

$ mypy script.py script.py:25: error: Argument 1 to "perform_search" has incompatible type "IncompleteSearch"; expected "PybitesSearchProtocol" [arg-type] script.py:25: note: Following member(s) of "IncompleteSearch" have conflicts: script.py:25: note: Expected: script.py:25: note: def match_content(self, search: str) -> list[str] script.py:25: note: Got: script.py:25: note: def match_content(self) -> Any Found 1 error in 1 file (checked 1 source file) Step 3: Incompatible Return Type

Finally, we correct the method signature but we return an incorrect type.

class IncompleteSearch: def match_content(self, search: str) -> list[str]: return (1, 2)

Returning incorrect types from methods can lead to subtle bugs, such as incorrect data processing or application logic failures, which may not be immediately apparent and could lead to significant issues over time.

Once again, mypy flags it:

$ mypy script.py script.py:15: error: Incompatible return value type (got "tuple[int, int]", expected "list[str]") [return-value] Found 1 error in 1 file (checked 1 source file)

Incorporating typing.Protocol into our Python codebase not only facilitates earlier error detection and circumvents the inheritance requirement but also subtly enforces the Liskov Substitution Principle (LSP).

By ensuring that objects are replaceable with instances of their subtypes without altering the correctness of the program, typing.Protocol aids in creating more reliable and maintainable code.

This alignment with SOLID principles highlights the broader impact of adopting modern type checking in Python, enhancing both code quality and developer productivity.

Enhancing Protocols with Runtime Checkability

To bridge the gap between static type checking and runtime flexibility, Python’s typing module includes the @typing.runtime_checkable decorator.

This feature allows protocols to be used in runtime type checks, similar to abstract base classes.

Applying @typing.runtime_checkable to a protocol makes it possible to use isinstance() and issubclass() checks, offering a layer of dynamic validation that complements static type checking.

However, it’s important to note the limitation: while runtime checkable protocols can verify the presence of required methods at runtime, they do not validate method signatures or return types as static type checking does.

This offers a practical yet limited approach to enforcing interface contracts dynamically, providing developers with additional tools to ensure their code’s correctness and robustness.

One practical use case for @typing.runtime_checkable is in developing plugins or extensions. When you’re designing an API where third-party developers can provide plugin implementations, runtime checks can be used to verify that an object passed to your API at runtime correctly implements the expected interface.

This can be especially useful in dynamically loaded modules or plugins where static type checks might not catch mismatches. For example, before invoking plugin-specific methods, you might use isinstance(plugin, PluginProtocol) to ensure that the plugin adheres to your defined protocol, enhancing reliability and reducing the risk of runtime errors.

Structural Subtyping and Embracing Python’s Duck Typing

The concept of structural subtyping, formalized through typing.Protocol, is a testament to Python’s commitment to flexibility and the “duck typing” philosophy.

Structural subtyping allows a class to be considered a subtype of another if it meets certain criteria, specifically if it has all the required methods and properties, irrespective of the inheritance relationship.

This approach enables developers to design more generic, reusable components that adhere to specified interfaces without being bound by a strict class hierarchy.

It essentially allows objects to be used based on their capabilities rather than their specific types, echoing the Pythonic saying, “If it looks like a duck and quacks like a duck, it’s a duck.”

By leveraging typing.Protocol, Python developers can enjoy the benefits of static type checking while maintaining the language’s dynamic, expressive nature, ensuring code is both flexible and type-safe.

Further reading
  • Python typing Module Documentation: Dive into the official Python documentation for an in-depth look at type hints. This guide covers everything from basic annotations to advanced features like typing.Protocol, equipping you with the knowledge to write clearer and more maintainable Python code.
  • PEP 544 – Protocols: Structural subtyping (static duck typing): Explore the proposal that introduced typing.Protocol to Python. This document provides valuable context on the motivation behind protocols, detailed examples, and insights into how they enhance Python’s type system, making it an essential read for developers interested in type checking and Python’s design philosophy.
  • Building Implicit Interfaces in Python with Protocol Classes: This article offers a practical approach to using typing.Protocol for defining and implementing interfaces in Python. It’s perfect for readers looking for actionable advice and examples on how to leverage protocols in their projects.
  • Robust Python: Patrick Viafore’s book is a treasure trove of information on Python’s typing system, including a dedicated chapter on typing.Protocol. It’s a great resource for those seeking to deepen their understanding of Python’s type hints and how to use them effectively to write robust, error-resistant code.
Conclusion

Adopting typing.Protocol in the PyBites Search feature showcased not just an alternative to ABCs and abstract methods, but also illuminated a path toward more proactive error management and a deeper alignment with Python’s dynamic and flexible design ethos.

By embracing typing.Protocol, we not only enhance our code’s reliability through early error detection but also open the door to more robust design patterns that leverage Python’s strengths in readability and expressiveness.

The exploration of runtime checkability with @typing.runtime_checkable and the discussion around structural subtyping and duck typing further underscore the versatile and powerful nature of Python’s type system.

These features collectively foster a development environment where code is not just correct, but also elegantly aligned with the principles of modern software design.

As Python continues to evolve, tools like typing.Protocol are invaluable for developers aiming to write high-quality, maintainable code that adheres to both the letter and spirit of Python’s design philosophy.

Categories: FLOSS Project Planets

TechBeamers Python: 20 Practical Pandas Tips and Tricks for Python

Fri, 2024-02-09 10:51

Welcome to this Python tutorial including Pandas tips and tricks! In this guide, we’ll share 20 practical techniques to make your data tasks easier and improve your Python data analysis. Whether you’re new or experienced, these tips will help you become more efficient in using Pandas for data manipulation. Let’s dive in and explore the […]

The post 20 Practical Pandas Tips and Tricks for Python appeared first on TechBeamers.

Categories: FLOSS Project Planets

Real Python: The Real Python Podcast – Episode #191: Focusing on Data Science & Less on Engineering and Dependencies

Fri, 2024-02-09 07:00

How do you manage the dependencies of a large-scale data science project? How do you migrate that project from a laptop to cloud infrastructure or utilize GPUs and multiple instances in parallel? This week on the show, Savin Goyal returns to discuss the updates to the open-source framework Metaflow.

[ 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 People: Nikita Karamov - Django project maintainer, from Russia to Germany

Thu, 2024-02-08 14:08

Nikita Karamov is a Python developer and maintainer on various open source Python projects.

Some topics covered:

  • Notes on university education in programming and engineering vs theory
  • Jazzband for maintaining Django projects
  • Contributing to open source makes you a better programmer
  • Moving from Russia to Germany during college
  • Cultural differences between Russia, Germany, and Oregon
  • The nice lack of drama in the Python community
  • A lack of universities teaching Python for web development


Links from the show


The Complete pytest Course

★ Support this podcast on Patreon ★ <p>Nikita Karamov is a Python developer and maintainer on various open source Python projects.</p><p>Some topics covered:</p><ul><li>Notes on university education in programming and engineering vs theory</li><li>Jazzband for maintaining Django projects</li><li>Contributing to open source makes you a better programmer</li><li>Moving from Russia to Germany during college</li><li>Cultural differences between Russia, Germany, and Oregon</li><li>The nice lack of drama in the Python community</li><li>A lack of universities teaching Python for web development</li></ul><p><br>Links from the show</p><ul><li><a href="https://jazzband.co">Jazzband</a></li><li><a href="https://jazzband.co/projects/django-simple-menu">django-simple-menu</a></li></ul> <br><p><strong>The Complete pytest Course</strong></p><ul><li>Level up your testing skills and save time during coding and maintenance.</li><li>Check out <a href="https://courses.pythontest.com/p/complete-pytest-course">courses.pythontest.com</a></li></ul> <strong> <a href="https://www.patreon.com/PythonPeople" rel="payment" title="★ Support this podcast on Patreon ★">★ Support this podcast on Patreon ★</a> </strong>
Categories: FLOSS Project Planets

PyCharm: PyCharm 2024.1 EAP 4: Sticky Lines, and More

Thu, 2024-02-08 12:44

The Early Access Program for PyCharm 2024.1 continues with our latest build where you can preview new features, including convenient sticky lines in the editor, and more.

You can download the new version from our website, update directly from the IDE or via the free Toolbox App, or use snaps for Ubuntu.

Download PyCharm 2024.1 EAP

Learn about the key highlights below.

User experience Sticky lines in the editor

To simplify working with large files and exploring new codebases, we’ve introduced sticky lines in the editor. This feature keeps key structural elements, like the beginnings of classes or methods, pinned to the top of the editor as you scroll. This way, scopes always remain in view, and you can promptly navigate through the code by clicking on a pinned line.

As of PyCharm 2024.1, this feature will be enabled by default. You can disable it via a checkbox in Settings/Preferences | Editor | General | Appearance, where you can also set the maximum number of pinned lines.

Version control systems Option to display review branch changes in a Git Log tab

IntelliJ IDEA 2024.1 EAP 4 streamlines the code review workflow by offering a focused view of branch-related changes. For GitHub, GitLab, and Space, it is now possible to see changes in a certain branch in a separate Log tab within the Git tool window. To do so, click on the branch name in the Pull Requests tool window and pick Show in Git Log from the menu.

These are the most notable improvements in the latest PyCharm 2024.1 EAP build. Explore the full list of changes in the release notes, and stay tuned for more updates next week.

We encourage you to share your opinion about the newest additions in the comments below or by contacting us on X (formerly Twitter). If you spot a bug, please report it via our issue tracker.

Categories: FLOSS Project Planets

TechBeamers Python: Python’s Map() and List Comprehension: Tips and Best Practices

Thu, 2024-02-08 11:20

Welcome to this tutorial where we will explore Python map() and List Comprehension best practices and share some cool coding tips. These techniques, when mastered, can make your code cleaner, more concise, and efficient. In this guide, we’ll explore these concepts from the ground up, using simple and practical examples. Let’s begin mastering the map […]

The post Python’s Map() and List Comprehension: Tips and Best Practices appeared first on TechBeamers.

Categories: FLOSS Project Planets

TechBeamers Python: 20 Practical Python Data Analysis Tips and Tricks

Thu, 2024-02-08 09:00

Hey, welcome! Today, we’re talking about practical Python data analysis tips and tricks. With Pandas and NumPy, we’ll tidy up data, spot trends, and do some smart analysis. It’s all about making your data work easier and getting cool insights. Let’s dive in to optimize your data analysis tasks. Practical Python Tips for Solid Data […]

The post 20 Practical Python Data Analysis Tips and Tricks appeared first on TechBeamers.

Categories: FLOSS Project Planets

Python Software Foundation: Software Bill-of-Materials documents are now available for CPython

Thu, 2024-02-08 05:53

Our Security Developer-in-Residence, Seth Larson, has been working to improve the management of vulnerabilities for Python users. Seth has championed progress on this goal in a variety of areas:

With the release of CPython 3.12.2, the next step of the Python Software Foundation’s vulnerability management strategy is now available in the form of Software Bill-of-Materials (SBOM) documents for CPython source releases. The documents are available for download in their own column labeled “SBOM” in the “Files” table on the release page. User documentation and a getting started guide for CPython SBOMs is available on python.org.

These documents are relatively new but have been tested with multiple tools that accept SPDX SBOM documents. Please report any feedback on the SBOM to the CPython issue tracker.

What is a Software Bill-of-Materials (SBOM)?

Software Bill-of-Materials are machine-readable documents using an ecosystem-independent format like SPDX or CycloneDX to describe what a piece of software is made of and how each component within the software relates to other components. There are multiple use-cases for SBOMs, but for CPython we primarily focused on software supply chain and vulnerability management.

Many vulnerability scanning tools support passing an SBOM document as input to provide a comprehensive scan for software vulnerabilities without needing to rely on fallible software discovery. This means there’s less chances for vulnerabilities to be missed by scanners.

There are existing tools for automatically creating SBOMs for software, but SBOMs which aren’t accurate are sometimes more dangerous than having no SBOM due to causing a false sense of security. This is especially true for complex pieces of software or projects which exist outside of package ecosystems, both of which apply to CPython and make generating an SBOM difficult. For this reason the content of CPython SBOMs is curated by hand on first pass to ensure accuracy and completeness and then automated to track updates as the software changes.

SBOM documents are becoming a requirement for compliance in multiple areas and industries. In order to meet those requirements we are providing a comprehensive and accurate SBOM for CPython that will provide assurance for Python users.

What is included in CPython SBOMs?

CPython SBOMs use the SPDX SBOM standard. SBOM documents include a description of the contained software, including all of its dependencies. Information in CPython SBOMs includes:

  • Names and versions of all software components
  • Software identifiers (like CPE and Package URLs)
  • Download URLs for source code with checksums
  • File names and content checksums
  • Dependency relationships between each component

CPython SBOMs satisfy the requirements listed in the NTIA Minimum Elements for a Software Bill of Materials. Software identifiers can be used for correlating software in use to vulnerability databases like the CVE database and Open Source Vulnerability database, typically done automatically using vulnerability scanning tools.

What isn’t included in CPython SBOMs?

Keep in mind that software libraries that you supply yourself to compile CPython, such as OpenSSL and zlib, are not included in the SBOMs for source artifacts.

This is due to these libraries not being included in source artifacts, so CPython users have a choice of which version and sources to use for these third-party libraries. Folks who are compiling CPython from source are responsible for tracking their own dependencies either in a separate SBOM document or by appending new entries to your local CPython SBOM.

CPython’s SBOMs don’t include licensing information for dependencies. See the CPython licensing page for licensing information.

What is coming next for CPython SBOMs?

This is only the beginning for CPython SBOMs, as mentioned above there are only SBOM documents published for source releases today. The CPython release managers also publish binary installers for Windows and macOS on a variety of distribution channels. These artifacts will need their own SBOM documents as they are compiled with software that’s typically not available on those platforms (e.g. OpenSSL).

There’s also more infrastructure needed to reduce noise and churn for Python users and Python Security Response Team members alike. Vulnerability EXchange (VEX) statements are a set of standards which allows software producers to signal to user tooling whether a piece of software in use is affected by a vulnerability, even for vulnerabilities affecting dependencies. This is an area of active development and is being explored alongside the OpenSSF Security Tooling Working Group.

The Security Developer-in-Residence role and this work is funded by a substantial investment from the OpenSSF Alpha-Omega Project. Thanks to Alpha-Omega for their support in improving the security posture of the entire Python ecosystem.The OpenSSF is a non-profit cross-industry collaboration that unifies security initiatives and brings together leaders to improve the security of open source software by building a broader community, targeted initiatives, and best practices.

Categories: FLOSS Project Planets

Talk Python to Me: #448: Full-Time Open Source Devs Panel

Thu, 2024-02-08 03:00
So you've created a Python-based open source project and it's started to take off. You're getting contributors, lots of buzz in the podcast space, and more. But you have that day job working on Java. How do you make the transition from popular hobby project to full time job? After all, you are giving away your open source project for free, right? Well, on this episode, I have put together an amazing panel of guests who all have done exactly this: Turned their project into full time work and even companies in some cases. We have Samuel Colvin, Gina Häußge, Sebastián Ramírez, Charlie Marsh, Will McGugan and Eric Holscher on to share their stories.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/basedash'>Basedash</a><br> <a href='https://talkpython.fm/sentry-monorepo'>Sentry Error Monitoring, Code TALKPYTHON</a><br> <a href='https://talkpython.fm/training'>Talk Python Courses</a><br/> <br/> <strong>Links from the show</strong><br/> <br/> <div><b>Will McGugan</b>: <a href="https://twitter.com/willmcgugan" target="_blank" rel="noopener">@willmcgugan</a><br/> <b>Charlie Marsh</b>: <a href="https://hachyderm.io/@charliermarsh" target="_blank" rel="noopener">@charliermarsh@hachyderm</a><br/> <b>Sebastián Ramírez</b>: <a href="https://twitter.com/tiangolo" target="_blank" rel="noopener">@tiangolo</a><br/> <b>Samuel Colvin</b>: <a href="https://twitter.com/samuel_colvin" target="_blank" rel="noopener">@samuel_colvin</a><br/> <b>Gina on Mastodon</b>: <a href="https://chaos.social/@foosel" target="_blank" rel="noopener">chaos.social/@foosel</a><br/> <b>Eric Holscher</b>: <a href="https://twitter.com/ericholscher" target="_blank" rel="noopener">@ericholscher</a><br/> <br/> <b>Pydantic</b>: <a href="https://pydantic.dev" target="_blank" rel="noopener">pydantic.dev</a><br/> <b>Astral (makes of Ruff)</b>: <a href="https://astral.sh" target="_blank" rel="noopener">astral.sh</a><br/> <b>Octoprint</b>: <a href="https://octoprint.org" target="_blank" rel="noopener">octoprint.org</a><br/> <b>Read the Docs</b>: <a href="https://about.readthedocs.com/" target="_blank" rel="noopener">readthedocs.com</a><br/> <b>FastAPI</b>: <a href="https://fastapi.tiangolo.com" target="_blank" rel="noopener">fastapi.tiangolo.com</a><br/> <b>Textual (makes of Rich)</b>: <a href="https://www.textualize.io" target="_blank" rel="noopener">textualize.io</a><br/> <b>Watch this episode on YouTube</b>: <a href="https://www.youtube.com/watch?v=HV1LKitAr44" target="_blank" rel="noopener">youtube.com</a><br/> <b>Episode transcripts</b>: <a href="https://talkpython.fm/episodes/transcript/448/full-time-open-source-devs-panel" target="_blank" rel="noopener">talkpython.fm</a><br/> <br/> <b>--- Stay in touch with us ---</b><br/> <b>Subscribe to us on YouTube</b>: <a href="https://talkpython.fm/youtube" target="_blank" rel="noopener">youtube.com</a><br/> <b>Follow Talk Python on Mastodon</b>: <a href="https://fosstodon.org/web/@talkpython" target="_blank" rel="noopener"><i class="fa-brands fa-mastodon"></i>talkpython</a><br/> <b>Follow Michael on Mastodon</b>: <a href="https://fosstodon.org/web/@mkennedy" target="_blank" rel="noopener"><i class="fa-brands fa-mastodon"></i>mkennedy</a><br/></div>
Categories: FLOSS Project Planets

Matt Layman: Stripe Checkout - Building SaaS with Python and Django #182

Wed, 2024-02-07 19:00
In this episode, we did work to get the Stripe checkout session going. We set up Stripe Product and Price objects to get the subscription plan ready and got the Stripe checkout session working mostly end-to-end
Categories: FLOSS Project Planets

Seth Michael Larson: CPython 3.12.2 is SBOM-ified!

Wed, 2024-02-07 19:00
CPython 3.12.2 is SBOM-ified! AboutBlogNewsletterLinks CPython 3.12.2 is SBOM-ified!

Published 2024-02-08 by Seth Larson
Reading time: minutes

This critical role would not be possible without funding from the OpenSSF Alpha-Omega project. Massive thank-you to Alpha-Omega for investing in the security of the Python ecosystem! SBOM for CPython source artifacts

CPython 3.12.2 is the first release that has SBOMs for source artifacts 🥳 There's an announcement for the PSF blog, so go read that first!

Work items for SBOMs that got worked on this week:

After consulting with legal help regarding the licensing questions that came up during SBOM development I opted to change all licenseConcluded fields for dependencies in the SBOM to NOASSERTION as the primary use-case for CPython's SBOMs was for supply chain and vulnerability management.

Next steps for this project include investigating SBOMs for Windows installers and continuing to learn about Vulnerability Exchange (VEX) and how it can be applied to CPython SBOMs.

Other items
  • My work on the libwebp vulnerability was referenced at a FOSDEM talk by Henrik Plate of Endor Labs.
  • Got access to CDN logs for python.org to track downloads of SBOMs and Sigstore artifacts.
  • Got up to speed with the effort to switch mail protocols (SMTP, IMAP, and POP3) and FTP in the standard library to verify certificates by default.
  • Had both of my PyCon US 2024 talk proposals declined, but will be attending regardless (look forward to a security-focused open space!)
  • Attended the Alpha-Omega monthly community meeting.
  • 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

Mike Driscoll: Episode 27 – Python Formatters with Lukasz Langa

Wed, 2024-02-07 13:30

Episode 27 of The Python Show Podcast welcomes Lukasz Langa as our guest.

Lukasz is a CPython Developer in Residence, which means he works full-time on the core CPython language. He is also the creator of Black, a super popular Python code formatter.

In this episode, we talked about the following topics:

  • Core python development
  • The origin of Black
  • YAPF and Ruff
  • Python, Python, Python
  • and more!
Links

You can follow or connect with ?ukasz at the following sites:

The post Episode 27 – Python Formatters with Lukasz Langa appeared first on Mouse Vs Python.

Categories: FLOSS Project Planets

Real Python: How to Write Beautiful Python Code With PEP 8

Wed, 2024-02-07 09:00

PEP 8, sometimes spelled PEP8 or PEP-8, is a document that provides guidelines and best practices on how to write Python code. It was written in 2001 by Guido van Rossum, Barry Warsaw, and Alyssa Coghlan. The primary focus of PEP 8 is to improve the readability and consistency of Python code.

By the end of this tutorial, you’ll be able to:

  • Write Python code that conforms to PEP 8
  • Understand the reasoning behind the guidelines laid out in PEP 8
  • Set up your development environment so that you can start writing PEP 8 compliant Python code

PEP stands for Python Enhancement Proposal, and there are many PEPs. These documents primarily describe new features proposed for the Python language, but some PEPs also focus on design and style and aim to serve as a resource for the community. PEP 8 is one of these style-focused PEPs.

In this tutorial, you’ll cover the key guidelines laid out in PEP 8. You’ll explore beginner to intermediate programming topics. You can learn about more advanced topics by reading the full PEP 8 documentation.

Get Your Code Click here to download the free sample code that shows you how to write PEP 8 compliant code.

Why We Need PEP 8

“Readability counts.”

— The Zen of Python

PEP 8 exists to improve the readability of Python code. But why is readability so important? Why is writing readable code one of the guiding principles of the Python language, according to the Zen of Python?

Note: You may encounter the term Pythonic when the Python community refers to code that follows the idiomatic writing style specific to Python. Pythonic code adheres to Python’s design principles and philosophy and emphasizes readability, simplicity, and clarity.

As Guido van Rossum said, “Code is read much more often than it’s written.” You may spend a few minutes, or a whole day, writing a piece of code to process user authentication. Once you’ve written it, you’re never going to write it again.

But you’ll definitely have to read it again. That piece of code might remain part of a project you’re working on. Every time you go back to that file, you’ll have to remember what that code does and why you wrote it, so readability matters.

It can be difficult to remember what a piece of code does a few days, or weeks, after you wrote it.

If you follow PEP 8, you can be sure that you’ve named your variables well. You’ll know that you’ve added enough whitespace so it’s easier to follow logical steps in your code. You’ll also have commented your code well. All of this will mean your code is more readable and easier to come back to. If you’re a beginner, following the rules of PEP 8 can make learning Python a much more pleasant task.

Note: Following PEP 8 is particularly important if you’re looking for a development job. Writing clear, readable code shows professionalism. It’ll tell an employer that you understand how to structure your code well.

If you have more experience writing Python code, then you may need to collaborate with others. Writing readable code here is crucial. Other people, who may have never met you or seen your coding style before, will have to read and understand your code. Having guidelines that you follow and recognize will make it easier for others to read your code.

Naming Conventions

“Explicit is better than implicit.”

— The Zen of Python

When you write Python code, you have to name a lot of things: variables, functions, classes, packages, and so on. Choosing sensible names will save you time and energy later. You’ll be able to figure out, from the name, what a certain variable, function, or class represents. You’ll also avoid using potentially confusing names that might result in errors that are difficult to debug.

One suggestion is to never use l, O, or I single letter names as these can be mistaken for 1 and 0, depending on what typeface a programmer uses.

For example, consider the code below, where you assign the value 2 to the single letter O:

Python O = 2 # ❌ Not recommended Copied!

Doing this may look like you’re trying to reassign 2 to zero. While making such a reassignment isn’t possible in Python and will cause a syntax error, using an ambigious variable name such as O can make your code more confusing and harder to read and reason about.

Naming Styles

The table below outlines some of the common naming styles in Python code and when you should use them:

Read the full article at https://realpython.com/python-pep8/ »

[ 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

PyBites: How to Send Email Notifications Using Sendgrid and GitHub Actions

Wed, 2024-02-07 08:05
Introduction

As a Python developer I want to stay up2date with trends and useful tips & tricks.

Of course there are great newsletters like Pycoders, but those are already hitting my inbox.

Let’s look at Planet Python in this article, an aggregation site/ service that indexes a lot of good Python blog feeds. Keeping an eye on that resource will be useful.

In this article we’ll build a tool to parse this resource and get a daily email with new articles.

We will use Sendgrid for the emailing and GitHub Actions to run it automatically.

Planet Python parse and email script

Here is the script I came up with:

from datetime import datetime, timedelta, UTC from typing import NamedTuple import feedparser from dateutil.parser import parse from decouple import config from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Mail SENDGRID_API_KEY = config("SENDGRID_API_KEY") FROM_EMAIL = config("FROM_EMAIL") TO_EMAIL = config("TO_EMAIL") PLANET_PYTHON_FEED = "https://planetpython.org/rss20.xml" ONE_DAY = 1 class Article(NamedTuple): title: str link: str publish_date: str def fetch_articles() -> list[Article]: feed = feedparser.parse(PLANET_PYTHON_FEED) return [ Article(entry.title, entry.link, entry.published) for entry in feed.entries ] def filter_recent_articles( articles: list[Article], days: int = ONE_DAY ) -> list[Article]: recent_articles = [] now = datetime.now(UTC) for article in articles: publish_date = parse(article.publish_date) if now - publish_date <= timedelta(days): recent_articles.append(article) return recent_articles def send_email( from_email: str, to_email: str, subject: str, content: str ) -> None: message = Mail( from_email=from_email, to_emails=to_email, subject=subject, html_content=content, ) try: sg = SendGridAPIClient(SENDGRID_API_KEY) response = sg.send(message) print(f"Email sent with status code: {response.status_code}") except Exception as e: print(e) def main() -> None: articles = fetch_articles() recent_articles = filter_recent_articles(articles) if len(recent_articles) == 0: print("No new articles found") return subject = "New Planet Python articles" def _create_link(article: Article) -> str: return f"<a href='{article.link}'>{article.title}</a>" body = "<br>".join( [_create_link(article) for article in recent_articles] ) send_email(FROM_EMAIL, TO_EMAIL, subject, body) if __name__ == "__main__": main()

Explanation:

Environment Configuration: I use decouple (python-decouple package, related article here) to securely manage environment variables, including the SendGrid API key and email addresses for both sender and receiver.

RSS Feed Parsing: I use feedparser to fetch and parse the RSS feed from Planet Python, extracting articles’ titles, links, and published dates.

Article Data Structure: I use a typed NamedTuple, Article, to store essential information about each article, including its title, link, and publish date.

Fetching Recent Articles: I created a function fetch_articles that retrieves all articles from the RSS feed and returns a list of Article instances.

Filtering by Publish Date: The filter_recent_articles function filters articles to include only those published within the last day (or a specified number of days), using dateutil.parser to handle date parsing and datetime for date comparison. Note that I later learned that you can use datetime.UTC instead of ZoneInfo, saving the import.

Email Preparation and Sending: Leverages the SendGrid API through sendgrid library to compose and send an HTML email. The email contains links to recent articles, formatted using a helper function _create_link.

Dynamic Email Content: I create the email body dynamically by converting the list of recent articles into HTML links.

By the way, my first go was text only, but that looked ugly:

Error Handling in Email Sending: Includes try-except blocks around the email sending process to catch and print any errors, enhancing reliability and debugging ease.

While our script employs a broad Exception catch for simplicity and broad coverage, understanding and implementing more granular exception handling can significantly enhance your application’s robustness and debuggability. In Python, different exceptions can be raised for various reasons, such as ValueError for an invalid parameter, or IOError for issues accessing a file. By catching exceptions more specifically, you can provide more detailed error messages, recover gracefully from certain errors, or even retry failed operations under specific conditions. For those interested in diving deeper into this topic, the Python documentation on Built-in Exceptions offers a comprehensive overview of the different exception types available. You can also check out our 7 Tips to Improve Your Error Handling in Python article.

Main Workflow Execution: The main function orchestrates the script’s workflow: fetching articles, filtering for recency, composing, and sending the email notification if new articles are found.

Conditional Execution Check: Before sending an email, checks if there are any new articles. If none are found, it prints a message and exits, avoiding unnecessary emails (while the printed message will still end up in the GitHub Action logs).

Hooking it up with a GitHub Action

Here is the GitHub workflow I created in .github/workflows (the required target directory structure for GitHub Actions):

name: Send Email Notification on: schedule: - cron: '0 7 * * *' # 9 AM CET workflow_dispatch: # also enable on button click jobs: send_email: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.11' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - name: Send email env: SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }} FROM_EMAIL: ${{ secrets.FROM_EMAIL }} TO_EMAIL: ${{ secrets.TO_EMAIL }} run: python script.py

Explanation:

Scheduled Trigger: The workflow is configured to run automatically at a specific time—7:00 AM UTC (which corresponds to 9:00 AM CET)—every day, using the cron syntax in the schedule event. This ensures the email notifications are sent out consistently without manual intervention.

Manual Trigger Option: Besides the scheduled trigger, the workflow can also be manually triggered using the workflow_dispatch event. This flexibility allows users to run the workflow at any time, independent of the scheduled time, by clicking a button in the GitHub Actions interface.

Environment Setup: The workflow runs on the latest Ubuntu runner provided by GitHub Actions (ubuntu-latest). It begins by checking out the repository code using actions/checkout@v2 and then sets up the specified version of Python (3.11) with actions/setup-python@v2, preparing the environment for script execution.

Dependency Installation: To ensure all required Python libraries are available, the workflow includes a step to upgrade pip and install dependencies from the requirements.txt file. This step is crucial for the Python script to run successfully, as it relies on external libraries such as feedparser, python-decouple, and sendgrid.

Email Sending Execution: The final step of the workflow executes the Python script (script.py) that sends out the email notifications.

This step securely accesses the SendGrid API key and email addresses (for both sender and receiver) from GitHub Secrets (secrets.SENDGRID_API_KEY, secrets.FROM_EMAIL, and secrets.TO_EMAIL), ensuring sensitive information is kept secure and not exposed in the repository.


See GitHub’s documentation how to work with secrets. So locally I work with a hidden .env file, remotely I use secrets.

Result

And voilà: this was the email I received this morning with the Planet Python entries of the last 24 hours.

Conclusion

In this article I’ve showed you a way to parse Planet Python entries, filter on the latest ones (last 24 hours), and email the results, hooking it up with a GitHub Actions.

Not only will this action run once a day via its cronjob feature, thanks to workflow_dispatch you can also run it manually from the Action tab on the GitHub repo’s page.

Through this process we’ve learned about the feedparser, python-decouple, and sendgrid Python libraries and how to manage environment config variables, both locally and on GitHub using its Secrets feature.

I hope this helps you automate more tasks and leverage GitHub Actions.

Next steps

Check out the repo here. Feel free to contribute by opening issues and/or submitting code.

I am also happy to hear your thoughts in our community.

Categories: FLOSS Project Planets

Python GUIs: Drag &amp; Drop Widgets with PyQt6 — Sort widgets visually with drag and drop in a container

Wed, 2024-02-07 08:00

I had an interesting question from a reader of my PyQt6 book, about how to handle dragging and dropping of widgets in a container showing the dragged widget as it is moved.

I'm interested in managing movement of a QWidget with mouse in a container. I've implemented the application with drag & drop, exchanging the position of buttons, but I want to show the motion of QPushButton, like what you see in Qt Designer. Dragging a widget should show the widget itself, not just the mouse pointer.

First, we'll implement the simple case which drags widgets without showing anything extra. Then we can extend it to answer the question. By the end of this quick tutorial we'll have a generic drag drop implementation which looks like the following.

Table of Contents Drag & Drop Widgets

We'll start with a simple application which creates a window using QWidget and places a series of QPushButton widgets into it.

You can substitute QPushButton for any other widget you like, e.g. QLabel. Any widget can have drag behavior implemented on it, although some input widgets will not work well as we capture the mouse events for the drag.

python from PyQt6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget class Window(QWidget): def __init__(self): super().__init__() self.blayout = QHBoxLayout() for l in ["A", "B", "C", "D"]: btn = QPushButton(l) self.blayout.addWidget(btn) self.setLayout(self.blayout) app = QApplication([]) w = Window() w.show() app.exec()

If you run this you should see something like this.

The series of QPushButton widgets in a horizontal layout.

Here we're creating a window, but the Window widget is subclassed from QWidget, meaning you can add this widget to any other layout. See later for an example of a generic object sorting widget.

QPushButton objects aren't usually draggable, so to handle the mouse movements and initiate a drag we need to implement a subclass. We can add the following to the top of the file.

python from PyQt6.QtCore import QMimeData, Qt from PyQt6.QtGui import QDrag from PyQt6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget class DragButton(QPushButton): def mouseMoveEvent(self, e): if e.buttons() == Qt.MouseButton.LeftButton: drag = QDrag(self) mime = QMimeData() drag.setMimeData(mime) drag.exec(Qt.DropAction.MoveAction)

We implement a mouseMoveEvent which accepts the single e parameter of the event. We check to see if the left mouse button is pressed on this event -- as it would be when dragging -- and then initiate a drag. To start a drag, we create a QDrag object, passing in self to give us access later to the widget that was dragged. We also must pass in mime data. This is used for including information about what is dragged, particularly for passing data between applications. However, as here, it is fine to leave this empty.

Finally, we initiate a drag by calling drag.exec_(Qt.MoveAction). As with dialogs exec_() starts a new event loop, blocking the main loop until the drag is complete. The parameter Qt.MoveAction tells the drag handler what type of operation is happening, so it can show the appropriate icon tip to the user.

You can update the main window code to use our new DragButton class as follows.

python class Window(QWidget): def __init__(self): super().__init__() self.blayout = QHBoxLayout() for l in ["A", "B", "C", "D"]: btn = DragButton(l) self.blayout.addWidget(btn) self.setLayout(self.blayout)

If you run the code now, you can drag the buttons, but you'll notice the drag is forbidden.

Dragging of the widget starts but is forbidden.

What's happening? The mouse movement is being detected by our DragButton object and the drag started, but the main window does not accept drag & drop.

To fix this we need to enable drops on the window and implement dragEnterEvent to actually accept them.

python class Window(QWidget): def __init__(self): super().__init__() self.setAcceptDrops(True) self.blayout = QHBoxLayout() for l in ["A", "B", "C", "D"]: btn = DragButton(l) self.blayout.addWidget(btn) self.setLayout(self.blayout) def dragEnterEvent(self, e): e.accept()

If you run this now, you'll see the drag is now accepted and you see the move icon. This indicates that the drag has started and been accepted by the window we're dragging onto. The icon shown is determined by the action we pass when calling drag.exec_().

Dragging of the widget starts and is accepted, showing a move icon.

Releasing the mouse button during a drag drop operation triggers a dropEvent on the widget you're currently hovering the mouse over (if it is configured to accept drops). In our case that's the window. To handle the move we need to implement the code to do this in our dropEvent method.

The drop event contains the position the mouse was at when the button was released & the drop triggered. We can use this to determine where to move the widget to.

To determine where to place the widget, we iterate over all the widgets in the layout, until we find one who's x position is greater than that of the mouse pointer. If so then when insert the widget directly to the left of this widget and exit the loop.

If we get to the end of the loop without finding a match, we must be dropping past the end of the existing items, so we increment n one further (in the else: block below).

python def dropEvent(self, e): pos = e.position() widget = e.source() self.blayout.removeWidget(widget) for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if pos.x() < w.x(): # We didn't drag past this widget. # insert to the left of it. break else: # We aren't on the left hand side of any widget, # so we're at the end. Increment 1 to insert after. n += 1 self.blayout.insertWidget(n, widget) e.accept()

The effect of this is that if you drag 1 pixel past the start of another widget the drop will happen to the right of it, which is a bit confusing. To fix this we can adjust the cut off to use the middle of the widget using if pos.x() < w.x() + w.size().width() // 2: -- that is x + half of the width.

python def dropEvent(self, e): pos = e.position() widget = e.source() self.blayout.removeWidget(widget) for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if pos.x() < w.x() + w.size().width() // 2: # We didn't drag past this widget. # insert to the left of it. break else: # We aren't on the left hand side of any widget, # so we're at the end. Increment 1 to insert after. n += 1 self.blayout.insertWidget(n, widget) e.accept()

The complete working drag-drop code is shown below.

python from PyQt6.QtCore import QMimeData, Qt from PyQt6.QtGui import QDrag from PyQt6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget class DragButton(QPushButton): def mouseMoveEvent(self, e): if e.buttons() == Qt.MouseButton.LeftButton: drag = QDrag(self) mime = QMimeData() drag.setMimeData(mime) drag.exec(Qt.DropAction.MoveAction) class Window(QWidget): def __init__(self): super().__init__() self.setAcceptDrops(True) self.blayout = QHBoxLayout() for l in ["A", "B", "C", "D"]: btn = DragButton(l) self.blayout.addWidget(btn) self.setLayout(self.blayout) def dragEnterEvent(self, e): e.accept() def dropEvent(self, e): pos = e.position() widget = e.source() self.blayout.removeWidget(widget) for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if pos.x() < w.x() + w.size().width() // 2: # We didn't drag past this widget. # insert to the left of it. break else: # We aren't on the left hand side of any widget, # so we're at the end. Increment 1 to insert after. n += 1 self.blayout.insertWidget(n, widget) e.accept() app = QApplication([]) w = Window() w.show() app.exec() Visual Drag & Drop

We now have a working drag & drop implementation. Next we'll move onto improving the UX by showing the drag visually. First we'll add support for showing the button being dragged next to the mouse point as it is dragged. That way the user knows exactly what it is they are dragging.

Qt's QDrag handler natively provides a mechanism for showing dragged objects which we can use. We can update our DragButton class to pass a pixmap image to QDrag and this will be displayed under the mouse pointer as the drag occurs. To show the widget, we just need to get a QPixmap of the widget we're dragging.

python from PyQt6.QtCore import QMimeData, Qt from PyQt6.QtGui import QDrag, QPixmap from PyQt6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget class DragButton(QPushButton): def mouseMoveEvent(self, e): if e.buttons() == Qt.MouseButton.LeftButton: drag = QDrag(self) mime = QMimeData() drag.setMimeData(mime) pixmap = QPixmap(self.size()) self.render(pixmap) drag.setPixmap(pixmap) drag.exec(Qt.DropAction.MoveAction)

To create the pixmap we create a QPixmap object passing in the size of the widget this event is fired on with self.size(). This creates an empty pixmap which we can then pass into self.render to render -- or draw -- the current widget onto it. That's it. Then we set the resulting pixmap on the drag object.

If you run the code with this modification you'll see something like the following --

Dragging of the widget showing the dragged widget.

Generic Drag & Drop Container

We now have a working drag and drop behavior implemented on our window. We can take this a step further and implement a generic drag drop widget which allows us to sort arbitrary objects. In the code below we've created a new widget DragWidget which can be added to any window.

You can add items -- instances of DragItem -- which you want to be sorted, as well as setting data on them. When items are re-ordered the new order is emitted as a signal orderChanged.

python from PyQt6.QtCore import QMimeData, Qt, pyqtSignal from PyQt6.QtGui import QDrag, QPixmap from PyQt6.QtWidgets import ( QApplication, QHBoxLayout, QLabel, QMainWindow, QVBoxLayout, QWidget, ) class DragItem(QLabel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setContentsMargins(25, 5, 25, 5) self.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setStyleSheet("border: 1px solid black;") # Store data separately from display label, but use label for default. self.data = self.text() def set_data(self, data): self.data = data def mouseMoveEvent(self, e): if e.buttons() == Qt.MouseButton.LeftButton: drag = QDrag(self) mime = QMimeData() drag.setMimeData(mime) pixmap = QPixmap(self.size()) self.render(pixmap) drag.setPixmap(pixmap) drag.exec(Qt.DropAction.MoveAction) class DragWidget(QWidget): """ Generic list sorting handler. """ orderChanged = pyqtSignal(list) def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs): super().__init__() self.setAcceptDrops(True) # Store the orientation for drag checks later. self.orientation = orientation if self.orientation == Qt.Orientation.Vertical: self.blayout = QVBoxLayout() else: self.blayout = QHBoxLayout() self.setLayout(self.blayout) def dragEnterEvent(self, e): e.accept() def dropEvent(self, e): pos = e.position() widget = e.source() self.blayout.removeWidget(widget) for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if self.orientation == Qt.Orientation.Vertical: # Drag drop vertically. drop_here = pos.y() < w.y() + w.size().height() // 2 else: # Drag drop horizontally. drop_here = pos.x() < w.x() + w.size().width() // 2 if drop_here: break else: # We aren't on the left hand/upper side of any widget, # so we're at the end. Increment 1 to insert after. n += 1 self.blayout.insertWidget(n, widget) self.orderChanged.emit(self.get_item_data()) e.accept() def add_item(self, item): self.blayout.addWidget(item) def get_item_data(self): data = [] for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() data.append(w.data) return data class MainWindow(QMainWindow): def __init__(self): super().__init__() self.drag = DragWidget(orientation=Qt.Orientation.Vertical) for n, l in enumerate(["A", "B", "C", "D"]): item = DragItem(l) item.set_data(n) # Store the data. self.drag.add_item(item) # Print out the changed order. self.drag.orderChanged.connect(print) container = QWidget() layout = QVBoxLayout() layout.addStretch(1) layout.addWidget(self.drag) layout.addStretch(1) container.setLayout(layout) self.setCentralWidget(container) app = QApplication([]) w = MainWindow() w.show() app.exec()

Generic drag-drop sorting in horizontal orientation.

You'll notice that when creating the item, you can set the label by passing it in as a parameter (just like for a normal QLabel which we've subclassed from). But you can also set a data value, which is the internal value of this item -- this is what will be emitted when the order changes, or if you call get_item_data yourself. This separates the visual representation from what is actually being sorted, meaning you can use this to sort anything not just strings.

In the example above we're passing in the enumerated index as the data, so dragging will output (via the print connected to orderChanged) something like:

python [1, 0, 2, 3] [1, 2, 0, 3] [1, 0, 2, 3] [1, 2, 0, 3]

If you remove the item.set_data(n) you'll see the labels emitted on changes.

python ['B', 'A', 'C', 'D'] ['B', 'C', 'A', 'D']

We've also implemented orientation onto the DragWidget using the Qt built in flags Qt.Orientation.Vertical or Qt.Orientation.Horizontal. This setting this allows you sort items either vertically or horizontally -- the calculations are handled for both directions.

Generic drag-drop sorting in vertical orientation.

Adding a Visual Drop Target

If you experiment with the drag-drop tool above you'll notice that it doesn't feel completely intuitive. When dragging you don't know where an item will be inserted until you drop it. If it ends up in the wrong place, you'll then need to pick it up and re-drop it again, using guesswork to get it right.

With a bit of practice you can get the hang of it, but it would be nicer to make the behavior immediately obvious for users. Many drag-drop interfaces solve this problem by showing a preview of where the item will be dropped while dragging -- either by showing the item in the place where it will be dropped, or showing some kind of placeholder.

In this final section we'll implement this type of drag and drop preview indicator.

The first step is to define our target indicator. This is just another label, which in our example is empty, with custom styles applied to make it have a solid "shadow" like background. This makes it obviously different to the items in the list, so it stands out as something distinct.

python from PyQt6.QtCore import QMimeData, Qt, pyqtSignal from PyQt6.QtGui import QDrag, QPixmap from PyQt6.QtWidgets import ( QApplication, QHBoxLayout, QLabel, QMainWindow, QVBoxLayout, QWidget, ) class DragTargetIndicator(QLabel): def __init__(self, parent=None): super().__init__(parent) self.setContentsMargins(25, 5, 25, 5) self.setStyleSheet( "QLabel { background-color: #ccc; border: 1px solid black; }" )

We've copied the contents margins from the items in the list. If you change your list items, remember to also update the indicator dimensions to match.

The drag item is unchanged, but we need to implement some additional behavior on our DragWidget to add the target, control showing and moving it.

First we'll add the drag target indicator to the layout on our DragWidget. This is hidden to begin with, but will be shown during the drag.

python class DragWidget(QWidget): """ Generic list sorting handler. """ orderChanged = pyqtSignal(list) def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs): super().__init__() self.setAcceptDrops(True) # Store the orientation for drag checks later. self.orientation = orientation if self.orientation == Qt.Orientation.Vertical: self.blayout = QVBoxLayout() else: self.blayout = QHBoxLayout() # Add the drag target indicator. This is invisible by default, # we show it and move it around while the drag is active. self._drag_target_indicator = DragTargetIndicator() self.blayout.addWidget(self._drag_target_indicator) self._drag_target_indicator.hide() self.setLayout(self.blayout)

Next we modify the DragWidget.dragMoveEvent to show the drag target indicator. We show it by inserting it into the layout and then calling .show -- inserting a widget which is already in a layout will move it. We also hide the original item which is being dragged.

In the earlier examples we determined the position on drop by removing the widget being dragged, and then iterating over what is left. Because we now need to calculate the drop location before the drop, we take a different approach.

If we wanted to do it the same way, we'd need to remove the item on drag start, hold onto it and implement re-inserting at it's old position on drag fail. That's a lot of work.

Instead, the dragged item is left in place and hidden during move.

python def dragMoveEvent(self, e): # Find the correct location of the drop target, so we can move it there. index = self._find_drop_location(e) if index is not None: # Inserting moves the item if its alreaady in the layout. self.blayout.insertWidget(index, self._drag_target_indicator) # Hide the item being dragged. e.source().hide() # Show the target. self._drag_target_indicator.show() e.accept()

The method self._find_drop_location finds the index where the drag target will be shown (or the item dropped when the mouse released). We'll implement that next.

The calculation of the drop location follows the same pattern as before. We iterate over the items in the layout and calculate whether our mouse drop location is to the left of each widget. If it isn't to the left of any widget, we drop on the far right.

python def _find_drop_location(self, e): pos = e.position() spacing = self.blayout.spacing() / 2 for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if self.orientation == Qt.Orientation.Vertical: # Drag drop vertically. drop_here = ( pos.y() >= w.y() - spacing and pos.y() <= w.y() + w.size().height() + spacing ) else: # Drag drop horizontally. drop_here = ( pos.x() >= w.x() - spacing and pos.x() <= w.x() + w.size().width() + spacing ) if drop_here: # Drop over this target. break return n

The drop location n is returned for use in the dragMoveEvent to place the drop target indicator.

Next wee need to update the get_item_data handler to ignore the drop target indicator. To do this we check w against self._drag_target_indicator and skip if it is the same. With this change the method will work as expected.

python def get_item_data(self): data = [] for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if w != self._drag_target_indicator: # The target indicator has no data. data.append(w.data) return data

If you run the code a this point the drag behavior will work as expected. But if you drag the widget outside of the window and drop you'll notice a problem: the target indicator will stay in place, but dropping the item won't drop the item in that position (the drop will be cancelled).

To fix that we need to implement a dragLeaveEvent which hides the indicator.

python def dragLeaveEvent(self, e): self._drag_target_indicator.hide() e.accept()

With those changes, the drag-drop behavior should be working as intended. The complete code is shown below.

python from PyQt6.QtCore import QMimeData, Qt, pyqtSignal from PyQt6.QtGui import QDrag, QPixmap from PyQt6.QtWidgets import ( QApplication, QHBoxLayout, QLabel, QMainWindow, QVBoxLayout, QWidget, ) class DragTargetIndicator(QLabel): def __init__(self, parent=None): super().__init__(parent) self.setContentsMargins(25, 5, 25, 5) self.setStyleSheet( "QLabel { background-color: #ccc; border: 1px solid black; }" ) class DragItem(QLabel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setContentsMargins(25, 5, 25, 5) self.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setStyleSheet("border: 1px solid black;") # Store data separately from display label, but use label for default. self.data = self.text() def set_data(self, data): self.data = data def mouseMoveEvent(self, e): if e.buttons() == Qt.MouseButton.LeftButton: drag = QDrag(self) mime = QMimeData() drag.setMimeData(mime) pixmap = QPixmap(self.size()) self.render(pixmap) drag.setPixmap(pixmap) drag.exec(Qt.DropAction.MoveAction) class DragWidget(QWidget): """ Generic list sorting handler. """ orderChanged = pyqtSignal(list) def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs): super().__init__() self.setAcceptDrops(True) # Store the orientation for drag checks later. self.orientation = orientation if self.orientation == Qt.Orientation.Vertical: self.blayout = QVBoxLayout() else: self.blayout = QHBoxLayout() # Add the drag target indicator. This is invisible by default, # we show it and move it around while the drag is active. self._drag_target_indicator = DragTargetIndicator() self.blayout.addWidget(self._drag_target_indicator) self._drag_target_indicator.hide() self.setLayout(self.blayout) def dragEnterEvent(self, e): e.accept() def dragLeaveEvent(self, e): self._drag_target_indicator.hide() e.accept() def dragMoveEvent(self, e): # Find the correct location of the drop target, so we can move it there. index = self._find_drop_location(e) if index is not None: # Inserting moves the item if its alreaady in the layout. self.blayout.insertWidget(index, self._drag_target_indicator) # Hide the item being dragged. e.source().hide() # Show the target. self._drag_target_indicator.show() e.accept() def dropEvent(self, e): widget = e.source() # Use drop target location for destination, then remove it. self._drag_target_indicator.hide() index = self.blayout.indexOf(self._drag_target_indicator) if index is not None: self.blayout.insertWidget(index, widget) self.orderChanged.emit(self.get_item_data()) widget.show() self.blayout.activate() e.accept() def _find_drop_location(self, e): pos = e.position() spacing = self.blayout.spacing() / 2 for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if self.orientation == Qt.Orientation.Vertical: # Drag drop vertically. drop_here = ( pos.y() >= w.y() - spacing and pos.y() <= w.y() + w.size().height() + spacing ) else: # Drag drop horizontally. drop_here = ( pos.x() >= w.x() - spacing and pos.x() <= w.x() + w.size().width() + spacing ) if drop_here: # Drop over this target. break return n def add_item(self, item): self.blayout.addWidget(item) def get_item_data(self): data = [] for n in range(self.blayout.count()): # Get the widget at each index in turn. w = self.blayout.itemAt(n).widget() if w != self._drag_target_indicator: # The target indicator has no data. data.append(w.data) return data class MainWindow(QMainWindow): def __init__(self): super().__init__() self.drag = DragWidget(orientation=Qt.Orientation.Vertical) for n, l in enumerate(["A", "B", "C", "D"]): item = DragItem(l) item.set_data(n) # Store the data. self.drag.add_item(item) # Print out the changed order. self.drag.orderChanged.connect(print) container = QWidget() layout = QVBoxLayout() layout.addStretch(1) layout.addWidget(self.drag) layout.addStretch(1) container.setLayout(layout) self.setCentralWidget(container) app = QApplication([]) w = MainWindow() w.show() app.exec()

If you run this example on macOS you may notice that the widget drag preview (the QPixmap created on DragItem) is a bit blurry. On high-resolution screens you need to set the device pixel ratio and scale up the pixmap when you create it. Below is a modified DragItem class which does this.

Update DragItem to support high resolution screens.

python class DragItem(QLabel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setContentsMargins(25, 5, 25, 5) self.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setStyleSheet("border: 1px solid black;") # Store data separately from display label, but use label for default. self.data = self.text() def set_data(self, data): self.data = data def mouseMoveEvent(self, e): if e.buttons() == Qt.MouseButton.LeftButton: drag = QDrag(self) mime = QMimeData() drag.setMimeData(mime) # Render at x2 pixel ratio to avoid blur on Retina screens. pixmap = QPixmap(self.size().width() * 2, self.size().height() * 2) pixmap.setDevicePixelRatio(2) self.render(pixmap) drag.setPixmap(pixmap) drag.exec(Qt.DropAction.MoveAction)

That's it! We've created a generic drag-drop handled which can be added to any projects where you need to be able to reposition items within a list. You should feel free to experiment with the styling of the drag items and targets as this won't affect the behavior.

Categories: FLOSS Project Planets

Python Software Foundation: Introducing PSF Grants Program Office Hours

Wed, 2024-02-07 06:33

In October 2023, we acknowledged the situation surrounding DjangoCon Africa and noted our intent to make ongoing improvements to the Grants Program. We also recognize that we are in a new world of hybrid programming since the onset of the pandemic which comes with different funding and cost challenges. One step we are taking to refresh the Grants Program (we’ll be reporting on other steps soon) is to establish PSF Grants Program Office Hours.

The office hours will be hosted on the Python Software Foundation Discord once a month at 2-3PM UTC (9AM Eastern) on the third Tuesday of the month. (Check what time that is for you.) We invite the Python community to join in to receive support for Grant-related questions and inquiries! If you have urgent or immediate questions related to the Grants Program, please email grants@pyfound.org.

Direct line of communication

As we sat down to address the challenges and issues raised around the Grants Program and how to better support the Python community, we came to realize that refreshing the program would not be an easy and quick task. We need a two-way communication channel dedicated to the topic of grants with the PSF Board, the Grants Working Group, the D&I Working Group, the Code of Conduct Working Group, and most importantly, our vibrant and diverse community.

We believe a direct line of communication between the PSF and the worldwide Python community is the best first step. In order to create that direct line, gather your feedback, and collaborate on the future of the program, we are establishing regular PSF Grants Program Office Hours!

What’s the goal?

There are a couple of goals we hope to accomplish with the Grants Program Office Hours. In the short term, we believe recurring time supporting communication between the community and the PSF is key. In other words, a place for folks to come with questions and ideas regarding the Grants Program, with an understanding that we don’t have it perfect yet. If we have the answer, or we can point you to the right resource - amazing! If we don’t, that’s an area we know needs more work and will be added to our “To Do.”

We hope to see the office hours evolve over time as we work through feedback and make updates to our process, documentation, and resources. In the long term, the PSF hopes Grants Program Office Hours will create a place for our community to ask questions about the Grants Program and for us to have (almost) all the answers. We’d like the office hours to continue to be a place where we receive feedback from the community and continuously improve what we can do for Pythonistas around the world.

PSF Grants Program Office Hour Hosts

The PSF Grants Program Office Hours will be hosted by members of the PSF Staff. This will change over time, but for now you can expect to see Laura Graves, Senior Accountant, and Marie Nordin, Community Communications Manager, hosting the sessions. When needed, other PSF staff members will sub in for Laura and Marie.  

This sounds great! How can I join?

The PSF Grants Program Office Hours will be a text-only chat based office hour hosted on the Python Software Foundation Discord at 2-3PM UTC (9AM Eastern) on the third Tuesday of the month. The server is moderated by PSF Staff and is locked in between office hour sessions. If you’re new to Discord, check out some Discord Basics to help you get started. And as always, if you have urgent or immediate questions related to the Grants Program, please email grants@pyfound.org.

Come prepared to the Office Hours with questions and shareable links to your Grant applications drafts in progress via Google docs, etherpad, pastebin, etc. We hope to see you there!

Categories: FLOSS Project Planets

Python Insider: Python 3.12.2 and 3.11.8 are now available.

Wed, 2024-02-07 05:07

Python 3.12.2 and 3.11.8 are now available. In addition to all the usual bugfixes, these releases contain a small security fix: hidden .pth files are no longer automatically read and executed as part of Python startup. (New releases of 3.8, 3.9 and 3.10 containing the same fix are expected next week.)  Python 3.12.2

https://www.python.org/downloads/release/python-3122/

Python 3.12’s second bugfix release. In addition to the mentioned security fix and the usual slew of bug fixes, build changes and documentation updates (more than 350 commits), this is also the first release to include a Software Bill-of-Materials for the source packages (Python-3.12.2.tgz and Python-3.12.2.tar.xz). Full changelogPython 3.11.8

https://www.python.org/downloads/release/python-3118/

More than 300 commits of bug fixes, build changes and documentation updates. Full changelog.

 

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 Wouters
on behalf of your release team,  Ned DeilySteve Dower
Pablo Galindo Salgado
Łukasz Langa
Categories: FLOSS Project Planets

Pages