Planet Python

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

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

Test and Code: 214: Python Testing in VS Code

Wed, 2024-02-07 00:20

If you haven't tried running automated tests, especially with pytest,  in VS Code recently, you should take another look.
The Python for VS Code interface for testing, especially for pytest, has changed recently. 
On this episode we discuss the change with the software engineer working on the pytest changes, Eleanor Boyd, and the product manager, Courtney Webster. 

Links from the episode:


The Complete pytest Course

<p>If you haven't tried running automated tests, especially with pytest,  in VS Code recently, you should take another look.<br>The Python for VS Code interface for testing, especially for pytest, has changed recently. <br>On this episode we discuss the change with the software engineer working on the pytest changes, Eleanor Boyd, and the product manager, Courtney Webster. </p><p>Links from the episode:</p><ul><li><a href="https://devblogs.microsoft.com/python/python-in-visual-studio-code-june-2023-release/#test-discovery-and-execution-rewrite">Blog post announcing the rewrite</a> <a href="https://devblogs.microsoft.com/python/python-in-visual-studio-code-june-2023-release/#test-discovery-and-execution-rewrite"> </a></li><li><a href="https://github.com/microsoft/vscode-python">Code repo for questions, comments, issues etc</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>
Categories: FLOSS Project Planets

Andrea Grandi: How to show a cover image in Pelican based blog posts

Tue, 2024-02-06 16:03

How to add a cover image to Pelican based blog posts, so that when the article is shared on social media the image is shown in the preview.

Categories: FLOSS Project Planets

PyCoder’s Weekly: Issue #615 (Feb. 6, 2024)

Tue, 2024-02-06 14:30

#615 – FEBRUARY 6, 2024
View in Browser »

Rye: A [Python Developer Experience] Vision Continued

Armin, who’s also the creator of Flask, discusses the continued development of Rye, a tool he created to improve the Python packaging and project management experience. Rye aims to be a “one-stop shop” for tasks like downloading Python versions, creating virtual environments, managing dependencies, linting and formatting.
ARMIN RONACHER

Starting Points for Contributing to Open Source With Python

What’s it like to sit down for your first developer sprint at a conference? How do you find an appropriate issue to work on as a new open-source contributor? This week on the show, author and software engineer Stefanie Molin is here to discuss starting to contribute to open-source projects.
REAL PYTHON podcast

Data Science Workflows: Machine Learning Models with Python & Posit Connect

Via an end-to-end example, learn how data science organizations use Posit Connect as a platform for hosting internal machine learning models. This will give you a robust pattern for hosting and sharing models on Connect before you deploy them to a customer-facing system →
POSIT sponsor

Boring Python: Dependency Management

How to set up dependency management for Python projects in a “boring” way, meaning using standard, well-established tools to minimize surprises. It recommends using pip, virtualenv, and pip-tools to define dependencies, isolate environments, and track the full dependency tree.
JAMES BENNETT

Django Admin and Service Layer

If you need to perform operations on multiple Django objects, you might create service layer functions. The problem with this is the Django Admin is linked to the objects themselves. This post covers how to add custom calls to the Admin to invoke service layer functions.
ROMAN IMANKULOV

Python’s Format Mini-Language for Tidy Strings

In this tutorial, you’ll learn about Python’s format mini-language. See how to use it for creating working format specifiers and build nicely formatted strings and messages in your code.
REAL PYTHON

Python as a Configuration Language

“Conf is not a solved problem. While TOML, YAML, and JSON are popular choices…” the author often roles his own Python using Django-like modelling concepts. Read about how they do it.
BITE CODE

Python News: What’s New From January 2024

In January 2024, Python 3.13.0a3 was released. A new JIT compiler was added to Python 3.13. The Python Software Foundation announced new developers in residence, and the Python ecosystem released new versions of projects, such as Django and pandas.
REAL PYTHON

Discussions How to Deal With Constant Interruptions at Work?

HACKER NEWS

What Python Automation Scripts Do You Reuse Frequently?

HACKER NEWS

Articles & Tutorials Python Debugging Handbook

“Programming is an art, and bugs are an inevitable part of the creative process. Every developer encounters errors in their code – there’s really no exception to it. Because of this, understanding how to effectively debug is a crucial skill that can save you time and frustration.”
SAMYAK JAIN

Handling Unset Values in FastAPI With Pydantic

When using the HTTP PATCH method only those fields that got changed are updated. Pydantic sets fields not given as arguments as None so there is no way to distinguish between an explicit None value and an unset field. This post explains how you process this scenario.
ROMAN IMANKULOV

The Morning Paper for Hacker News Readers

Want a byte-sized version of Hacker News? Try TLDR’s free daily newsletter. TLDR covers the most interesting tech, science, and coding news in just 5 minutes. No sports, politics, or weather. Subscribe for free!
TLDR sponsor

Building Enumerations With Python’s enum

Discover the art of creating and using enumerations of logically connected constants in Python. To accomplish this, you’ll explore the Enum class and other associated tools and types from the enum module in the Python standard library.
REAL PYTHON course

What Are Python Raw Strings?

In this tutorial, you’ll learn the nuances of using raw string literals in your Python source code. Raw strings offer convenient syntax for including backslash characters in string literals without the complexity of escape sequences.
REAL PYTHON

Microdot: Yet Another Python Web Framework

Miguel is the maintainer of Microdot, a web framework for Python. This post covers why he wrote another Python web framework and what distinguishes Microdot from the crowd.
MIGUEL GRINBERG

Real Life Use of Decorators

Part 3 in a series on how Python decorators are used. This part covers real-life use cases including call interception, function registration, and behavioral enrichment.
BITE CODE

Add Vue to Your Django Templates With Vite

You don’t have to build a separate app to use Vue with Django. This article talks about the Vite tool and how you can integrate Vue.js directly into your templates.
ALICE

The Python Rust-Aissance

Companies like Polars are showing how with Rust, Python developers now have a better, smoother path towards building high-performance libraries.
SLATER STICH

Mutating the Immutable Tuple (Sort Of)

“Tuples are immutable, but their contents can change. Eh?! Let me tell you the story of my neighbours moving house, and all will make sense.”
STEPHEN GRUPETTA • Shared by Stephen Gruppetta

map() vs List Comprehensions

This tutorial covers the difference between Python’s map() and using list comprehensions, with examples on how to use both.
SOUMYA AGARWAL

Django, SQLite, and the Database Is Locked Error

This post explains “database is locked” errors in Django when using SQLite, and how to solve them.
ANZE

Using Python & Poetry Inside Docker

ASHISH BHATIA

Projects & Code apple-ocr: Apple Vision Wrapper for Text Extraction and Clustering

PYPI.ORG

RustPython: OSS CPython Written in Rust

RUSTPYTHON.GITHUB.IO

granian: Rust HTTP Server for Python Applications

GITHUB.COM/EMMETT-FRAMEWORK

django-webhook: Outgoing Webhooks Triggered on Model Changes

GITHUB.COM/DANIHODOVIC

Applio: Ultimate Voice Cloning Tool

GITHUB.COM/IAHISPANO

wafer: Web Application Firewall Fuzzing Tool

GITHUB.COM/SYSDIG

django-mock-queries: Mock Django Queryset Functions for Testing

GITHUB.COM/STPHIVOS

typed_configparser: Fully Typed INI/Config File Parser

GITHUB.COM/AJATKJ

tuttut: Converts a Midi File to ASCII Guitar Tabs

GITHUB.COM/NATECDR

Events Python Atlanta

February 9, 2024
MEETUP.COM

PyDelhi User Group Meetup

February 10, 2024
MEETUP.COM

DFW Pythoneers 2nd Saturday Teaching Meeting

February 10, 2024
MEETUP.COM

PiterPy Meetup

February 13, 2024
PITERPY.COM

Leipzig Python User Group Meeting

February 13, 2024
MEETUP.COM

IndyPy Monthly Meetup

February 14, 2024
MEETUP.COM

Happy Pythoning!
This was PyCoder’s Weekly Issue #615.
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

TechBeamers Python: Concat DataFrames in Pandas: A Step-by-Step Tutorial

Tue, 2024-02-06 13:02

In this tutorial, we’ll explore and demonstrate how to concat DataFrames in Pandas with different Python examples and use cases. If you usually work with data, merge datasets, or handle lots of info, learning the DataFrame concatenation technique in Pandas will be helpful. It makes your data analysis tasks a lot easier. Prerequisites Before we […]

The post Concat DataFrames in Pandas: A Step-by-Step Tutorial appeared first on TechBeamers.

Categories: FLOSS Project Planets

TechBeamers Python: How to Read Excel Files Using Pandas in Python

Tue, 2024-02-06 11:47

In this tutorial, we will explore how to read Excel files using the popular Python library, Pandas. Pandas is widely used for data manipulation and analysis, and it provides excellent support for handling Excel files. Whether you are working on a data analysis project, extracting data for machine learning, or simply need to read data […]

The post How to Read Excel Files Using Pandas in Python appeared first on TechBeamers.

Categories: FLOSS Project Planets

TechBeamers Python: How to Use Python To Generate Test Cases for Java Classes

Tue, 2024-02-06 10:49

In this HowTo tutorial, we’ll use Python to generate test cases for Java classes. For this purpose, Python provides an external module namely, javalang, so we’ll utilize it for test case generation. Since Python is much more user-friendly and easy to use than any other programming language, so why should we as a developer not […]

The post How to Use Python To Generate Test Cases for Java Classes appeared first on TechBeamers.

Categories: FLOSS Project Planets

Mike Driscoll: Creating a Modal Dialog For Your TUIs in Textual

Tue, 2024-02-06 10:24

Textual is a Python package that you can use to create beautiful text-based user interfaces (TUIs). In other words, you can create a GUI in your terminal with Textual.

In this tutorial, you will learn how to create a modal dialog in your terminal. Dialogs are great ways to alert the user about something or get some input. You also see dialogs used for such things as settings or help.

Let’s get started!

Adding a Modal Dialog

The first step is to think up some kind of application that needs a dialog. For this tutorial, you will create a form allowing the user to enter a name and address. Your application won’t save any data as that is outside the scope of this article, but it will demonstrate how to create the user interface and you can add that functionality yourself later on, if you wish.

To start, create a new file and name it something like tui_form.py. Then enter the following code:

from textual.app import App, ComposeResult from textual.containers import Center from textual.containers import Grid from textual.screen import ModalScreen from textual.widgets import Button, Footer, Header, Input, Static, Label class QuitScreen(ModalScreen): """Screen with a dialog to quit.""" def compose(self) -> ComposeResult: yield Grid( Label("Are you sure you want to quit?", id="question"), Button("Quit", variant="error", id="quit"), Button("Cancel", variant="primary", id="cancel"), id="dialog", ) def on_button_pressed(self, event: Button.Pressed) -> None: if event.button.id == "quit": self.app.exit() else: self.app.pop_screen() class Form(Static): def compose(self) -> ComposeResult: """ Creates the main UI elements """ yield Input(id="first_name", placeholder="First Name") yield Input(id="last_name", placeholder="Last Name") yield Input(id="address", placeholder="Address") yield Input(id="city", placeholder="City") yield Input(id="state", placeholder="State") yield Input(id="zip_code", placeholder="Zip Code") yield Input(id="email", placeholder="email") with Center(): yield Button("Save", id="save_button") class AddressBookApp(App): CSS_PATH = "modal.tcss" BINDINGS = [("q", "request_quit", "Quit")] def compose(self) -> ComposeResult: """ Lays out the main UI elemens plus a header and footer """ yield Header() yield Form() yield Footer() def action_request_quit(self) -> None: """Action to display the quit dialog.""" self.push_screen(QuitScreen()) if __name__ == "__main__": app = AddressBookApp() app.run()

Since this code is a little long, you will review it piece by piece. You will start at the bottom since the main entry point is the AddressBookApp class.

Here’s the code:

class AddressBookApp(App): CSS_PATH = "modal.tcss" BINDINGS = [("q", "request_quit", "Quit")] def compose(self) -> ComposeResult: """ Lays out the main UI elemens plus a header and footer """ yield Header() yield Form() yield Footer() def action_request_quit(self) -> None: """Action to display the quit dialog.""" self.push_screen(QuitScreen()) if __name__ == "__main__": app = AddressBookApp() app.run()

The AddressBookApp class is your application code. Here, you create a header, the form itself, and the footer of your application. When the user presses the q button, you also set up an accelerator key or key binding that calls action_request_quit(). This will cause your modal dialog to appear!

But before you look at that code, you should check out your Form’s code:

class Form(Static): def compose(self) -> ComposeResult: """ Creates the main UI elements """ yield Input(id="first_name", placeholder="First Name") yield Input(id="last_name", placeholder="Last Name") yield Input(id="address", placeholder="Address") yield Input(id="city", placeholder="City") yield Input(id="state", placeholder="State") yield Input(id="zip_code", placeholder="Zip Code") yield Input(id="email", placeholder="email") with Center(): yield Button("Save", id="save_button")

The Form class has a series of Input() widgets, which are text boxes, that you can enter your address information into. You specify a unique id for each widgets to give you a way to style each of them. You can read more about how that works in Using CSS to Style a Python TUI with Textual. The placeholder parameter lets you add a label to the text control so the user knows what should be entered in the widget.

Now you are ready to move on and look at your modal dialog code:

class QuitScreen(ModalScreen): """Screen with a dialog to quit.""" def compose(self) -> ComposeResult: yield Grid( Label("Are you sure you want to quit?", id="question"), Button("Quit", variant="error", id="quit"), Button("Cancel", variant="primary", id="cancel"), id="dialog", ) def on_button_pressed(self, event: Button.Pressed) -> None: if event.button.id == "quit": self.app.exit() else: self.app.pop_screen()

The QuitScreen class is made up of two functions:

  • compose() – Creates the widgets in the modal dialog
  • on_button_pressed() – The event handler that is fired when a button is pressed

The application will exit if the user presses the “Quit” button. Otherwise, the dialog will be dismissed, and you’ll return to your form screen.

You can style your application, including the modal dialog, using CSS. If you scroll back up to your application class, you’ll see that it refers to a CSS_PATH that points to a file named modal.tcss.

Here’s the CSS code that you’ll need to add to that file:

QuitScreen { align: center middle; } #dialog { grid-size: 2; grid-gutter: 1 2; grid-rows: 1fr 3; padding: 0 1; width: 60; height: 11; border: thick $background 80%; background: $surface; } #question { column-span: 2; height: 1fr; width: 1fr; content-align: center middle; } Button { width: 100%; }

This CSS will set the size and location of your modal dialog on the application. You tell Textual that you want the buttons to be centered and the dialog to be centered in the application.

To run your code, open up your terminal (or cmd.exe / Powershell on Windows), navigate to the folder that has your code in it, and run this command:

python tui_form.py

When you run this command, the initial screen will look like this:

To see the dialog, click the Save button to take the focus out of the text controls. Then hit the q button, and you will see the following:

You’ve done it! You created a modal dialog in your terminal!

Wrapping Up

Textual is a great Python package. You can create rich, expressive user interfaces in your terminal. These GUIs are also lightweight and can be run in your browser via Textual-web.

Want to learn more about Textual? Check out some of the following tutorials:

 

The post Creating a Modal Dialog For Your TUIs in Textual appeared first on Mouse Vs Python.

Categories: FLOSS Project Planets

Django Weblog: Django security releases issued: 5.0.2, 4.2.10, and 3.2.24

Tue, 2024-02-06 09:55

In accordance with our security release policy, the Django team is issuing Django 5.0.2, Django 4.2.10, and Django 3.2.24. These releases address the security issue detailed below. We encourage all users of Django to upgrade as soon as possible.

CVE-2024-24680: Potential denial-of-service in intcomma template filter

The intcomma template filter was subject to a potential denial-of-service attack when used with very long strings.

Affected supported versions
  • Django main branch
  • Django 5.0
  • Django 4.2
  • Django 3.2
Resolution

Patches to resolve the issue have been applied to Django's main branch and the 5.0, 4.2, and 3.2 stable branches. The patches may be obtained from the following changesets:

The following releases have been issued:

The PGP key ID used for this release is Natalia Bidart: 2EE82A8D9470983E

General notes regarding security reporting

As always, we ask that potential security issues be reported via private email to security@djangoproject.com, and not via Django's Trac instance, nor via the Django Forum, nor via the django-developers list. Please see our security policies for further information.

Categories: FLOSS Project Planets

Real Python: Python Basics Exercises: Lists and Tuples

Tue, 2024-02-06 09:00

In Python Basics: Lists and Tuples, you learned that Python lists resemble real-life lists in many ways. They serve as containers for organizing and storing collections of objects, allowing for the inclusion of different data types. You also learned about tuples, which are also collections of objects. However, while lists are mutable, tuples are immutable.

In this Python Basics Exercises course, you’ll test and reinforce your knowledge of Python lists and tuples. Along the way, you’ll also get experience with some good programming practices that will help you solve future challenges.

In this video course, you’ll practice:

  • Defining and manipulating lists and tuples in Python
  • Leveraging the unique qualities of lists and tuples
  • Determining when you should use lists vs tuples

By the end of this course, you’ll have an even stronger grasp of Python lists and tuples. You’ll be equipped with the knowledge to effectively incorporate them into your own programming projects.

This video course is part of the Python Basics series, which accompanies Python Basics: A Practical Introduction to Python 3. You can also check out the other Python Basics courses.

Note that you’ll be using IDLE to interact with Python throughout this course.

[ 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: #370 Your Very Own Heroku

Tue, 2024-02-06 03:00
<strong>Topics covered in this episode:</strong><br> <ul> <li><a href="https://dokku.com"><strong>Dokku</strong></a></li> <li><a href="https://www.nicholashairs.com/posts/major-changes-between-python-versions/"><strong>Summary of Major Changes Between Python Versions</strong></a></li> <li>How to check Internet Speed via Terminal? <a href="https://github.com/sivel/speedtest-cli">speedtest-cli</a></li> <li><strong>Blogs: We all should blog more</strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=1P-XjiHzSNU' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="370">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="https://training.talkpython.fm/"><strong>courses at Talk Python Training</strong></a></li> <li><a href="https://courses.pythontest.com/p/the-complete-pytest-course"><strong>The Complete pytest Course</strong></a></li> <li><a href="https://www.patreon.com/pythonbytes"><strong>Patreon Supporters</strong></a></li> </ul> <p><strong>Connect with the hosts</strong></p> <ul> <li>Michael: <a href="https://fosstodon.org/@mkennedy"><strong>@mkennedy@fosstodon.org</strong></a></li> <li>Brian: <a href="https://fosstodon.org/@brianokken"><strong>@brianokken@fosstodon.org</strong></a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes"><strong>@pythonbytes@fosstodon.org</strong></a></li> </ul> <p>Join us on YouTube at <a href="https://pythonbytes.fm/stream/live"><strong>pythonbytes.fm/live</strong></a> to be part of the audience. Usually Tuesdays at 11am PT. Older video versions available there too.</p> <p><strong>Michael #1:</strong> <a href="https://dokku.com"><strong>Dokku</strong></a></p> <ul> <li>An open source PAAS alternative to Heroku.</li> <li>Dokku helps you build and manage the lifecycle of applications from building to scaling.</li> <li>Powered by Docker, you can install Dokku on any hardware.</li> <li>Once it's set up on a host, you can push Heroku-compatible applications to it via Git. </li> <li>Rich <a href="https://dokku.com/docs/community/plugins/">plug in architecture</a>.</li> </ul> <p><strong>Brian #2:</strong> <a href="https://www.nicholashairs.com/posts/major-changes-between-python-versions/"><strong>Summary of Major Changes Between Python Versions</strong></a></p> <ul> <li>Nicholas Hairs</li> <li>Changes between versions &amp; Tools &amp; utilities to help with switching</li> <li>Hopefully you’re already at least at 3.8, but come on, 3.11 &amp; 3.12 are so fun!</li> <li>Useful things <ul> <li><code>pyupgrade</code> can automatically upgrade code base <ul> <li>(However, I frequently just upgrade and run tests and let my old code be as-is until it bugs me. - Brian)</li> </ul></li> <li><code>black</code> checks <code>pyproject.toml</code> <code>requires-python</code> setting and uses version specific rules.</li> </ul></li> <li>Versions (way more highlights listed in the article) <ul> <li>3.8 <ul> <li>Assignment expressions <code>:=</code> walrus</li> <li><code>f"{variable=}"</code> now works</li> </ul></li> <li>3.9 <ul> <li>Typing has built in generics like <code>dict[]</code>, so no more <code>from typing import Dict</code></li> <li>Dict union operator</li> <li>Strings can <code>removeprefix</code> and <code>removesuffix</code></li> </ul></li> <li>3.10 <ul> <li>Structural pattern matching <code>match/case</code></li> <li>Typing: Union using pipe <code>|</code></li> <li>Dataclasses support <code>slots=True</code> and <code>kw_only=True</code></li> </ul></li> <li>3.11 <ul> <li><code>tomllib</code> included as a standard TOMP parser</li> <li>Exception groups</li> <li>Exception Notes <code>add_note()</code></li> <li>Typing: A <code>Self</code> type</li> <li>Star unpacking expressions allowed in <code>for</code> statements: <code>for x in *a, *b:</code></li> </ul></li> <li>3.12 <ul> <li>f-strings can re-use quotes</li> <li>Typing: better type parameter syntax</li> <li>Typing: <code>@override</code> decorator ensures a method being overridden by a child class actually exists.</li> </ul></li> </ul></li> </ul> <p><strong>Michael #3:</strong> How to check Internet Speed via Terminal? <a href="https://github.com/sivel/speedtest-cli">speedtest-cli</a></p> <ul> <li>Command line interface for testing internet bandwidth using speedtest.net</li> <li>Just <code>pipx install speedtest-cli</code></li> <li>Has a <a href="https://github.com/sivel/speedtest-cli/wiki">Python API</a> too</li> </ul> <p><strong>Brian #4:</strong> <strong>Blogs: We all should blog more</strong></p> <ul> <li>Jeff Triplett is attempting one post per day in February <ul> <li>Feb 1: <a href="https://micro.webology.dev/2024/02/01/choosing-the-right.html">Choosing the Right Python and Django Versions for Your Projects</a></li> <li>Feb 2: <a href="https://micro.webology.dev/2024/02/02/my-first-mac.html">My First Mac</a> <ul> <li>Which also links to a quite interesting Personal: <a href="https://jefftriplett.com/2023/default-apps-2023/">Default Apps 2023</a></li> </ul></li> <li>Feb 3: <a href="https://micro.webology.dev/2024/02/03/whats-your-goto.html">What’s Your Go-to Comfort Media? [rough cut]</a></li> <li>Feb 4: <a href="https://micro.webology.dev/2024/02/04/the-django-apps.html">The Django apps I actually use (rough cut)</a></li> <li>Feb 5: <a href="https://micro.webology.dev/2024/02/05/how-to-test.html">How to test with Django and pytest fixtures</a></li> </ul></li> <li>Need ideas? <ul> <li>Check out <a href="https://hamatti.org/posts/build-an-idea-bank-and-never-run-out-of-blog-ideas/">Build an idea bank and never run out of blog ideas</a></li> </ul></li> <li>Not using AI? Thanks. We appreciate that. <ul> <li>Maybe tag it as <a href="https://notbyai.fyi/">Not By AI</a></li> </ul></li> </ul> <p><strong>Extras</strong> </p> <p>Brian:</p> <ul> <li>If upgrading to pytest 8, be aware that running individual tests with parametrization will result in a reverse order. <ul> <li>It shouldn’t matter. <a href="https://podcast.pythontest.com/episodes/211-stamp-out-test-dependencies-with-pytest-plugins">You shouldn’t be depending on test order.</a></li> <li>But it was surprising to me.</li> <li><a href="https://github.com/pytest-dev/pytest/issues/11937">Issue has been logged</a></li> </ul></li> </ul> <p>Michael:</p> <ul> <li><a href="https://orbstack.dev">Orbstack</a> follow up</li> </ul> <p><strong>Joke:</strong> <a href="https://workchronicles.com/white-lies/">White Lies</a></p>
Categories: FLOSS Project Planets

Glyph Lefkowitz: Let Me Tell You A Secret

Mon, 2024-02-05 17:36

I do consulting1 on software architecture, network protocol development, python software infrastructure, streamlined cloud deployment, and open source strategy, among other nerdy things. I enjoy solving challenging, complex technical problems or contributing to the open source commons. On the best jobs, I get to do both.

Today I would like to share with you a secret of the software technology consulting trade.

I should note that this secret is not specific to me. I have several colleagues who have also done software consulting and have reflected versions of this experience back to me.2

We’ll get to the secret itself in a moment, but first, some background.

Companies do not go looking for consulting when things are going great. This is particularly true when looking for high-level consulting on things like system architecture or strategy. Almost by definition, there’s a problem that I have been brought in to solve. Ideally, that problem is a technical challenge.

In the software industry, your team probably already has some software professionals with a variety of technical skills, and thus they know what to do with technical challenges. Which means that, as often as not, the problem is to do with people rather than technology, even it appears otherwise.

When you hire a staff-level professional like myself to address your software team’s general problems, that consultant will need to gather some information. If I am that consultant and I start to suspect that the purported technology problem that you’ve got is in fact a people problem, here is what I am going to do.

I am going to go get a pen and a pad of paper, then schedule a 90-minute meeting with the most senior IC3 engineer that you have on your team. I will bring that pen and paper to the meeting. I will then ask one question:

What is fucked up about this place?

I will then write down their response in as much detail as I can manage. If I have begun to suspect that this meeting is necessary, 90 minutes is typically not enough time, and I will struggle to keep up. Even so, I will usually manage to capture the highlights.

One week later, I will schedule a meeting with executive leadership, and during that meeting, I will read back a very lightly edited4 version of the transcript of the previous meeting. This is then routinely praised as a keen strategic insight.

I should pause here to explicitly note that — obviously, I hope — this is not an oblique reference to any current or even recent client; if I’d had this meeting recently it would be pretty awkward to answer that “so, I read your blog…” email.5 But talking about clients in this way, no matter how obfuscated and vague the description, is always a bit professionally risky. So why risk it?

The thing is, I’m not a people manager. While I can do this kind of work, and I do not begrudge doing it if it is the thing that needs doing, I find it stressful and unfulfilling. I am a technology guy, not a people person. This is generally true of people who elect to go into technology consulting; we know where the management track is, and we didn’t pick it.

If you are going to hire me for my highly specialized technical expertise, I want you to get the maximum value out of it. I know my value; my rates are not low, and I do not want clients to come away with the sense that I only had a couple of “obvious” meetings.

So the intended audience for this piece is potential clients, leaders of teams (or organizations, or companies) who have a general technology problem and are wondering if they need a consultant with my skill-set to help them fix it. Before you decide that your issue is the need to implement a complex distributed system consensus algorithm, check if that is really what’s at issue. Talk to your ICs, and — taking care to make sure they understand that you want honest feedback and that they are safe to offer it — ask them what problems your organization has.

During this meeting it is important to only listen. Especially if you’re at a small company and you are regularly involved in the day-to-day operations, you might feel immediately defensive. Sit with that feeling, and process it later. Don’t unload your emotional state on an employee you have power over.6

“Only listening” doesn’t exclusively mean “don’t push back”. You also shouldn’t be committing to fixing anything. While the information you are gathering in these meetings is extremely valuable, and you should probably act on more of it than you will initially want to, your ICs won’t have the full picture. They really may not understand why certain priorities are set the way they are. You’ll need to take that as feedback for improving internal comms rather than “fixing” the perceived problem, and you certainly don’t want to make empty promises.

If you have these conversations directly, you can get something from it that no consultant can offer you: credibility. If you can actively listen, the conversation alone can improve morale. People like having their concerns heard. If, better still, you manage to make meaningful changes to address the concerns you’ve heard about, you can inspire true respect.

As a consultant, I’m going to be seen as some random guy wasting their time with a meeting. Even if you make the changes I recommend, it won’t resonate the same way as someone remembering that they personally told you what was wrong, and you took it seriously and fixed it.

Once you know what the problems are with your organization, and you’ve got solid technical understanding that you really do need that event-driven distributed systems consensus algorithm implemented using Twisted, I’m absolutely your guy. Feel free to get in touch.

  1. While I immensely value my patrons and am eternally grateful for their support, at — as of this writing — less than $100 per month it doesn’t exactly pay the SF bay area cost-of-living bill. 

  2. When I reached out for feedback on a draft of this essay, every other consultant I showed it to said that something similar had happened to them within the last month, all with different clients in different sectors of the industry. I really cannot stress how common it is. 

  3. “individual contributor”, if this bit of jargon isn’t universal in your corner of the world; i.e.: “not a manager”. 

  4. Mostly, I need to remove a bunch of profanity, but sometimes I will also need to have another interview, usually with a more junior person on the team to confirm that I’m not relaying only a single person’s perspective. It is pretty rare that the top-of-mind problems are specific to one individual, though. 

  5. To the extent that this is about anything saliently recent, I am perhaps grumbling about how tech CEOs aren’t taking morale problems generated by the constant drumbeat of layoffs seriously enough

  6. I am not always in the role of a consultant. At various points in my career, I have also been a leader needing to sit in this particular chair, and believe me, I know it sucks. This would not be a common problem if there weren’t a common reason that leaders tend to avoid this kind of meeting. 

Categories: FLOSS Project Planets

TestDriven.io: Django REST Framework and Vue versus Django and HTMX

Mon, 2024-02-05 17:28
This article compares the development experience with Vue and Django REST Framework against HTMX and Django.
Categories: FLOSS Project Planets

PyCharm: PyCharm 2024.1 EAP 3: Tool Window Names in the New UI

Mon, 2024-02-05 17:05

PyCharm 2024.1 EAP 3 is now available. This latest update focuses on enhancing your interactions with the IDE’s interface.

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.

Let’s take a closer look at what’s included in this new version.

User experience Option to show tool window names in the side toolbars

Starting with PyCharm 2024.1 EAP 3, it is now possible to display tool window names on side toolbars in the new UI. Simply right-click on the toolbar to access the context menu and choose Show Tool Window Names, or enable this option via Settings / Preferences | Appearance & Behavior | Appearance | Tool Windows. It’s also possible to tailor the width of the toolbar by dragging its edge.

Git tab removed from the Search Everywhere dialog

After analyzing the usage statistics, we’ve removed the Git tab from the Search Everywhere dialog by default. If you want to bring it back, you can do so via the Show Git tab in Search Everywhere checkbox in Settings / Preferences | Advanced Settings | Version Control. Git.

That’s it for this week. For a full list of changes, please refer to the release notes.

We value your feedback on these new updates and features. Feel free to leave your thoughts in the comments section or via X (formerly Twitter). If you encounter any issues, please don’t hesitate to report them via our issue tracker.

Categories: FLOSS Project Planets

EuroPython: Seize the Opportunity: EuroPython 2024 Call for Contributors Extended!

Mon, 2024-02-05 13:28

Dear Python Enthusiasts,

Excitement is in the air as we gear up for EuroPython 2024, the conference that brings together the brightest minds in the European Python community!

In the spirit of inclusivity and community collaboration, we are thrilled to announce an extension of the Call for Contributors for EuroPython 2024! We want to ensure that everyone who wishes to contribute has the opportunity to do so. This extension allows more Pythonistas to be a part of this incredible event, shaping the conference and making it an enriching experience for all.

How Can You Get Involved?1. Review the proposals:

Have you navigated the ins and outs of proposal submissions? The heart of EuroPython lies in its program. If you&aposre passionate about curating an exceptional conference agenda, we welcome you to join our team of Reviewers. By reviewing proposals and assisting in the selection process, you&aposll contribute to crafting a lineup that reflects the diversity and innovation within the Python community.

2. Volunteer for the Conference Organisation:

EuroPython is not just about talks; it&aposs about fostering connections and creating memorable experiences. We&aposre looking for volunteers to help with various aspects of the conference, from registration to technical support. Your contribution, big or small, will play a crucial role in making EuroPython 2024 an unforgettable event for everyone involved.

3. Become a Speaker Mentor:

Do you have experience as a speaker at Python conferences? Are you passionate about mentorship? We invite you to be a guiding force for our aspiring speakers. By volunteering as a Speaker Mentor, you&aposll play a pivotal role in shaping the future of Python conference speakers. Your expertise will help potential speakers refine their proposals, ensuring a diverse and high-quality program.

How to Get Involved:

1. Reviewers:

  • Sign up on the Registration Form.
  • Share insights into your experience with Python and conferences.
  • Join our collaborative effort to shape an outstanding conference program.

2. Volunteer for Conference Organisation:

  • If you’d like to learn more about how we work, check this link: Teams Descriptions
  • Let us know your availability and preferences for volunteering roles.
  • Be a crucial part of the behind-the-scenes magic that makes EuroPython memorable.

3. Speaker Mentorship:

  • Please fill out the Speaker Mentor Application Form.
  • Provide details about your experience and areas of expertise.
  • Our team will match you with potential speakers based on your background.
&#x1F4DD; Make sure to sign up by February 12th 2024 AoE! &#x1F4DD;

Why Volunteer?

Community Contribution:

  • Contribute to the Python community by sharing your knowledge and experience.
  • Help newcomers find their voice in the tech world.

Networking Opportunities:

  • Connect with like-minded individuals, speakers, and fellow volunteers.
  • Build relationships that can shape your professional journey.

Personal Growth:

  • Hone your mentoring and organisational skills.
  • Gain insights into the latest trends and innovations in the Python ecosystem.

Don&apost miss this chance to be an integral part of EuroPython 2024! Let&aposs join forces to make this conference a celebration of Python&aposs diversity, innovation, and community spirit. Together, we can create an event that inspires, educates, and leaves a lasting impact on every participant.

See you at EuroPython 2024!


EuroPython 2024 Organizing Team

Categories: FLOSS Project Planets

Pages