Feeds
Junichi Uekawa: Graph for my furusato tax.
Russ Allbery: Review: Long Live Evil
Review: Long Live Evil, by Sarah Rees Brennan
Series: Time of Iron #1 Publisher: Orbit Copyright: July 2024 ISBN: 0-316-56872-4 Format: Kindle Pages: 433Long Live Evil is a portal fantasy (or, arguably more precisely, a western take on an isekai villainess fantasy) and the first book of a series. If the author's name sounds familiar, it's possibly because of In Other Lands, which got a bunch of award nominations in 2018, She has also written a lot of other YA fantasy, but this is her first adult epic fantasy novel.
Rae is in the hospital, dying of cancer. Everything about that experience, from the obvious to the collapse of her friendships, absolutely fucking sucks. One of the few bright points is her sister's favorite fantasy series, Time of Iron, which her sister started reading to her during chemo sessions. Rae mostly failed to pay attention until the end of the first book and the rise of the Emperor. She fell in love with the brooding, dangerous anti-hero and devoured the next two books. The first book was still a bit hazy, though, even with the help of a second dramatic reading after she was too sick to read on her own.
This will be important later.
After one of those reading sessions, Rae wakes up to a strange woman in her hospital room who offers her an option. Rather than die a miserable death that bankrupts her family, she can go through a door to Eyam, the world of Time of Iron, and become the character who suits her best. If she can steal the Flower of Life and Death from the imperial greenhouse on the one day a year that it blooms, she will wake up, cured. If not, she will die. Rae of course goes through, and wakes in the body of Lady Rahela, the Beauty Dipped in Blood, the evil stepsister. One of the villains, on the night before she is scheduled to be executed.
Rae's initial panic slowly turns to a desperate glee. She knows all of these characters. She knows how the story will turn out. And she has a healthy body that's not racked with pain. Maybe she's not the heroine, but who cares, the villains are always more interesting anyway. If she's going to be cast as the villain, she's going to play it to the hilt. It's not like any of these characters are real.
Stories in which the protagonists are the villains are not new (Nimona and Hench come to mind just among books I've reviewed), but they are having a moment. Assistant to the Villain by Hannah Nicole Maehrer came out last year, and this book and Django Wexler's How to Become the Dark Lord and Die Trying both came out this year. This batch of villain books all take different angles on the idea, but they lean heavily on humor. In Long Live Evil, that takes the form of Rae's giddy embrace of villainous scheming, flouncing, and blatant plot manipulation, along with her running commentary on the various characters and their in-story fates.
The setup here is great. Rae is not only aware that she's in a story, she knows it's full of cliches and tropes. Some of them she loves, some of them she thinks are ridiculous, and she isn't shy about expressing both of those opinions. Rae is a naturally dramatic person, and it doesn't take her long to lean into the opportunities for making dramatic monologues and villainous quips, most of which involve modern language and pop culture references that the story characters find baffling and disconcerting.
Unfortunately, the base Time of Iron story is, well, bad. It's absurd grimdark epic fantasy with paper-thin characters and angst as a central character trait. This is clearly intentional for both in-story and structural reasons. Rae enjoys it precisely because it's full of blood and battles and over-the-top brooding, malevolent anti-heroes, and Rae's sister likes the impossibly pure heroes who suffer horrible fates while refusing to compromise their ideals. Rae is also about to turn the story on its head and start smashing its structure to try to get herself into position to steal the Flower of Life and Death, and the story has to have a simple enough structure that it doesn't get horribly confusing once smashed. But the original story is such a grimdark parody, and so not my style of fantasy, that I struggled with it at the start of the book.
This does get better eventually, as Rae introduces more and more complications and discovers some surprising things about the other characters. There are several delightful twists concerning the impossibly pure heroine of the original story that I will not spoil but that I thought retroactively made the story far more interesting. But that leads to the other problem: Rae is both not very good at scheming, and is flippant and dismissive of the characters around her. These are both realistic; Rae is a young woman with cancer, not some sort of genius mastermind, and her whole frame for interacting with the story is fandom discussions and arguments with her sister. Early in the book, it's rather funny. But as the characters around her start becoming more fleshed out and complex, Rae's inability to take them seriously starts to grate. The grand revelation to Rae that these people have their own independent existence comes so late in the book that it's arguably a spoiler, but it was painfully obvious to everyone except Rae for hundreds of pages before it got through Rae's skull.
Those are my main complaints, but there was a lot about this book that I liked. The Cobra, who starts off as a minor villain in the story, is by far the best character of the book. He's not only more interesting than Rae, he makes everyone else in the book, including Rae, more interesting characters through their interactions. The twists around the putative heroine, Lady Rahela's stepsister, are a bit too long in coming but are an absolute delight. And Key, the palace guard that Rae befriends at the start of the story, is the one place where Rae's character dynamic unquestionably works. Key anchors a lot of Rae's scenes, giving them a sense of emotional heft that Rae herself would otherwise undermine.
The narrator in this book does not stick with Rae. We also get viewpoint chapters from the Cobra, the Last Hope, and Emer, Lady Rahela's maid. The viewpoints from the Time of Iron characters can be a bit eye-roll-inducing at the start because of how deeply they follow the grimdark aesthetic of the original story, but by the middle of the book I was really enjoying the viewpoint shifts. This story benefited immensely from being seen from more angles than Rae's chaotic manipulation. By the end of the book, I was fully invested in the plot line following Cobra and the Last Hope, to the extent that I was a bit disappointed when the story would switch back to Rae.
I'm not sure this was a great book, but it was fun. It's funny in places, but I ended up preferring the heartfelt parts to the funny parts. It is a fascinating merger of gleeful fandom chaos and rather heavy emotional portrayals of both inequality and the experience of terminal illness. Rees Brennan is a stage four cancer survivor and that really shows; there's a depth, nuance, and internal complexity to Rae's reactions to illness, health, and hope that feels very real. It is the kind of book that can give you emotional whiplash; sometimes it doesn't work, but sometimes it does.
One major warning: this book ends on a ridiculous cliffhanger and does not in any sense resolve its main plot arc. I found this annoying, not so much because of the wait for the second volume, but because I thought this book was about the right length for the amount of time I wanted to spend in this world and wish Rees Brennan had found a way to wrap up the story in one book. Instead, it looks like there will be three books. I'm in for at least one more, since the story was steadily getting better towards the end of Long Live Evil, but I hope the narrative arc survives being stretched out across that many words.
This one's hard to classify, since it's humorous fantasy on the cover and in the marketing, and that element is definitely present, but I thought the best parts of the book were when it finally started taking itself seriously. It's metafictional, trope-subverting portal fantasy full of intentional anachronisms that sometimes fall flat and sometimes work brilliantly. I thought the main appeal of it would be watching Rae embrace being a proper villain, but then the apparent side characters stole the show. Recommended, but you may have to be in just the right mood.
Content notes: Cancer, terminal illness, resurrected corpses, wasting disease, lots of fantasy violence and gore, and a general grimdark aesthetic.
Rating: 7 out of 10
Charles: Hello World
As the computer science tradition demands, we must start with a Hello World.
Though I have to say this hello world took quite a long time to reach the internet. I’ve been thinking about setting up this website for way over a year, but there are always too many things to decide - what Static Site Generator will I use? Where should I get a domain from? Which registrar would be better now? What if I want to set up a mail server, is it good enough? Oh, and what about the theme, which one to choose? Can I get one simple enough to not fetch javascript or css from external sources?
This was taking so long that even my friends were saying “Please, just share your screen and let’s do it now!”. Well, rejoice friends, now it’s done!
Oliver Davies' daily list: Override Node Options and Drupal 11
Last week, I released a new version of the Override Node Options module - version 8.x-2.9.
This version makes the module compatible with Drupal 11 and, as there are no breaking changes, it's still compatible with Drupal 9 and 10.
It's great to see the module used on many Drupal 8+ websites and distributions such as LocalGov Drupal.
Whilst the overall number of installations has been consistent, the number of Drupal 7 installations has decreased whilst the Drupal 8+ version installations have increased.
With a Drupal 11-compatible version now available, I hope it continues to increase.
unifont @ Savannah: Unifont 16.0.02 Released
1 December 2024 Unifont 16.0.02 is now available. This is a minor release with many glyph improvements. See the ChangeLog file for details.
Download this release from GNU server mirrors at:
https://ftpmirror.gnu.org/unifont/unifont-16.0.02/
or if that fails,
https://ftp.gnu.org/gnu/unifont/unifont-16.0.02/
or, as a last resort,
ftp://ftp.gnu.org/gnu/unifont/unifont-16.0.02/
These files are also available on the unifoundry.com website:
https://unifoundry.com/pub/unifont/unifont-16.0.02/
Font files are in the subdirectory
https://unifoundry.com/pub/unifont/unifont-16.0.02/font-builds/
A more detailed description of font changes is available at
https://unifoundry.com/unifont/index.html
and of utility program changes at
https://unifoundry.com/unifont/unifont-utilities.html
Information about Hangul modifications is at
https://unifoundry.com/hangul/index.html
and
http://unifoundry.com/hangul/hangul-generation.html
Guido Günther: Free Software Activities November 2024
Another short status update of what happened on my side last month. The larger blocks are the Phosh 0.43 release, the initial file chooser portal, phosh-osk-stub now handling digit, number, phone and PIN input purpose via special layouts as well as Phoc mostly catching up with wlroots 0.18 and the current development version targeting 0.19.
phosh- When taking a screenshot via keybinding or power button long press save screenshots to clipboard and disk (MR)
- Robustify Screenshot CI job (MR)
- Update CI pipeline (MR)
- Fix notifications banners that aren't tall enough not being shown (MR). Another 4y old bug hopefully out of the way.
- Add rfkill mock and docs (MR). Useful for HKS testing.
- Release 0.43~rc1 and 0.43
- Drop libsoup workaround (MR)
- Ensure notification only takes its actual height (MR)
- Move wlroots 0.18 update forward (MR). Needs a bit more work before we can make it default.
- Catch up with wlroots development branch (MR) allowing us to test current wlroots again.
- Some of the above already applies to main so schedule it for 0.44 (MR)
- Add layouts for PIN, number and phone input purpose (MR)
- Release 0.43~rc1
- Ensure translation get picked up, various cleanups and release 0.43.0 (MR)
- Make desktop file match app-id (MR)
- Fix typo and reduce number of strings to translate (MR)
- Add translator comments (MR). This, the above and additional fixes in p-m-s were prompted by i18n feedback from Alexandre Franke, thanks a lot!
- Release 0.43.0
- Initial version of the adaptive file chooser dialog using gtk-rs. See demo.
- Allow to activate via double click (for non-touch use) (MR)
- Use pfs to provide a file chooser portal (MR)
- Slightly improve point release handling (MR)
- Improve string freeze announcements and add phosh-tour (MR)
- Upload Phosh 0.43.0~rc1 and 0.43.0 (MR, MR, MR, MR, MR, MR, MR, MR, MR, MR, MR)
- meta-phosh: Add Recommend: for xdg-desktop-portal-phosh (MR)
- phosh-osk-data got accepted, create repo, brush up packaging and upload to unstable (MR
- phosh-osk-stub: Recommend data packager (MR
- Phosh: drop reverts (MR)
- varnam-schemes: Fix autopkgtest (MR)
- varnam-schemes: Improve packaging (MR)
- Prepare govarnam 1.9.1 (MR)
- ussd: Set input purpose and switch to AdwDialog (MR, Screenshot)
- Drop libhandy leftover (MR)
- Improve docs and cleanup markdown (MR)
- Mention gbp push in intro (MR)
- Use application instead of productname entities to improve reading flow (MR)
- Drop mention of wlr_renderer_begin_with_buffer (MR)
- Add mock for gsd-rfkill (MR)
- Sync notification categories with the portal spec (MR)
- Add categories for SMS (MR)
- Add a pubdate so it's clear the specs aren't stale (MR) (got fixed in a different and better way, thanks Matthias!)
- Allow to set filters in file chooser portal demo (MR)
- Robustify file generation (MR)
- Unbreak tests on non intel/amd architectures (e.g. arm64) (MR)
This is not code by me but reviews I did on other peoples code. The list is incomplete, but I hope to improve on this in the upcoming months. Thanks for the contributions!
- flathub: livi runtime and gst update (MR)
- phosh: Split linters into their own test suite (MR)
- phosh; QuickSettings follow-up (MR)
- phosh: Accent color fixes (MR)
- phosh: Notification animation (MR)
- phosh: end-session dialog timeout fix (MR)
- phosh: search daemon (MR)
- phosh-ev: Migrate to newer gtk-rs and async_channel (MR)
- phosh-mobile-settings: Update gmobile (MR)
- phosh-mobile-settings: Make panel-switcher scrollable (MR)
- phosh-mobile-settings: i18n comments (MR)
- gbp doc updates (MR)
- gbp handle suite names with number prefix (MR)
- Debian libvirt dependency changes (MR
- Chatty: misc improvements (MR
- iio-sensor-proxy: buffer driver without trigger (MR)
- gbp doc improvements (MR)
- gbp: More doc improvements (MR)
- gbp: Clean on failure (MR)
- gbp: DEP naming consistency (MR)
If you want to support my work see donations. This includes a list of hardware we want to improve support for. Thanks a lot to all current and past donors.
Comments?Join the Fediverse thread
Colin Watson: Free software activity in November 2024
Most of my Debian contributions this month were sponsored by Freexian.
You can also support my work directly via Liberapay.
ConferencesI attended MiniDebConf Toulouse 2024, and the MiniDebCamp before it. Most of my time was spent with the Freexian folks working on debusine; Stefano gave a talk about its current status with a live demo (frantically fixed up over the previous couple of days, as is traditional) and with me and others helping to answer questions at the end. I also caught up with some people I haven’t seen in ages, ate a variety of delicious cheeses, and generally had a good time. Many thanks to the organizers and sponsors!
After the conference, Freexian collaborators spent a day and a half doing some planning for next year, and then went for an afternoon visiting the Cité de l’espace.
Rust teamI upgraded these packages to new upstream versions, as part of upgrading pydantic and rpds-py:
- rust-archery
- rust-jiter (noticing an upstream test bug in the process)
- rust-pyo3 (fixing CVE-2024-9979)
- rust-pyo3-build-config
- rust-pyo3-ffi
- rust-pyo3-macros
- rust-pyo3-macros-backend
- rust-regex
- rust-regex-automata
- rust-regex
- rust-serde
- rust-serde-derive
- rust-serde-json
- rust-speedate
- rust-triomphe
Last month, I mentioned that we still need to work out what to do about the multipart vs. python-multipart name conflict in Debian (#1085728). We eventually managed to come up with an agreed plan; Sandro has uploaded a renamed binary package to experimental, and I’ve begun work on converting reverse-dependencies (asgi-csrf, fastapi, python-curies, and starlette done so far). There’s a bit more still to do, but I expect we can finish it soon.
I fixed problems related to adding Python 3.13 support in:
- coreapi
- git-repo-updater
- offlineimap3
- ptyprocess
- pytest-testinfra
- python-formencode
- python-iniparse
- python-line-profiler
- python-parameterized
- python-sdjson (contributed upstream)
- python-testfixtures (contributed upstream)
- python-venusian
- sphinx-a4doc (contributed upstream, and I also made a small antlr4 improvement)
- webpy
I fixed some packaging problems that resulted in failures any time we add a new Python version to Debian:
I fixed other build/autopkgtest failures in:
- psycopg3 (contributed upstream)
- python-aiohttp
- python-cotengrust
- python-distutils-extra
- python-formencode
- python-openapi-schema-validator
- python-openapi-spec-validator
- sphinxcontrib-towncrier
I packaged python-quart-trio, needed for a new upstream version of python-urllib3, and contributed a small packaging tweak upstream.
I backported a twisted fix that caused problems in other packages, including breaking debusine‘s tests.
I disentangled some upstream version confusion in python-catalogue, and upgraded to the current upstream version.
I upgraded these packages to new upstream versions:
- aioftp (fixing a Python 3.13 failure)
- ansible-core
- ansible
- debugpy
- jsonpickle
- manuel
- psycopg2
- pydantic-core
- pydantic
- pydantic-settings
- pymssql (fixing a Python 3.13 failure)
- pyodbc (fixing a Python 3.13 failure)
- python-argh (fixing a Python 3.13 failure)
- python-boltons (fixing a Python 3.13 failure)
- python-channels-redis
- python-colorlog (fixing a Python 3.13 failure)
- python-django-pgtrigger
- python-line-profiler
- python-pathvalidate (fixing a Python 3.13 failure)
- python-plac (fixing a Python 3.13 failure)
- python-precis-i18n
- python-pure-eval (fixing a Python 3.13 failure)
- python-pythonjsonlogger (contributing a small packaging fix upstream, as well as a test fix to jupyter-events)
- python-rdata (fixing a Python 3.13 failure)
- python-semantic-release
- python-telethon (fixing a Python 3.13 failure, and contributing some test fixes upstream)
- python-tornado (fixing CVE-2024-52804)
- python-trio (fixing a Python 3.13 failure)
- python-trustme
- python-typeguard
- python-urllib3 (fixing CVE-2024-37891 and a Python 3.13 failure, and requiring some shenanigans with its hypercorn test-dependency)
- python-zipp
- quart (fixing CVE-2024-49767)
- rpds-py (fixing a build failure)
- sen
- sqlparse
- stravalib
- transaction
- waitress
- zope.interface
I contributed Incus support to needrestart upstream.
In response to Helmut’s Cross building talk at MiniDebConf Toulouse, I fixed libfilter-perl to support cross-building (5b4c2e10, f9788c27).
I applied a patch to move aliased files from / to /usr in iprutils (#1087733).
I adjusted debconf to use the new /usr/lib/apt/apt-extracttemplates path (#1087523).
I upgraded putty to 0.82.
gettext @ Savannah: GNU gettext 0.23 released
Download from https://ftp.gnu.org/pub/gnu/gettext/gettext-0.23.tar.gz
New in this release:
- Internationalized data formats:
- XML:
- The escaping of characters such as & < > has been changed:
- No escaping is done any more by xgettext, when creating a POT file.
- Instead, extra escaping can be requested for the msgfmt pass, when merging into an XML file.
- The default value of 'escape' in the <gt:escapeRule> was "yes"; now it is "no".
- This means that existing translations of older POT files may no longer fully apply. As a maintainer of a package that has translatable XML files, you need to regenerate the POT file and pass it on to your translators.
- XML schemas for .its and .loc files are now provided.
- The value of the xml:lang attribute, inserted by msgfmt, now conforms to W3C standards.
- 'msgfmt --xml' accept an option --replace-text, that causes the output to be a mono-lingual XML file instead of a multi-lingual XML file.
- xgettext and 'msgfmt --xml' now support DocBook XML files.
- The escaping of characters such as & < > has been changed:
- Desktop: xgettext now produces POT files with correct line numbers.
- XML:
- Programming languages support:
- Python:
- xgettext now assumes source code for Python 3 rather than Python 2. This affects the interpretation of escape sequences in string literals.
- xgettext now recognizes the f-string syntax.
- Scheme:
- xgettext now supports the option '-L Guile' as an alternative to '-L Scheme'. They are nearly equivalent. They differ in the interpretation of escape sequences in string literals: While 'xgettext -L Scheme' assumes the R6RS and R7RS syntax of string literals, 'xgettext -L Guile' assumes the syntax of string literals understood by Guile 2.x and 3.0 (without command-line option '--r6rs' or '--r7rs', and before a '#!r6rs' directive is seen).
- xgettext now recognizes comments of the form '#; <expression>'.
- Java: xgettext now has an improved recognition of format strings when the String.formatted method is used.
- JavaScript:
- xgettext now parses template literals inside JSX correctly.
- xgettext has a new option --tag that customizes the behaviour of tagged template literals.
- C#:
- The build system and tools now also support 'dotnet' (.NET) as C# implementation. In order to declare a preference for 'dotnet' over 'mono', you can use the configure option '--enable-csharp=dotnet'.
- xgettext now recognizes strings with embedded expressions (a.k.a. interpolated strings).
- awk: xgettext now recognizes string concatenation by juxtaposition.
- Smalltalk: xgettext now recognizes the string concatenation operator ','.
- Vala: xgettext now has an improved recognition of format strings when the string.printf method is used.
- Glade: xgettext has improved support for GtkBuilder 4.
- Tcl: With the recently released Tcl 9.0, characters outside the Unicode BMP in Tcl message catalogs (.msg files) will work regardless of the locale's encoding.
- Perl:
- xgettext now reports warnings instead of fatal errors.
- xgettext now recognizes strings with embedded expressions (a.k.a. interpolated strings).
- PHP:
- xgettext now recognizes strings with embedded expressions.
- xgettext now scans Heredoc and Nowdoc strings correctly.
- xgettext now regards the format string directives %E, %F, %g, %G, %h, %H as valid.
- Python:
- Runtime behaviour:
- In the C.UTF-8 locale, like in the C locale, the *gettext() functions now return the msgid untranslated. This is relevant for GNU systems, Linux with musl libc, FreeBSD, NetBSD, OpenBSD, Cygwin, and Android.
- Documentation:
- The section "Preparing Strings" now gives more advice how to deal with string concatenation and strings with embedded expressions.
- xgettext:
- Most of the diagnostics emitted by xgettext are now labelled as "warning" or "error".
- msgmerge:
- The option '--sorted-output' is now deprecated.
- libgettextpo library:
- This library is now multithread-safe.
- The function 'po_message_set_format' now supports resetting a format string mark.
Real Python: Python String Formatting: Available Tools and Their Features
String formatting is essential in Python for creating dynamic and well-structured text by inserting values into strings. This tutorial covers various methods, including f-strings, the .format() method, and the modulo operator (%). Each method has unique features and benefits for different use cases. The string formatting mini-language provides additional control over the output format, allowing for aligned text, numeric formatting, and more.
F-strings provide an intuitive and efficient way to embed expressions inside string literals. The .format() method offers flexibility for lazy interpolation and is compatible with Python’s formatting mini-language. The modulo operator is an older technique still found in legacy code. Understanding these methods will help you choose the best option for your specific string formatting needs.
By the end of this tutorial, you’ll understand that:
- String formatting in Python involves inserting and formatting values within strings using interpolation.
- Python supports different types of string formatting, including f-strings, the .format() method, and the modulo operator (%).
- F-strings are generally the most readable and efficient option for eager interpolation in Python.
- Python’s string formatting mini-language offers features like alignment, type conversion, and numeric formatting.
- While f-strings are more readable and efficient compared to .format() and the % operator, the .format() method supports lazy evaluation.
To get the most out of this tutorial, you should be familiar with Python’s string data type and the available string interpolation tools. Having a basic knowledge of the string formatting mini-language is also a plus.
Get Your Code: Click here to download the free sample code you’ll use to learn about Python’s string formatting tools.
Take the Quiz: Test your knowledge with our interactive “Python String Formatting: Available Tools and Their Features” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python String Formatting: Available Tools and Their FeaturesYou can take this quiz to test your understanding of the available tools for string formatting in Python, as well as their strengths and weaknesses. These tools include f-strings, the .format() method, and the modulo operator.
Interpolating and Formatting Strings in PythonString interpolation involves generating strings by inserting other strings or objects into specific places in a base string or template. For example, here’s how you can do some string interpolation using an f-string:
Python >>> name = "Bob" >>> f"Hello, {name}!" 'Hello, Bob!' Copied!In this quick example, you first have a Python variable containing a string object, "Bob". Then, you create a new string using an f-string. In this string, you insert the content of your name variable using a replacement field. When you run this last line of code, Python builds a final string, 'Hello, Bob!'. The insertion of name into the f-string is an interpolation.
Note: To dive deeper into string interpolation, check out the String Interpolation in Python: Exploring Available Tools tutorial.
When you do string interpolation, you may need to format the interpolated values to produce a well-formatted final string. To do this, you can use different string interpolation tools that support string formatting. In Python, you have these three tools:
- F-strings
- The str.format() method
- The modulo operator (%)
The first two tools support the string formatting mini-language, a feature that allows you to fine-tune your strings. The third tool is a bit old and has fewer formatting options. However, you can use it to do some minimal formatting.
Note: The built-in format() function is yet another tool that supports the format specification mini-language. This function is typically used for date and number formatting, but you won’t cover it in this tutorial.
In the following sections, you’ll start by learning a bit about the string formatting mini-language. Then, you’ll dive into using this language, f-strings, and the .format() method to format your strings. Finally, you’ll learn about the formatting capabilities of the modulo operator.
Using F-Strings to Format StringsPython 3.6 added a string interpolation and formatting tool called formatted string literals, or f-strings for short. As you’ve already learned, f-strings let you embed Python objects and expressions inside your strings. To create an f-string, you must prefix the string with an f or F and insert replacement fields in the string literal. Each replacement field must contain a variable, object, or expression:
Python >>> f"The number is {42}" 'The number is 42' >>> a = 5 >>> b = 10 >>> f"{a} plus {b} is {a + b}" '5 plus 10 is 15' Copied!In the first example, you define an f-string that embeds the number 42 directly into the resulting string. In the second example, you insert two variables and an expression into the string.
Formatted string literals are a Python parser feature that converts f-strings into a series of string constants and expressions. These are then joined up to build the final string.
Using the Formatting Mini-Language With F-StringsWhen you use f-strings to create strings through interpolation, you need to use replacement fields. In f-strings, you can define a replacement field using curly brackets ({}) as in the examples below:
Python >>> debit = 300.00 >>> credit = 450.00 >>> f"Debit: ${debit}, Credit: ${credit}, Balance: ${credit - debit}" 'Debit: $300, Credit: $450.0, Balance: $150.0' Copied! Read the full article at https://realpython.com/python-string-formatting/ »[ 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 ]
Real Python: How to Check if a Python String Contains a Substring
To check if a string contains another string in Python, use the in membership operator. This is the recommended method for confirming the presence of a substring within a string. The in operator is intuitive and readable, making it a straightforward way to evaluate substring existence.
Additionally, you can use string methods like .count() and .index() to gather more detailed information about substrings, such as their frequency and position. For more complex substring searches, use regular expressions with the re module. When you’re dealing with tabular data, then pandas provides efficient tools for searching for substrings within DataFrame columns.
By the end of this tutorial, you’ll understand that:
- The in membership operator is the recommended way to check if a Python string contains a substring.
- Converting input text to lowercase generalizes substring checks by removing case sensitivity.
- The .count() method counts occurrences of a substring, while .index() finds the first occurrence’s position.
- Regular expressions in the re module allow for advanced substring searches based on complex conditions.
- The .str.contains() method in pandas identifies which DataFrame entries contain a specific substring.
Understanding these methods and tools enables you to effectively check for substrings in Python strings, catering to various needs from simple checks to complex data analysis.
Get Your Code: Click here to download the free sample code that you’ll use to check if a string contains a substring.
How to Confirm That a Python String Contains Another StringIf you need to check whether a string contains a substring, use Python’s membership operator in. In Python, this is the recommended way to confirm the existence of a substring in a string:
Python >>> raw_file_content = """Hi there and welcome. ... This is a special hidden file with a SECRET secret. ... I don't want to tell you The Secret, ... but I do want to secretly tell you that I have one.""" >>> "secret" in raw_file_content True Copied!The in membership operator gives you a quick and readable way to check whether a substring is present in a string. You may notice that the line of code almost reads like English.
Note: If you want to check whether the substring is not in the string, then you can use not in:
Python >>> "secret" not in raw_file_content False Copied!Because the substring "secret" is present in raw_file_content, the not in operator returns False.
When you use in, the expression returns a Boolean value:
- True if Python found the substring
- False if Python didn’t find the substring
You can use this intuitive syntax in conditional statements to make decisions in your code:
Python >>> if "secret" in raw_file_content: ... print("Found!") ... Found! Copied!In this code snippet, you use the membership operator to check whether "secret" is a substring of raw_file_content. If it is, then you’ll print a message to the terminal. Any indented code will only execute if the Python string that you’re checking contains the substring that you provide.
Note: Python considers empty strings always as a substring of any other string, so checking for the empty string in a string returns True:
Python >>> "" in "secret" True Copied!This may be surprising because Python considers emtpy strings as false, but it’s an edge case that is helpful to keep in mind.
The membership operator in is your best friend if you just need to check whether a Python string contains a substring.
However, what if you want to know more about the substring? If you read through the text stored in raw_file_content, then you’ll notice that the substring occurs more than once, and even in different variations!
Which of these occurrences did Python find? Does capitalization make a difference? How often does the substring show up in the text? And what’s the location of these substrings? If you need the answer to any of these questions, then keep on reading.
Generalize Your Check by Removing Case SensitivityPython strings are case sensitive. If the substring that you provide uses different capitalization than the same word in your text, then Python won’t find it. For example, if you check for the lowercase word "secret" on a title-case version of the original text, the membership operator check returns False:
Python >>> title_cased_file_content = """Hi There And Welcome. ... This Is A Special Hidden File With A Secret Secret. ... I Don't Want To Tell You The Secret, ... But I Do Want To Secretly Tell You That I Have One.""" >>> "secret" in title_cased_file_content False Copied!Despite the fact that the word secret appears multiple times in the title-case text title_cased_file_content, it never shows up in all lowercase. That’s why the check that you perform with the membership operator returns False. Python can’t find the all-lowercase string "secret" in the provided text.
Humans have a different approach to language than computers do. This is why you’ll often want to disregard capitalization when you check whether a string contains a substring in Python.
Read the full article at https://realpython.com/python-string-contains-substring/ »[ 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 ]
Real Python: Python Exceptions: An Introduction
Python exceptions provide a mechanism for handling errors that occur during the execution of a program. Unlike syntax errors, which are detected by the parser, Python raises exceptions when an error occurs in syntactically correct code. Knowing how to raise, catch, and handle exceptions effectively helps to ensure your program behaves as expected, even when encountering errors.
In Python, you handle exceptions using a try … except block. This structure allows you to execute code normally while responding to any exceptions that may arise. You can also use else to run code if no exceptions occur, and the finally clause to execute code regardless of whether an exception was raised.
By the end of this tutorial, you’ll understand that:
- Exceptions in Python occur when syntactically correct code results in an error.
- You can handle exceptions using the try, except, else, and finally keywords.
- The try … except block lets you execute code and handle exceptions that arise.
- Python 3 introduced more built-in exceptions compared to Python 2, making error handling more granular.
- It’s bad practice to catch all exceptions at once using except Exception or the bare except clause.
- Combining try, except, and pass allows your program to continue silently without handling the exception.
- Using try … except is not inherently bad, but you should use it judiciously to handle only known issues appropriately.
In this tutorial, you’ll get to know Python exceptions and all relevant keywords for exception handling by walking through a practical example of handling a platform-related exception. Finally, you’ll also learn how to create your own custom Python exceptions.
Get Your Code: Click here to download the free sample code that shows you how exceptions work in Python.
Take the Quiz: Test your knowledge with our interactive “Python Exceptions: An Introduction” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python Exceptions: An IntroductionIn this quiz, you'll test your understanding of Python exceptions. You'll cover the difference between syntax errors and exceptions and learn how to raise exceptions, make assertions, and use the try and except block.
Understanding Exceptions and Syntax ErrorsSyntax errors occur when the parser detects an incorrect statement. Observe the following example:
Python Traceback >>> print(0 / 0)) File "<stdin>", line 1 print(0 / 0)) ^ SyntaxError: unmatched ')' Copied!The arrow indicates where the parser ran into the syntax error. Additionally, the error message gives you a hint about what went wrong. In this example, there was one bracket too many. Remove it and run your code again:
Python >>> print(0 / 0) Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: division by zero Copied!This time, you ran into an exception error. This type of error occurs whenever syntactically correct Python code results in an error. The last line of the message indicates what type of exception error you ran into.
Instead of just writing exception error, Python details what type of exception error it encountered. In this case, it was a ZeroDivisionError. Python comes with various built-in exceptions as well as the possibility to create user-defined exceptions.
Raising an Exception in PythonThere are scenarios where you might want to stop your program by raising an exception if a condition occurs. You can do this with the raise keyword:
You can even complement the statement with a custom message. Assume that you’re writing a tiny toy program that expects only numbers up to 5. You can raise an error when an unwanted condition occurs:
Python low.py number = 10 if number > 5: raise Exception(f"The number should not exceed 5. ({number=})") print(number) Copied!In this example, you raised an Exception object and passed it an informative custom message. You built the message using an f-string and a self-documenting expression.
When you run low.py, you’ll get the following output:
Python Traceback Traceback (most recent call last): File "./low.py", line 3, in <module> raise Exception(f"The number should not exceed 5. ({number=})") Exception: The number should not exceed 5. (number=10) Copied!The program comes to a halt and displays the exception to your terminal or REPL, offering you helpful clues about what went wrong. Note that the final call to print() never executed, because Python raised the exception before it got to that line of code.
With the raise keyword, you can raise any exception object in Python and stop your program when an unwanted condition occurs.
Debugging During Development With assert Read the full article at https://realpython.com/python-exceptions/ »[ 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 ]
This Week in KDE Apps: OptiImage first release, Itinerary redesign and more
Welcome to a new issue of "This Week in KDE Apps"! Every week we cover as much as possible of what's happening in the world of KDE apps.
This week, we are continuing to polish our applications for the KDE Gear 24.12.0 release, but already starting the work for the 25.04 release happening next year. We also made the first release of OptiImage, an image size optimizer.
Meanwhile, as part of the 2024 end-of-year fundraiser, you can "Adopt an App" in a symbolic effort to support your favorite KDE app. This week, we are particularly grateful to Yogesh Girikumar, Luca Weiss and 1peter10 for supporting Itinerary; Tobias Junghans and Curtis for Konsole; Daniel Bagge and Xavier Guillot for Filelight; F., Christian Terboven, Kevin Krammer and Sean M. for the Kontact suite; Tanguy Fardet, dabe, lengau and Joshua Strobl for NeoChat; Pablo Rauzy for KWrite; PJ. for LabPlot; Dominik Barth for Kasts; Kevin Krammer for Ruqola; Florent Tassy, elbekai and retrokestrel for Gwenview; MathiusD and Dadanaut for Elisa, Andreas Kilgus and @rph.space for Konqueror; trainden@lemmy.blahaj.zone for KRDC; Marco Rebhan and Travis McCoy for Ark; and domportera for Krfb.
Getting back to all that's new in the KDE App scene, let's dig in!
GCompris Educational game for childrenGCompris 4.3 is out and contains bug fixes and graphics improvements on multiple activities.
KDE Itinerary Digital travel assistantWe redesigned the timeline of your trips and the query result pages when searching for a public transport connection to work better with a small screen while still showing all the relevant information. (Carl Schwan, 25.04.0. Link 1 and link 2)
Checking for updates and downloading map data now are scoped to a trip and will only query data from the internet related to the trip. (Volker Krause, 25.04.0. Link 1 and link 2)
The export buttons used in Itinerary are not blurry anymore. (Carl Schwan, 24.12.0. Link)
Add an extractor for GoOut tickets (an event platform in Poland, Czechia and Slovakia) as well as luma and the pkpasses from Flixbus.de. (David Pilarcik, 24.12.0. Link, link 2 and link 3)
Optimize querying a location by sorting the list of countries only once instead of hundreds of times. (Carl Schwan, 24.12.0. Link)
OptiImage Image optimizer to reduce the size of imagesOptiImage 1.0.0 is out! This is the initial release of this image size optimizer and you can read all the details on the announcement blog post.
Karp KDE arranger for PDFsKarp is now directly using the QPDF library instead of invoking a separate process, which improves the speed while making PDF operation more reliable. (Tomasz Bojczuk. Link)
Kate Advanced Text EditorMake it again possible to scroll, select text and click on links inside documentation tooltips in Kate. (Leia uwu, 24.12.0. Link) Leia also improved the tooltip positioning logic so that it doesn't obscure the hovered word. (Leia uwu, 25.04.0. Link)
KMail A feature-rich email applicationThe mail folder selection dialog now remembers which folders were collapsed and expanded between invocations.
Ruqola Rocket Chat ClientRuqola 2.3.2 is out and includes many fixes for RocketChat 7.0!
Spectacle Screenshot Capture UtilityOn Wayland, the "Window Under Cursor" mode is renamed to "Select Window" as you need to select the window. (Noah Davis, 25.04.0. Link)
Tokodon Browse the FediverseBetter icon for Android, which is also adaptable depending on your theme. (Alois Spitzbart, 24.12. Link)
Streaming timeline events and notifications now work for servers using GoToSocial. (snow flurry, 24.12. Link)
Slightly improved the performance of the timeline, with particular focus on the media. (Joshua Goins, 24.12. Link)
In the status composer, user info is now shown - useful if you post from multiple accounts. Also, the look of the text box has been updated. (Joshua Goins, 24.12. Link)
Added the ability to configure your notification policy. This allows you to reject or allow notifications e.g. for new accounts. (Joshua Goins, 25.03. Link)
Improved the appearance of the search page on desktop. (Joshua Goins, 24.12. Link)
Added preliminary support for Iceshrimp.NET instances. (Joshua Goins, 24.12. Link)
Added an error log in the UI to keep track of network errors. (Joshua Goins, 25.03. Link)
Kirigami AddonsKirigami Addons 1.6.0. is out! You can read the full announcement on my (Carl's) blog. This week we also made the following changes:
Speedup loading Kirigami pages using FormComboboxDelegate, this is particularly noticable for the country combobox in Itinerary, but affects more applications. (Carl Schwan, Kirigami Addons 1.6.0. Link)
Add new RadioSelector and FormRadioSelectorDelegate components to Kirigami Addons. On the screenshot below you can see how they're used in Itinerary. (Mathis Brüchert, Kirigami Addons 1.6.0. Link)
…And Everything ElseThis blog only covers the tip of the iceberg! If you’re hungry for more, check out Nate's blog about Plasma and be sure not to miss his This Week in Plasma series, where every Saturday he covers all the work being put into KDE's Plasma desktop environment.
For a complete overview of what's going on, visit KDE's Planet, where you can find all KDE news unfiltered directly from our contributors.
Get InvolvedThe KDE organization has become important in the world, and your time and contributions have helped us get there. As we grow, we're going to need your support for KDE to become sustainable.
You can help KDE by becoming an active community member and getting involved. Each contributor makes a huge difference in KDE — you are not a number or a cog in a machine! You don’t have to be a programmer either. There are many things you can do: you can help hunt and confirm bugs, even maybe solve them; contribute designs for wallpapers, web pages, icons and app interfaces; translate messages and menu items into your own language; promote KDE in your local community; and a ton more things.
You can also help us by donating. Any monetary contribution, however small, will help us cover operational costs, salaries, travel expenses for contributors and in general just keep KDE bringing Free Software to the world.
To get your application mentioned here, please ping us in invent or in Matrix.
LostCarPark Drupal Blog: Drupal Advent Calendar day 1 - Starshot: a Brief Introduction to Drupal CMS
Welcome to this year’s Drupal Advent Calendar, and this year the focus is on the most important Drupal initiative in quite some time.
Code named Starshot, it aims to take Drupal to a new level of user friendliness and ease of use.
Over recent versions, Drupal has become incredibly powerful, and it now powers many enterprise websites for major corporations, governments, and NGOs around the world.
Starshot was announced by the founder of the Drupal project, Dries Buytaert, at DrupalCon Portland, in April of this year. This proposed a new default installation of Drupal with many extra features, and…
TagsTalk Python to Me: #487: Building Rust Extensions for Python
Tryton News: Newsletter December 2024
During the last month we focused on fixing bugs, improving the behaviour of things, speeding-up performance issues - building on the changes from our last Tryton Release 7.4. We also added some new features which we would like to introduce to you in this newsletter.
For an in depth overview of the Tryton issues please take a look at our issue tracker or see the issues and merge requests filtered by label.
Changes for the User Accounting, Invoicing and PaymentsNow we compute the maturity date of grouped account move lines per debit/credit.
New ReleasesWe released bug fixes for the currently maintained long term support series
7.0 and 6.0, and for the penultimate series 7.4 and 7.2.
Now we support plural translations for report and ir.message. The other translatable strings are labels that have no count. The plural rule is a Python expression which returns an integer following the gettext design. For the report, it is possible to use the Genshi i18n:choose, i18n:singular, i18n:plural and for OpenDocument the gettext and ngettext methods are added to the evaluation context.
Changes for the System AdministratorNow we can set the decimal precision of default context from the environment variable TRYTOND_DECIMAL_PREC.
Changes for Implementers and DevelopersTo provide better feedback to the user, now we also add equivalent domains of SQL constraints.
Now we extract the timesheet cost computation from the _get_cost method to allow customization e.g. to use a different composition of the costs.
Also we round the work total with currency digits in get_total method now, to allow extending the computation methods, without rounding too early.
A trytond.model.Model is defined by a python class:
from trytond.model import ModelSQL, fields class Party(ModelSQL): """Party""" __name__ = 'party.party'It was needed to define three different identifiers:
- Python class name: Party
- class doc-string: Party
- trytond.model.Model.__name__: party.party
The translated text string of the model which is shown in the user interface was extracted from the __doc__ (doc-string) of the class definition.
Now we replaced the class doc-string extraction by parsing the trytond.model.Model.__name__ attribute and try to make it human readable. Changing an existing __name__ attribute usually implies a database migration.
To work-around existing not so useful __name__ values we introduced the new class attribute trytond.model.Model.__string__ which let you define the model string directly.
We also take the chance and clean-up the name-space by renaming
- ir.model.name field into ir.model.string,
- ir.model.model field into ir.model.name and
- ir.model.field.field_description into ir.model.field.string
Finally we removed most of the former doc-strings from all the classes, only in some rare cases we add the __string__ attribute.
1 post - 1 participant
Junichi Uekawa: Lots of travel and back to Tokyo.
Russ Allbery: Review: Unexploded Remnants
Review: Unexploded Remnants, by Elaine Gallagher
Publisher: Tordotcom Copyright: 2024 ISBN: 1-250-32522-6 Format: Kindle Pages: 111Unexploded Remnants is a science fiction adventure novella. The protagonist and world background would support an episodic series, but as of this writing it stands alone. It is Elaine Gallagher's first professional publication.
Alice is the last survivor of Earth: an explorer, information trader, and occasional associate of the Archive. She scouts interesting places, looks for inconsistencies in the stories the galactic civilizations tell themselves, and pokes around ruins for treasure. As this story opens, she finds a supposedly broken computer core in the Alta Sidoie bazaar that is definitely not what the trader thinks it is. Very shortly thereafter, she's being hunted by a clan of dangerous Delosi while trying to decide what to do with a possibly malevolent AI with frightening intrusion abilities.
This is one of those stories where all the individual pieces sounded great, but the way they were assembled didn't click for me. Unusually, I'm not entirely sure why. Often it's the characters, but I liked Alice well enough. The Lewis Carroll allusions were there but not overdone, her computer agent Bugs is a little too much of a Warner Brothers cartoon but still interesting, and the world building has plenty of interesting hooks. I certainly can't complain about the pacing: the plot moves briskly along to a somewhat predictable but still adequate conclusion. The writing is smooth and competent, and the world is memorable enough that I'm still thinking about it.
And yet, I never connected with this story. I think it may be because both Alice and the tight third-person narrator tend towards breezy confidence and matter-of-fact descriptions. Alice does, at times, get scared or angry, but I never felt those emotions. They were just events that were described to me. There wasn't an emotional hook, a place where the character grabbed me, and so it felt like everything was happening at an odd remove. The advantage of this approach is that there are no overwrought emotional meltdowns or brooding angstful protagonists, just an adventure story about a competent and thoughtful character, but I think I wanted a bit more emotional involvement than I got.
The world background is the best part and feels like it could be part of a larger series. The Milky Way is connected by an old, vast, and only partly understood network of teleportation portals, which had cut off Earth for unknown reasons and then just as mysteriously reactivated when Alice, then Andrew, drunkenly poked at a standing stone while muttering an old prayer in Gaelic. The Archive spent a year sorting out her intellectual diseases (capitalism was particularly alarming) and giving her a fresh start with a new body. Humanity subsequently destroyed itself in a paroxysm of reactionary violence, leaving Alice a free agent, one of a kind in a galaxy of dizzying variety and forgotten history.
Gallagher makes great use of the weirdness of the portal network to create a Star Wars style of universe: the focus is more on the diversity of the planets and alien species than on a coherent unifying structure. The settings of this book are not prone to Planet of the Hats problems. They instead have the contrasts that one would get if one dropped portals near current or former Earth population centers and then took a random walk through them (or, in other words, what playing GeoGuessr on a world map feels like). I liked this effect, but I have to admit that it also added to that sense of sliding off the surface of the story. The place descriptions were great bits of atmosphere, but I never cared about them. There isn't enough emotional coherence to make them memorable.
One of the more notable quirks of this story is the description of ideologies and prejudices as viral memes that can be cataloged, cured, and deployed like weapons. This is a theme of the world-building as well: this society, or at least the Archive-affiliated parts of it, classifies some patterns of thought as potentially dangerous but treatable contagious diseases. I'm not going to object too much to this as a bit of background and characterization in a fairly short novella stuffed with a lot of other world-building and plot, but there's was something about treating ethical systems like diseases that bugged me in much the same way that medicalization of neurodiversity bugs me. I think some people will find that sense of moral clarity relaxing and others will find it vaguely irritating, and I seem to have ended up in the second group.
Overall, I would classify this as an interesting not-quite-success. It felt like a side story in a larger universe, like a story that would work better if I already knew Alice from other novels and had an established emotional connection with her. As is, I would not really recommend it, but there are enough good pieces here that I would be interested to see what Gallagher does next.
Rating: 6 out of 10
QML Dependency tracking in Debian
Tracking library dependencies work in Debian to resolve from symbols usage to a library and add this to the list of dependencies. That is working for years now. The KDE community nowadays create more and more QML based applications. Unfortunately QML is a interpreted language, this means missing QML dependencies will only be an issue at runtime.
To fix this I created dh_qmldeps, that searches for QML dependencies at build time and will fail if it can't resolve the QML dependency.
Me didn't create an own QML interpreter, just using qmlimportscanner behind the scenes and process the output further to resolve the QML modules to Debian packages.
The workflow is like follows:
The package compiles normally and split to the binary packages. Than dh_qmldeps scans through the package content to find QML content ( .qml files, or qmldirfor QML modules). All founded files will be scanned by qmlimportscanner, the output is a list of depended QML modules. As QML modules have a standardized file path, we can ask the Debian system, which packages ship this file path. We end up with a list of Debian packages in the variable ${qml6:Depends}. This variable can be attached to the list of dependencies of the scanned package. A maintainer can also lower some dependencies to Recommends or Suggest, if needed.
You can find the source code on salsa and usage documentation you can find on https://qt-kde-team.pages.debian.net/dh_qmldeps.html.
The last weeks I now enabled dh_qmldeps for newly every package, that creates a QML6 module package. So the first bugs are solved and it should be usable for more packages.
By scanning with qmlimportscanner trough all code, I found several non-existing QML modules:
- import QtQuick3DPrivate qt6-multimedia - no Private QML module QTBUG-131753.
- import QtQuickPrivate qt6-graphs - no Private QML module QTBUG-131754.
- import QtQuickTimeline qt6-quicktimeline - the correct QML name is QtQuick.Timeline QTBUG-131755.
- import QtQuickControls2 qt6-webengine - looks like a porting bug as the QML6 modules name is QtQuick.Controls QTBUG-131756.
- import QtGraphicalEffects kquickimageeditor - the correct name is for QML6 is qt5compat.graphicaleffects, properly as it is an example nobody checks it kquickimageeditor!7.
YEAH - the first milestone is reached. We are able to simply handle QML modules.
But QML applications there is still room for improvement. In apps the QML files are inside the executable. Additionally applications create internal QML modules, that are shipped directly in the same executable. I still search for a good way to analyse an executable to get a list of internal QML modules and a list of included QML files. Any ideas are welcomed :)
As workaround dh_qmldeps scans currently all QML files inside the application source code.
Sandro Knauß: QML Dependency tracking in Debian
Tracking library dependencies work in Debian to resolve from symbols usage to a library and add this to the list of dependencies. That is working for years now. The KDE community nowadays create more and more QML based applications. Unfortunately QML is a interpreted language, this means missing QML dependencies will only be an issue at runtime.
To fix this I created dh_qmldeps, that searches for QML dependencies at build time and will fail if it can't resolve the QML dependency.
Me didn't create an own QML interpreter, just using qmlimportscanner behind the scenes and process the output further to resolve the QML modules to Debian packages.
The workflow is like follows:
The package compiles normally and split to the binary packages. Than dh_qmldeps scans through the package content to find QML content ( .qml files, or qmldirfor QML modules). All founded files will be scanned by qmlimportscanner, the output is a list of depended QML modules. As QML modules have a standardized file path, we can ask the Debian system, which packages ship this file path. We end up with a list of Debian packages in the variable ${qml6:Depends}. This variable can be attached to the list of dependencies of the scanned package. A maintainer can also lower some dependencies to Recommends or Suggest, if needed.
You can find the source code on salsa and usage documentation you can find on https://qt-kde-team.pages.debian.net/dh_qmldeps.html.
The last weeks I now enabled dh_qmldeps for newly every package, that creates a QML6 module package. So the first bugs are solved and it should be usable for more packages.
By scanning with qmlimportscanner trough all code, I found several non-existing QML modules:
- import QtQuick3DPrivate qt6-multimedia - no Private QML module QTBUG-131753.
- import QtQuickPrivate qt6-graphs - no Private QML module QTBUG-131754.
- import QtQuickTimeline qt6-quicktimeline - the correct QML name is QtQuick.Timeline QTBUG-131755.
- import QtQuickControls2 qt6-webengine - looks like a porting bug as the QML6 modules name is QtQuick.Controls QTBUG-131756.
- import QtGraphicalEffects kquickimageeditor - the correct name is for QML6 is qt5compat.graphicaleffects, properly as it is an example nobody checks it kquickimageeditor!7.
YEAH - the first milestone is reached. We are able to simply handle QML modules.
But QML applications there is still room for improvement. In apps the QML files are inside the executable. Additionally applications create internal QML modules, that are shipped directly in the same executable. I still search for a good way to analyse an executable to get a list of internal QML modules and a list of included QML files. Any ideas are welcomed :)
As workaround dh_qmldeps scans currently all QML files inside the application source code.
Dima Kogan: Strava track filtering validation
After years of seeing people's strava tracks, I became convinced that they insufficiently filter the data, resulting in over-estimating the effort. Today I did a bit of lazy analysis, and half-confirmed this: in the one case I looked at, strava reported reasonable elevation gain numbers, but greatly overestimated the distance traveled.
I looked at a single gps track of a long bike ride. This was uploaded to strava manually, as a .gpx file. I can imagine that different things happen if you use the strava app or some device that integrates with the service (the filtering might happen before the data hits the server, and the server could decide to not apply any more filtering).
I processed the data with a simple hysteretic filter, ignoring small changes in position and elevation, trying out different thresholds in the process. I completely ignore the timestamps, and only look at the differences between successive points. This handles the usual GPS noise; it does not handle GPS jumps, which I completely ignore in this analysis. Ignoring these would produce inflated elevation/gain numbers, but I'm working with a looong track, so hopefully this is a small effect.
Clearly this is not scientific, but it's something.
The codeParsing .gpx is slow (this is a big file), so I cache that into a .vnl:
import sys import gpxpy filename_in = 'INPUT.gpx' filename_out = 'OUTPUT.gpx' with open(filename_in, 'r') as f: gpx = gpxpy.parse(f) f_out = open(filename_out, 'w') tracks = gpx.tracks if len(tracks) != 1: print("I want just one track", file=sys.stderr) sys.exit(1) track = tracks[0] segments = track.segments if len(segments) != 1: print("I want just one segment", file=sys.stderr) sys.exit(1) segment = segments[0] time0 = segment.points[0].time print("# time lat lon ele_m") for p in segment.points: print(f"{(p.time - time0).seconds} {p.latitude} {p.longitude} {p.elevation}", file = f_out)And I process this data with the different filters (this is a silly Python loop, and is slow):
#!/usr/bin/python3 import sys import numpy as np import numpysane as nps import gnuplotlib as gp import vnlog import pyproj geod = None def dist_ft(lat0,lon0, lat1,lon1): global geod if geod is None: geod = pyproj.Geod(ellps='WGS84') return \ geod.inv(lon0,lat0, lon1,lat1)[2] * 100./2.54/12. f = 'OUTPUT.gpx' track,list_keys,dict_key_index = \ vnlog.slurp(f) t = track[:,dict_key_index['time' ]] lat = track[:,dict_key_index['lat' ]] lon = track[:,dict_key_index['lon' ]] ele_ft = track[:,dict_key_index['ele_m']] * 100./2.54/12. @nps.broadcast_define( ( (), ()), (2,)) def filter_track(ele_hysteresis_ft, dxy_hysteresis_ft): dist = 0.0 ele_gain_ft = 0.0 lon_accepted = None lat_accepted = None ele_accepted = None for i in range(len(lat)): if ele_accepted is not None: dxy_here = dist_ft(lat_accepted,lon_accepted, lat[i],lon[i]) dele_here = np.abs( ele_ft[i] - ele_accepted ) if dxy_here < dxy_hysteresis_ft and dele_here < ele_hysteresis_ft: continue if ele_ft[i] > ele_accepted: ele_gain_ft += dele_here; dist += np.sqrt(dele_here * dele_here + dxy_here * dxy_here) lon_accepted = lon[i] lat_accepted = lat[i] ele_accepted = ele_ft[i] # lose the last point. It simply doesn't matter dist_mi = dist / 5280. return np.array((ele_gain_ft, dist_mi)) Nele_hysteresis_ft = 20 ele_hysteresis0_ft = 5 ele_hysteresis1_ft = 100 ele_hysteresis_ft_all = np.linspace(ele_hysteresis0_ft, ele_hysteresis1_ft, Nele_hysteresis_ft) Ndxy_hysteresis_ft = 20 dxy_hysteresis0_ft = 5 dxy_hysteresis1_ft = 1000 dxy_hysteresis_ft = np.linspace(dxy_hysteresis0_ft, dxy_hysteresis1_ft, Ndxy_hysteresis_ft) # shape (Nele,Ndxy,2) gain,distance = \ nps.mv( filter_track( nps.dummy(ele_hysteresis_ft_all,-1), dxy_hysteresis_ft), -1,0 ) # Stolen from mrcal def options_heatmap_with_contours( plotoptions, # we update this on output *, contour_min = 0, contour_max, contour_increment = None, do_contours = True, contour_labels_styles = 'boxed', contour_labels_font = None): r'''Update plotoptions, return curveoptions for a contoured heat map''' gp.add_plot_option(plotoptions, 'set', ('view equal xy', 'view map')) if do_contours: if contour_increment is None: # Compute a "nice" contour increment. I pick a round number that gives # me a reasonable number of contours Nwant = 10 increment = (contour_max - contour_min)/Nwant # I find the nearest 1eX or 2eX or 5eX base10_floor = np.power(10., np.floor(np.log10(increment))) # Look through the options, and pick the best one m = np.array((1., 2., 5., 10.)) err = np.abs(m * base10_floor - increment) contour_increment = -m[ np.argmin(err) ] * base10_floor gp.add_plot_option(plotoptions, 'set', ('key box opaque', 'style textbox opaque', 'contour base', f'cntrparam levels incremental {contour_max},{contour_increment},{contour_min}')) if contour_labels_font is not None: gp.add_plot_option(plotoptions, 'set', f'cntrlabel format "%d" font "{contour_labels_font}"' ) else: gp.add_plot_option(plotoptions, 'set', f'cntrlabel format "%.0f"' ) plotoptions['cbrange'] = [contour_min, contour_max] # I plot 3 times: # - to make the heat map # - to make the contours # - to make the contour labels _with = np.array(('image', 'lines nosurface', f'labels {contour_labels_styles} nosurface')) else: gp.add_plot_option(plotoptions, 'unset', 'key') _with = 'image' using = \ f'({dxy_hysteresis0_ft}+$1*{float(dxy_hysteresis1_ft-dxy_hysteresis0_ft)/(Ndxy_hysteresis_ft-1)}):' + \ f'({ele_hysteresis0_ft}+$2*{float(ele_hysteresis1_ft-ele_hysteresis0_ft)/(Nele_hysteresis_ft-1)}):3' plotoptions['_3d'] = True plotoptions['_xrange'] = [dxy_hysteresis0_ft,dxy_hysteresis1_ft] plotoptions['_yrange'] = [ele_hysteresis0_ft,ele_hysteresis1_ft] plotoptions['ascii'] = True # needed for using to work gp.add_plot_option(plotoptions, 'unset', 'grid') return \ dict( tuplesize=3, legend = "", # needed to force contour labels using = using, _with=_with) contour_granularity = 1000 plotoptions = dict() curveoptions = \ options_heatmap_with_contours( plotoptions, # we update this on output # round down to the nearest contour_granularity contour_min = (np.min(gain) // contour_granularity)*contour_granularity, # round up to the nearest contour_granularity contour_max = ((np.max(gain) + (contour_granularity-1)) // contour_granularity) * contour_granularity, do_contours = True) gp.add_plot_option(plotoptions, 'unset', 'key') gp.add_plot_option(plotoptions, 'set', 'size square') gp.plot(gain, xlabel = "Distance hysteresis (ft)", ylabel = "Elevation hysteresis (ft)", cblabel = "Elevation gain (ft)", wait = True, **curveoptions, **plotoptions, title = 'Computed gain vs filtering parameters') contour_granularity = 10 plotoptions = dict() curveoptions = \ options_heatmap_with_contours( plotoptions, # we update this on output # round down to the nearest contour_granularity contour_min = (np.min(distance) // contour_granularity)*contour_granularity, # round up to the nearest contour_granularity contour_max = ((np.max(distance) + (contour_granularity-1)) // contour_granularity) * contour_granularity, do_contours = True) gp.add_plot_option(plotoptions, 'unset', 'key') gp.add_plot_option(plotoptions, 'set', 'size square') gp.plot(distance, xlabel = "Distance hysteresis (ft)", ylabel = "Elevation hysteresis (ft)", cblabel = "Distance (miles)", wait = True, **curveoptions, **plotoptions, title = 'Computed distance vs filtering parameters') Results: gainStrava says the gain was 46307ft. The analysis says:
These show the filtered gain for different values of the distance and gain hysteresis thresholds. The same data is shown at diffent zoom levels. There's no sweet spot, but we get 46307ft with a reasonable amount of filtering. Maybe 46307ft is a bit low even.
Results: distanceStrava says the distance covered was 322 miles. The analysis says:
Once again, there's no sweet spot, but we get 322 miles only if we apply no filtering at all. That's clearly too high, and is not reasonable. From the map (and from other people's strava routes) the true distance is closer to 305 miles. Why those people's strava numbers are more believable is anybody's guess.