27 Nov 2019

feedPlanet Twisted

Itamar Turner-Trauring: Job negotiation for programmers: the basic principles

You need to negotiate at a new job: for your salary, or benefits, or my personal favorite, a shorter workweek. You're not sure what to do, or how to approach it, or what to say when the company says "how much do you want?" or "here's our offer-what do you say?"

Here's the thing: that final conversation about salary might be the most nerve-wracking part, but the negotiation process starts much much earlier. Which means you can enter that final conversation having positioned yourself for success-and feeling less stressed about it too.

The way you can do that is following certain basic principles, which I'll be covering in this article. I'm going to be focusing on salary negotiation as an example, but the same principles will apply when negotiating for a shorter workweek.

In particular, I'll be talking about:

  1. An example from early in my career when I negotiated very very badly.
  2. The right way to negotiate, based on four principles:
    1. Employment is a negotiated relationship.
    2. Knowledge is power.
    3. Negotiate from a position of strength.
    4. Use the right tactics.

The wrong way to negotiate

Before moving on to the principles of negotiation, let me share a story of how I negotiated badly.

During my first real job search I interviewed at a company in New York City that was building a financial trading platform. They were pretty excited about some specific technologies I'd learned while working on Twisted, an open source networking framework. They offered me a job, I accepted, and my job search was over.

But then they sent me their intellectual property agreement, and I actually read legal documents; you should read them too. The agreement would have given the company ownership over any open source work I did, including work on Twisted. I wanted to ensure I could keep doing open source development, especially given that was their reason for hiring me in the first place. I asked for an exemption covering Twisted, they wouldn't agree, and so we went back and forth trying to reach an agreement.

Eventually they came back with a new offer: in return for not working on Twisted I'd get a 20% salary increase over their initial offer. I thought about it briefly, then said no and walked away from the job. Since I had neither a CS degree-I'd dropped out-nor much of an employment history, open source contribution was important to my career. It was how I'd gotten contracting work, and it was the reason they'd offered me this job. And I enjoyed doing it, too, so I wasn't willing to give it up.

I posted about this experience online, and an employee of ITA Software, which was based in the Boston area, suggested they were happy to support contributions to open source projects. It seemed worth a try, so I applied for the position. And when eventually I got a job offer from ITA and they asked me for my salary requirements, I asked for the second offer I'd gotten, the one that was 20% higher than my original offer. They accepted, and I've lived in the Boston area ever since.

As we go through the principles below, I'll come back to this story and point out how they were (mis)applied in my two negotiations.

The four principles of negotiation

You can think of the negotiation process as building on four principles:

  1. Employment is a negotiated relationship.
  2. Knowledge is power.
  3. Negotiate from a position of strength.
  4. Use the right tactics.

Let's go through them one by one.

Principle #1: Employment is a negotiated relationship

If you're an employee, your employment relationship was negotiated. When you got a job offer and accepted it, that was a negotiation, even if you didn't push back at all. Your choice isn't between negotiating and not negotiating: it's between negotiating badly, or negotiating well.

Negotiate actively

If you don't actively try to negotiate, if you don't ask for what you want, if you don't ask for what you're worth-you're unlikely to get it. Salaries, for example, are a place where your interests and your employer's are very much at odds. All things being equal, if you're doing the exact same work and have the same likelihood of leaving, would your employer prefer to pay you less or more? Most employers will pay you less if they can, and I almost had to learn that the hard way.

Applying the principle: In my story above, I never proactively negotiated. Instead, I accepted a job offer from the financial company without any sort of additional demands. If they were happy to offer me a 20% raise just to quit open source, I probably could have gotten an even higher salary if I'd just asked in the first place.

Negotiation starts early, and never ends

Not only do you need to negotiate actively, you also need to realize that negotiation starts much earlier than you think, and ends only when you leave to a different job:

In short, your whole relationship as an employee is based on negotiation.

Distinguish between friend and foe

A negotiation involves two sides: yours, and the company's. When you're negotiating it's important to remember that anyone who works for the company is on the company's side. Not yours.

I once had to negotiate the intellectual property agreement at a new job. My new employer was based in the UK, and it had a US subsidiary organized by a specialist company. These subsidiary specialists had provided the contract I was signing.

When I explained the changes I wanted to make, the manager at the subsidiary specialist told me that my complaint had no merit, because the contract had been written by the "best lawyers in Silicon Valley." But the contract had been written by lawyers working for the company, not for me. If his claim had been true (spoiler: they were not in fact the best lawyers in Silicon Valley), that would have just made my argument stronger. The better the company's lawyers, the more carefully I ought to have read the contract, and the more I ought to have pushed back.

The contracts the company wants you to sign? They were written by lawyers working for the company.

Human Resources works for the company, as does the in-house recruiter. However friendly they may seem, they are not working for you. And third-party recruiters are paid by the company, not you. It's true that sometimes their commission is tied to your salary, which means they would rather you get paid more. But since they get paid only once per candidate, volume is more important than individual transactions: it's in their best interest to get you hired as quickly as possible so they can move on to placing the next candidate.

Since all these people aren't working for you, during a negotiation they're working against you.

The only potential exception to this rule are friends who also work for the company, and aren't directly involved in the negotiation process: even if they are constrained in some ways, they're probably still on your side. They can serve as a backchannel for feedback and other information that the company can't or won't share.

Principle #2: Knowledge is power

The more you know about the situation, the better you'll do as a negotiator. More knowledge gives more power: to you, but also to the company.

Know what you want

The first thing you need to do when negotiating is understand what you want.

Do your research

You also want to understand where the other side is coming from:

The more you understand going in, the better you'll do, and that means doing your research before negotiation starts.

Applying the principle: In my story above I never did any research about salaries, either in NY or in Boston. As a result, I had no idea I was being offered a salary far below market rates.

As a comparison, here's a real example of how research can help your negotiation, from an engineer named Adam:

Adam: "Being informed on salaries really helped my negotiating position. When my latest employer made me an offer I asked them why it was lower than their average salary on Glassdoor.com. The real reason was likely 'we offer as little as possible to get you on board.' They couldn't come up with a convincing reason and so the salary was boosted 10%."

Glassdoor is a site that allows employees to anonymously share salaries and job reviews. Five minutes of research got Adam a 10% raise: not bad at all!

Listen and empathize

If you only had to make yourself happy this wouldn't be a negotiation: you need to understand the other side's needs and wants, what they're worrying about, what they're feeling. That means you need to listen, not just talk: if you do, you will often gather useful information that can help you make yourself more valuable, or address a particular worry. And you need to feel empathy towards the person you're talking to: you don't need to agree or subordinate yourself to their goals, but you do need to understand how they're feeling.

Share information carefully

Sharing information at the wrong time during a negotiation can significantly weaken your position. For example, sharing your previous salary will often anchor what the company is willing to offer you:

Adam: "I graduated from university and started working at the end of 2012. At my first job I worked for way under my market rate. I knew this and was OK with it because they were a good company.

Then I switched jobs in 2013. What I hadn't accounted for was that my salary at my first job was going to limit my future salary prospects. I had to fight hard for raises at my next job before I was in line with people straight out of school, because they didn't want to double my salary at my previous company."

In general, when interviewing for a job you shouldn't share your previous salary, or your specific salary demands-except of course when it is helpful to do so. For example, let's say you're moving from Google to a tiny bootstrapped startup, and you know you won't be able to get the same level of salary. Sharing your current salary can help push your offer higher, or used as leverage to get shorter hours: "I know you can't offer me my previous salary of $$$, but here's something you could do-". Just make sure not to share it too early, or they might decide you'd never accept any offer at all and stop the interview process too early.

Most of the time, however, you shouldn't share either your previous salary or specific salary requirements. If the company insists on getting your previous salary, you can:

Applying the principle: I shouldn't have told ITA Software my salary requirement. Instead, I should have gotten them to make the first offer, which would have given me more information about what they were willing to pay.

Principle #3: Negotiate from a position of strength

The stronger your negotiation position, the more likely you are to get what you want. And this is especially important when you're asking for something abnormal, like a 3-day weekend.

Have a good fallback (BATNA)

If negotiation fails, what will you do? Whatever it is, that is your fallback, sometimes known as the "Best Alternative to a Negotiated Agreement" (BATNA). The better your fallback, the better your alternative, the stronger your negotiating position is. Always figure out your fallback in advance, before you start negotiating.

For example, imagine you're applying for a new job:

If you have a strong fallback, you can choose to walk away at any time, and this will make asking for more much easier.

Provide and demonstrate value

The more an organization wants you as an employee, the more they'll be willing to offer you. The people you're negotiating with don't necessarily know your value: you need to make sure they understand why you're worth what you're asking.

For example, when you're interviewing for a job, you need to use at least part of the interview to explain your value to your prospective employer: your accomplishments and skills. Once you've established the value of your skills, asking for more-more money, unusual terms-can actually make you seem more valuable. And having another job offer-or an existing job-can also help, by showing you are in demand.

Finally, remember that your goal is to make sure the other side's needs are met-not at your own expense, but if they don't think hiring you is worth it, you aren't going to get anything. Here's how Alex, another programmer I talked to, explains how he learned this:

Alex: "Think about the other person and how they're going to react, how you can try to manage that proactively. You need to treat your negotiating partner as a person, not a program.

Initially I had been approaching it adversarially, 'I need to extract value from you, I have to wrestle you for it' but it's more productive to negotiate with an attitude of 'we both need to get our needs met.' The person you're talking to is looking to hire someone productive who can create value, so figure out how can you couch what you want in a way that proactively addresses the other person's concerns."

Principle #4: Use the right tactics

Once you've realized you're negotiating, have done your research, and are negotiating from a position of strength, applying the right negotiation tactics will increase your chances of success even more.

Ask for more than you want

Obviously you don't want to ask for less than what you want. But why not ask for exactly what you want?

First, it might turn out that the company is willing to give you far more than you expected or thought possible.

Second, if you ask for exactly what you want there's no way for you to compromise without getting less than what you want. By asking for more, you can compromise while still getting what you wanted.

Applying the principle: If I'd wanted a $72,000 salary, and research suggested that was a fair salary, I should have asked for $80,000. If I was lucky the company would have said yes; if they wanted to negotiate me down, I would have no problems agreeing to a lower number so long as it was above $72,000.

Negotiate multiple things at once

Your goal when negotiating is not to "win." Rather, your goal is to reach an agreement that passes your minimal bar, and gets you as much as is feasible. Feasibility means you also need to take into account what the other side wants as well. If you've reached an impasse, and you still think you can make a deal that you like, try to come up with creative ways to work out a solution that they will like.

If you only negotiate one thing at once, every negotiation has a winner and a loser. For example, if all you're negotiating is salary, either you're making more money, or the company is saving money: it's a zero-sum negotiation. This limits your ability to come up with a solution that maximizes value for you while still meeting the other side's needs.

Applying the principle: In my story above, the financial company wanted intellectual property protection, I wanted to be able to write open source, and we were at an impasse. So they expanded the scope of the negotiation to include my salary, which allowed them to make tradeoffs between the two-more money for me in return for what they wanted. If I'd cared less about working on open source I might have accepted that offer.

Never give an answer immediately

During the actual negotiation you should never decide on the spot, nor are you required to. If you get a job offer you can explain that you need a little time to think about it: say something like "I have to run this by my spouse/significant other/resident expert." This will give you the time to consider your options in a calmer state of mind, and not just blurt out "yes" at the first semi-decent offer.

Having someone else review the offer is a good idea in general; a friend of mine ran her job offers by her sister, who had an MBA. But it's also useful to mention that other person as someone who has to sign off on the offer. That gives you the ability to say you'd like to accept an offer, but your spouse/expert thinks you can do better.

Notice that the employer almost always has this benefit already. Unless you're negotiating with the owner of the business, you're negotiating with an agent: someone in HR, say. When you make a demand, the HR person might say "I have go to check with the hiring manager", and when they come back with less than you wanted it's not their fault, they're just passing on the bad news. The implication is that the low offer is just the way it is, and there's nothing they can do about.

Don't fall for this trick: they often can change the offer.

Beyond negotiating for salary

You can negotiate for a higher salary-or rather, you should negotiate for a higher salary. The Adam I interviewed in this article is now a partner in DangoorMendel, who can help you negotiate a higher salary.

But salary isn't the only thing you can negotiate for. You can also negotiate for a shorter workweek.

And yes, this is harder, but it's definitely possible.

In fact, this article is an excerpt from a book I wrote to help you do just that: You Can Negotiate a 3-Day Weekend.



Struggling with a 40-hour workweek? Too tired by the end of the day to do anything but collapse on the sofa and watch TV?

Learn how you can get a 3-day weekend, every single week.

27 Nov 2019 5:00am GMT

18 Nov 2019

feedPlanet Twisted

Moshe Zadka: Raise Better Exceptions in Python

There is a lot of Python code in the wild which does something like:

raise ValueError("Could not fraz the buzz:"
                 f"{foo} is less than {quux}")

This is, in general, a bad idea. It does not matter if the exception is fairly generic, like ValueError or specific like CustomFormatParsingException.

Exceptions are not program panics. Program panics are things which should "never happen", and usually abort either the entire program, or at least an execution thread.

While exceptions sometimes do terminate the program, or the execution thread, with a traceback, they are different in that they can be caught.

The code that catches the exception will sometimes have a way to recover: for example, maybe it's not that important for the application to fraz the buzz if foo is 0. In that case, the code would look like:

try:
    some_function()
except ValueError as exc:
    if ???

Oh, right. We do not have direct access to foo. If we formatted better, using repr, at least we could tell the difference between 0 and "0": but we still would have to start parsing the representation out of the string.

Because of this, in general, it is better to raise exceptions like this:

raise ValueError("Could not fraz the buzz: foo is too small", foo, quux)

Note that all the exceptions defined in core Python already allow any number of arguments. Those arguments are available as exc.args, if exc is the exception object. If you do end up defining your custom exceptions, the easiest thing is to avoid overriding the __init__: this keeps this behavior.

Raising exceptions this way gives exception handling a lot of power: it can introspect foo, introspect quux and introspect the string. If by some reason the exception class is raised and we want to verify the reason, checking string equality, while not ideal, is still better than trying to match string parts or regular expression matching.

When the exception is presented to the user interface, in that case, it will not look as nice. Exceptions, in general, should reach the UI only in extreme circumstances. In those cases, having something that has as much information is useful for root cause analysis.

This is an update of an older blog post. Thanks to Mark Rice and Ben Nuttall for their improvement suggestions. All mistakes that are left are my responsibility.

18 Nov 2019 6:00am GMT

11 Nov 2019

feedPlanet Twisted

Twisted Matrix Laboratories: Twisted 19.10.0 Released

On behalf of Twisted Matrix Laboratories, I am honoured to announce the release of Twisted 19.10! The highlights of this release are:

You can find the downloads at <https://pypi.python.org/pypi/Twisted> (or alternatively <http://twistedmatrix.com/trac/wiki/Downloads>). The NEWS file is also available at <https://github.com/twisted/twisted/blob/twisted-19.10.0/NEWS.rst>.

Many thanks to everyone who had a part in this release - the supporters of the Twisted Software Foundation, the developers who contributed code as well as documentation, and all the people building great things with Twisted!

- hawkowl

11 Nov 2019 4:34am GMT

06 Nov 2019

feedPlanet Twisted

Hynek Schlawack: Python Application Dependency Management in 2018

We have more ways to manage dependencies in Python applications than ever. But how do they fare in production? Unfortunately this topic turned out to be quite polarizing and was at the center of a lot of heated debates. This is my attempt at an opinionated review through a DevOps lens.

06 Nov 2019 12:00am GMT

18 Oct 2019

feedPlanet Twisted

Moshe Zadka: An introduction to zope.interface

This has previously been published on opensource.com.

The Zen of Python is loose enough and contradicts itself enough that you can prove anything from it. Let's meditate upon one of its most famous principles: "Explicit is better than implicit."

One thing that traditionally has been implicit in Python is the expected interface. Functions have been documented to expect a "file-like object" or a "sequence." But what is a file-like object? Does it support .writelines? What about .seek? What is a "sequence"? Does it support step-slicing, such as a[1:10:2]?

Originally, Python's answer was the so-called "duck-typing," taken from the phrase "if it walks like a duck and quacks like a duck, it's probably a duck." In other words, "try it and see," which is possibly the most implicit you could possibly get.

In order to make those things explicit, you need a way to express expected interfaces. One of the first big systems written in Python was the Zope web framework, and it needed those things desperately to make it obvious what rendering code, for example, expected from a "user-like object."

Enter zope.interface, which was part of Zope but published as a separate Python package. The zope.interface package helps declare what interfaces exist, which objects provide them, and how to query for that information.

Imagine writing a simple 2D game that needs various things to support a "sprite" interface; e.g., indicate a bounding box, but also indicate when the object intersects with a box. Unlike some other languages, in Python, attribute access as part of the public interface is a common practice, instead of implementing getters and setters. The bounding box should be an attribute, not a method.

A method that renders the list of sprites might look like:

def render_sprites(render_surface, sprites):
    """
    sprites should be a list of objects complying with the Sprite interface:
    * An attribute "bounding_box", containing the bounding box.
    * A method called "intersects", that accepts a box and returns
      True or False
    """
    pass # some code that would actually render

The game will have many functions that deal with sprites. In each of them, you would have to specify the expected contract in a docstring.

Additionally, some functions might expect a more sophisticated sprite object, maybe one that has a Z-order. We would have to keep track of which methods expect a Sprite object, and which expect a SpriteWithZ object.

Wouldn't it be nice to be able to make what a sprite is explicit and obvious so that methods could declare "I need a sprite" and have that interface strictly defined? Enter zope.interface.

from zope import interface

class ISprite(interface.Interface):

    bounding_box = interface.Attribute(
        "The bounding box"
    )

    def intersects(box):
        "Does this intersect with a box"

This code looks a bit strange at first glance. The methods do not include a self, which is a common practice, and it has an Attribute thing. This is the way to declare interfaces in zope.interface. It looks strange because most people are not used to strictly declaring interfaces.

The reason for this practice is that the interface shows how the method will be called, not how it is defined. Because interfaces are not superclasses, they can be used to declare data attributes.

One possible implementation of the interface can be with a circular sprite:

@implementer(ISprite)
@attr.s(auto_attribs=True)
class CircleSprite:
    x: float
    y: float
    radius: float

    @property
    def bounding_box(self):
        return (
            self.x - self.radius,
            self.y - self.radius,
            self.x + self.radius,
            self.y + self.radius,
        )

    def intersects(self, box):
        # A box intersects a circle if and only if
        # at least one corner is inside the circle.
        top_left, bottom_right = box[:2], box[2:]
        for choose_x_from (top_left, bottom_right):
            for choose_y_from (top_left, bottom_right):
                x = choose_x_from[0]
                y = choose_y_from[1]
                if (((x - self.x) ** 2 + (y - self.y) ** 2) <=
                    self.radius ** 2):
                     return True
        return False

This explicitly declares that the CircleSprite class implements the interface. It even enables us to verify that the class implements it properly:

from zope.interface import verify

def test_implementation():
    sprite = CircleSprite(x=0, y=0, radius=1)
    verify.verifyObject(ISprite, sprite)

This is something that can be run by pytest, nose, or another test runner, and it will verify that the sprite created complies with the interface. The test is often partial: it will not test anything only mentioned in the documentation, and it will not even test that the methods can be called without exceptions! However, it does check that the right methods and attributes exist. This is a nice addition to the unit test suite and -- at a minimum -- prevents simple misspellings from passing the tests.

If you have some implicit interfaces in your code, why not document them clearly with zope.interface?

18 Oct 2019 3:00am GMT

16 Oct 2019

feedPlanet Twisted

Hynek Schlawack: Sharing Your Labor of Love: PyPI Quick and Dirty

A completely incomplete guide to packaging a Python module and sharing it with the world on PyPI.

16 Oct 2019 12:00am GMT

13 Oct 2019

feedPlanet Twisted

Glyph Lefkowitz: Mac Python Distribution Post Updated for Catalina and Notarization

I previously wrote a post about shipping a PyGame app to users on macOS. It's now substantially updated for the new Notarization requirements in Catalina. I hope it's useful to somebody!

13 Oct 2019 9:10pm GMT

07 Oct 2019

feedPlanet Twisted

Glyph Lefkowitz: The Numbers, They Lie

It's October, and we're all getting ready for Halloween, so allow me to me tell you a horror story, in Python:

1
2
>>> 0.1 + 0.2 - 0.3
5.551115123125783e-17

some scary branches

Some of you might already be familiar with this chilling tale, but for those who might not have experienced it directly, let me briefly recap.

In Python, the default representation of a number with a decimal point in it is something called an "IEEE 754 double precision binary floating-point number". This standard achieves a generally useful trade-off between performance, correctness, and is widely implemented in hardware, making it a popular choice for numbers in many programming language.

However, as our spooky story above indicates, it's not perfect. 0.1 + 0.2 is very slightly less than 0.3 in this representation, because it is a floating-point representation in base 2.

If you've worked professionally with software that manipulates money1, you typically learn this lesson early; it's quite easy to smash head-first into the problem with binary floating-point the first time you have an item that costs 30 cents and for some reason three dimes doesn't suffice to cover it.

There are a few different approaches to the problem; one is using integers for everything, and denominating your transactions in cents rather than dollars. A strategy which requires less weird unit-conversion2, is to use the built-in decimal module, which provides a floating-point base 10 representation, rather than the standard base-2, which doesn't have any of these weird glitches surrounding numbers like 0.1.

This is often where a working programmer's numerical education ends; don't use floats, they're bad, use decimals, they're good. Indeed, this advice will work well up to a pretty high degree of application complexity. But the story doesn't end there. Once division gets involved, things can still get weird really fast:

1
2
3
>>> from decimal import Decimal
>>> (Decimal("1") / 7) * 14
Decimal('2.000000000000000000000000001')

The problem is the same: before, we were working with 1/10, a value that doesn't have a finite (non-repeating) representation in base 2; now we're working with 1/7, which has the same problem in base 10.

Any time you have a representation of a number which uses digits and a decimal point, no matter the base, you're going to run in to some rational values which do not have an exact representation with a finite number of digits; thus, you'll drop some digits off the (necessarily finite) end, and end up with a slightly inaccurate representation.

But Python does have a way to maintain symbolic accuracy for arbitrary rational numbers -- the fractions module!

1
2
3
4
5
>>> from fractions import Fraction
>>> Fraction(1)/3 + Fraction(2)/3 == 1
True
>>> (Fraction(1)/7) * 14 == 2
True

You can multiply and divide and add and subtract to your heart's content, and still compare against zero and it'll always work exactly, giving you the right answers.

So if Python has a "correct" representation, which doesn't screw up our results under a basic arithmetic operation such as division, why isn't it the default? We don't care all that much about performance, right? Python certainly trades off correctness and safety in plenty of other areas.

First of all, while Python's willing to trade off some storage or CPU efficiency for correctness, precise fractions rapidly consume huge amounts of storage even under very basic algorithms, like consuming gigabytes while just trying to maintain a simple running average over a stream of incoming numbers.

But even more importantly, you'll notice that I said we could maintain symbolic accuracy for arbitrary rational numbers; but, as it turns out, a whole lot of interesting math you might want to do with a computer involves numbers which are irrational: like π. If you want to use a computer to do it, pretty much all trigonometry3 involves a slightly inaccurate approximation unless you have a literally infinite amount of storage.

As Morpheus put it, "welcome to the desert of the ".


  1. or any proxy for it, like video-game virtual currency

  2. and less time saying weird words like "nanodollars" to your co-workers

  3. or, for that matter, geometry, or anything involving a square root

07 Oct 2019 6:25am GMT

05 Oct 2019

feedPlanet Twisted

Glyph Lefkowitz: A Few Bad Apples

I'm a little annoyed at my Apple devices right now.

Time to complain.

"Trust us!" says Apple.

"We're not like the big, bad Google! We don't just want to advertise to you all the time! We're not like Amazon, just trying to sell you stuff! We care about your experience. Magical. Revolutionary. Courageous!"

But I can't hear them over the sound of my freshly-updated Apple TV - the appliance which exists solely to play Daniel Tiger for our toddler - playing the John Wick 3 trailer at full volume automatically as soon as it turns on.

For the aforementioned toddler.

I should mention that it is playing this trailer while specifically logged in to a profile that knows their birth date1 and also their play history2.


I'm aware of the preferences which control autoplay on the home screen; it's disabled now. I'm aware that I can put an app other than "TV" in the default spot, so that I can see ads for other stuff, instead of the stuff "TV" shows me ads for.

But the whole point of all this video-on-demand junk was supposed to be that I can watch what I want, when I want - and buying stuff on the iTunes store included the implicit promise of no advertisements.

At least Google lets me search the web without any full-screen magazine-style ads popping up.

Launch the app store to check for new versions?

apple arcade ad

I can't install my software updates without accidentally seeing HUGE ads for new apps.

Launch iTunes to play my own music?

apple music ad

I can't play my own, purchased music without accidentally seeing ads for other music - and also Apple's increasingly thirsty, desperate plea for me to remember that they have a streaming service now. I don't want it! I know where Spotify is if I wanted such a thing, the whole reason I'm launching iTunes is that I want to buy and own the music!

On my iPhone, I can't even launch the Settings app to turn off my WiFi without seeing an ad for AppleCare+, right there at the top of the UI, above everything but my iCloud account. I already have AppleCare+; I bought it with the phone! Worse, at some point the ad glitched itself out, and now it's blank, and when I tap the blank spot where the ad used to be, it just shows me this:

undefined is not an insurance plan

I just want to use my device, I don't need ad detritus littering every blank pixel of screen real estate.

Knock it off, Apple.


  1. less than 3 years ago

  2. Daniel Tiger, Doctor McStuffins, Word World; none of which have super significant audience overlap with the John Wick franchise

05 Oct 2019 6:32pm GMT

24 Sep 2019

feedPlanet Twisted

Jp Calderone: Tahoe-LAFS on Python 3 - Call for Porters

Hello Pythonistas,

Earlier this year a number of Tahoe-LAFS community members began an effort to port Tahoe-LAFS from Python 2 to Python 3. Around five people are currently involved in a part-time capacity. We wish to accelerate the effort to ensure a Python 3-compatible release of Tahoe-LAFS can be made before the end of upstream support for CPython 2.x.

Tahoe-LAFS is a Free and Open system for private, secure, decentralized storage. It encrypts and distributes your data across multiple servers. If some of the servers fail or are taken over by an attacker, the entire file store continues to function correctly, preserving your privacy and security.

Foolscap, a dependency of Tahoe-LAFS, is also being ported. Foolscap is an object-capability-based RPC protocol with flexible serialization.

Some details of the porting effort are available in a milestone on the Tahoe-LAFS trac instance.

For this help, we are hoping to find a person/people with significant prior Python 3 porting experience and, preferably, some familiarity with Twisted, though in general the Tahoe-LAFS project welcomes contributors of all backgrounds and skill levels.

We would prefer someone to start with us as soon as possible and no later than October 15th. If you are interested in this opportunity, please send us any questions you have, as well as details of your availability and any related work you have done previously (GitHub, LinkedIn links, etc). If you would like to find out more about this opportunity, please contact us at jessielisbetfrance at gmail (dot) com or on IRC in #tahoe-lafs on Freenode.


24 Sep 2019 4:59pm GMT

17 Sep 2019

feedPlanet Twisted

Moshe Zadka: Adding Methods Retroactively

The following post was originally published on OpenSource.com as part of a series on seven libraries that help solve common problems.

Imagine you have a "shapes" library. We have a Circle class, a Square class, etc.

A Circle has a radius, a Square has a side, and maybe Rectangle has height and width. The library already exists: we do not want to change it.

However, we do want to add an area calculation. If this was our library, we would just add an area method, so that we can call shape.area(), and not worry about what the shape is.

While it is possible to reach into a class and add a method, this is a bad idea: nobody expects their class to grow new methods, and things might break in weird ways.

Instead, the singledispatch function in functools can come to our rescue:

@singledispatch
def get_area(shape):
    raise NotImplementedError("cannot calculate area for unknown shape",
                              shape)

The "base" implementation for the get_area function just fails. This makes sure that if we get a new shape, we will cleanly fail instead of returning a nonsense result.

@get_area.register(Square)
def _get_area_square(shape):
    return shape.side ** 2
@get_area.register(Circle)
def _get_area_circle(shape):
    return math.pi * (shape.radius ** 2)

One nice thing about doing things this way is that if someone else writes a new shape that is intended to play well with our code, they can implement the get_area themselves:

from area_calculator import get_area

@attr.s(auto_attribs=True, frozen=True)
class Ellipse:
    horizontal_axis: float
    vertical_axis: float

@get_area.register(Ellipse)
def _get_area_ellipse(shape):
    return math.pi * shape.horizontal_axis * shape.vertical_axis

Calling get_area is straightforward:

print(get_area(shape))

This means we can change a function that has a long if isintance()/elif isinstance() chain to work this way, without changing the interface. The next time you are tempted to check if isinstance, try using singledispatch!

17 Sep 2019 1:00am GMT

10 Sep 2019

feedPlanet Twisted

Itamar Turner-Trauring: What can a software developer do about climate change?

Pines and firs are dying across the Pacific Northwest, fires rage across the Amazon, it's the hottest it's ever been in Paris-climate change is impacting the whole planet, and things are not getting any better. You want to do something about climate change, but you're not sure what.

If you do some research you might encounter an essay by Bret Victor-What can a technologist do about climate change? There's a whole pile of good ideas in there, and it's worth reading, but the short version is that you can use technology to "create options for policy-makers."

Thing is, policy-makers aren't doing very much.

So this essay isn't about technology, because technology isn't the bottleneck right now, it's about policy and politics what you can do about it. It's still written for software developers, because that's who I write for, but also because software developers often have access to two critical catalysts for political change. And it's written for software developers in the US, because that's where I live, and because the US is a big part of the problem.

But before I go into what you can do, let me tell you the story of a small success I happened to be involved in, a small step towards a better future.

Infrastructure and the status quo

About a year ago I spent some of my mornings handing out pamphlets to bicycle riders. I looked like an idiot: in order to show I was one of them I wore my bike helmet, which is weirdly shaped and the color of fluorescent yellow snot.

After finding an intersection with plenty of bicycle riders and a long red light that forces them to stop, I would do the following:

  1. When the light turns red, step into the street and hand out the pamphlet.
  2. Keep an eye out for the light changing to green so that I didn't get run over by moving cars.
  3. Twiddle my thumbs waiting for the next light cycle.

It was boring, and not very glamorous.

I was one of just many volunteers, and besides gathering signatures we also held rallies, had conversations with city councilors and staff, wrote emails, talked at city council meetings-it was a process. The total effort took a couple of years (and I only joined in towards the end)-but in the end we succeeded.

We succeeded in having the council pass a short ordinance, a city-level law in the city of Cambridge, Massachusetts. The ordinance states that whenever a road that was supposed to have protected bike lanes (per the city's Bike Plan) was rebuilt from scratch, it would have those lanes built by default.

Now, clearly this ordinance isn't going to solve climate change. In fact, nothing Cambridge does as a city will solve climate change, because there's only so much impact 100,000 people can have on greenhouse gas emissions.

But while in some ways this ordinance was a tiny victory in a massive war, if we take a step back it's actually more important than it seems. In particular, this ordinance has three effects:

  1. Locally, safer bike infrastructure means more bicycle riders, and fewer car drivers. That reduces emissions-a little.
  2. Over time, more bicycle riders can kick off a positive feedback cycle, reducing emissions even more.
  3. Most significantly, local initiatives spread to other cities-kicking off these three effects in those other cities.

Let's examine these effects one by one.

Effect #1: Fewer cars, less emissions

About 43% of the greenhouse gas emissions in Massachusetts are due to transportation; for the US overall it's 29% (ref). And that means cars.

The reason people in the US mostly drive cars is because all the transportation infrastructure is built for cars. No bike lanes, infrequent, slow and non-existent buses, no trains… Even in cities, where other means of transportation are feasible, the whole built infrastructure sends the very strong message that cars are the only reasonable way to get around.

If we focus on bicycles, our example at hand, the problem is that riding a bicycle can be dangerous-mostly because of all those cars! But if you get rid of the danger and build good infrastructure-dedicated protected bike lanes that separate bicycle riders from those dangerous cars-then bicycle use goes up.

Consider what Copenhagen achieved between 2008 and 2017 (ref):

2008 2018
# of seriously injured cyclists 121 81
% who residents who feel secure cycling 51 77
% who cycle to work/school 37 49

With safer infrastructure for bicycles, perception of safety goes up, and people bike more and drive less. Similarly, if you have frequent, fast, and reliable buses and trains, people drive less. And that means less carbon emissions.

In Copenhagen the number of kilometers driven by cars was flat or slightly down over those 10 years-whereas in the US, it's up 6-7% (ref).

Effect #2: A positive feedback loop

The changes in Copenhagen are a result of a plan the city government there adopted in 2011 (ref): they're the result of a policy action. And the political will was there in part because there were already a huge number of bicycle riders. So it's a positive feedback loop, and a good one.

Let's see how this is happening in Cambridge:

If Copenhagen can reach 50% of residents with a bicycle commute, so can Cambridge-and the ordinance is a good step in that direction.

Effect #3: The idea spreads

The Cambridge ordinance passed in April 2019-and the idea is spreading elsewhere:

All of this is the result of local advocacy-but I've no doubt Cambridge's example helped. It's always easier to be the second adopter. And the examples from these larger localities will no doubt inspire other groups and cities, spreading the idea even more.

Change requires politics

Bike infrastructure is just an example, not a solution-but there are three takeaways from this story that I'd like to emphasize:

By politics I don't just mean having an opinion or voting for a candidate, but rather engaging in the process of how policy decisions are made.

Merely having an opinion doesn't change anything. For example, two-thirds of Cambridge residents support building more protected bike lanes (ref). But that doesn't mean that many protected lanes are getting built-the neighboring much smaller city of Somerville is building far more than Cambridge.

The only reason the city polled residents about bike lanes is because, one suspects, all the fuss we'd been making-emails, rallies, meetings, city council policy orders-made the city staff wonder if bike infrastructure really had a lot of public support or not.

Voting results in some change, but not enough. Elected officials and government staff have lots and lots of things to worry about-if they're not being pressured to focus on a particular issue, it's likely to fall behind.

What's more, the candidates you get to vote for have to get on the ballot, and to do that they need money (for advertising, hiring staff, buying supplies). Lacking money, they need volunteer time.

And it's much easier for a small group of rich people to provide that support to the candidates they want-so by the time you're voting, you only get to choose between candidates that have been pre-vetted (I highly recommend reading The Golden Rule to understand how this works on a national level).

What you can do: Become an activist

In the end power is social. Power comes from people showing up to meetings, people showing up for rallies, people going door-to-door convincing other people to vote for the right person or support the right initiative, people blocking roads and making a fuss.

And that takes time and money.

So if you want to change policy, you need to engage in politics, with time and money:

Here are some policies you might be interested in:

Where you should do it: Start local

If you are going to become an activist, the local level is a good starting point.

Of course, local organizing is just the starting point for creating change on the global level. But you have to start somewhere. And global change is a lot easier if you have thousands of local organizations supporting it.

It's a good to be a software developer

Let's get back to our starting point-you're paid to write software, you want to do something about climate change. As a software developer you likely have access to the inputs needed to make political campaigns succeed-both candidate-based and issue-based:

If you don't have children or other responsibilities, you can work a 40-hour workweek, leaving you time for other things. Before I got married I worked full-time and went to a local adult education college half-time in the evenings: it was a lot of work, but it was totally doable. Set boundaries at your job, and you'll have at least some free time for activism.

You can also negotiate a shorter workweek, which is possible in part because software developers are in such demand. I've done this, I've interviewed people who have done it, I've found many random people on the Internet who have done it-it is possible.

If you need help doing it yourself, I've written a book to help you negotiate a shorter workweek. If you want to negotiate a shorter workweek so you have time for political activism, you can use the code FIGHTCLIMATECHANGE to get the book for 60% off.

Some common responses

"There will never be the political will to make this happen"

Things do change, for better and for worse, and sometimes unexpectedly. To give a couple of examples:

The timelines for gay marriage and cannabis legalization in the US are illuminating: these things didn't just happen, it was the result of long, sustained activist efforts, much of it at the local level.

Local changes do make a difference.

"Politics is awful and broken"

So are all our software tools, and somehow we manage to get things done!

"I don't like your policy suggestions, we should do X instead"

No problem, find the local groups that promote your favorite policies and join them.

"The necessary policies will never work because of problem Y"

Same answer: join and help the local groups working on Y.

"It's too late, the planet is doomed no matter what we do"

Perhaps, but it's very hard to say. So we're in Pascal's Wager territory here: given even a tiny chance there is something we can do, we had better do our best to make it happen.

And even if humanity really is doomed, there's always the hope that someday a hyperintelligent species of cockroach will inherit the Earth. And when cockroach archaeologists try to reconstruct our history, I would like them to be able to say, loosely translated from their complex pheromone-and-dancing system of communication: "These meatsacks may not have been as good at surviving as us cockroaches-but at least they tried!"

Time to get started

If you find this argument compelling-that policy is driven by power, and that power requires social mobilization-then it's up to you to take the next step. Find a local group or candidate pushing for a policy you care about, and show up for the next meeting.

And the meeting after that.

And then go to the rally.

And knock on doors.

And make some friends, and make some changes happen.

Some of the work is fun, some of it is boring, but there's plenty to do-time to get started!



Struggling with a 40-hour workweek? Too tired by the end of the day to do anything but collapse on the sofa and watch TV?

Learn how you can get a 3-day weekend, every single week.

10 Sep 2019 4:00am GMT

09 Sep 2019

feedPlanet Twisted

Ralph Meijer: XMPP Message Attaching, Fastening, References

Services like Twitter and Slack have functionality that attempts to interpret parts of the plain text of tweets or message as entered by the user. Pieces of the text that look like links, mentions of another user, hash tags, or stock symbols, cause additional meta data to be added to the object representing the message, so that receiving clients can mark up those pieces of text in a special way. Twitter calls this meta data Tweet Entities and for each piece of interpreted text, it includes indices for the start and end of along with additional information depending on the type of entity. A client can then do in-line replacements at the exact character indices, e.g. by making it into a hyperlink. Twitter Entities served as inspiration for XEP-0372: References.

References can be used in two ways: including a reference as a sibling to the body element of a message. The begin and end attributes then point to the indices of the plain text in the body. This would typically be used if the interpretation of the message is done by the sending client.

Alternatively, a service (e.g. a MUC service) could parse incoming messages and send a separate stanza to mark up the original stanza. In this case you need a mechanism for pointing to that other message. There have been two proposals for this, with slightly differing approaches, and in the examples below, I'll use the proto-XEP Message Fastening. While pointing to the stanza ID of the other message, it embeds a reference element in the apply-to element.

Mentioning another user

Let's start out with the example of mentioning another user.

<message from="room@muc.this.example/Kev" type="groupchat">
  <stanza-id id="2019-09-02-1" by="room@muc.this.example"
             xmlns="urn:xmpp:sid:0"/>
  <body>Some rubbish @ralphm</body>
</message>

A client might render this as:

Kev 13:04

Some rubbish @ralphm

The MUC service then parses the plain-text message, and finds a reference to my nickname prefixed with an @-sign, and sends a stanza to the room that marks up the message Kev sent to me.

<message from="room@muc.this.example"
         type="groupchat">
  <stanza-id xmlns="urn:xmpp:sid:0"
             id="2019-09-02-2" by="room@muc.this.example"/>
  <apply-to xmlns="urn:xmpp:fasten:0"
            id="2019-09-02-1">
    <reference begin="13" end="19" xmlns="urn:example:reference:0">
      <mention jid="room@muc.this.example/ralphm"/>
    </reference>
  </apply-to>
</message>

This stanza declares that it is attached to the previous message by the stanza ID that was included with the original stanza. In its payload, it includes a reference, referring to the characters 13 through 19. It has a mention child pointing to my occupant JID. Alternatively, the room might have linked to my real JID. A client can then alter the presentation of the original message to use the attached mention reference:

Kev 13:04

Some rubbish @ralphm

The characters referencing @ralphm are now highlighted, hovering the mention shows a tooltip with my full name, and clicking on it brings you to a page describing me. This information was not present in the stanza, but a client can use the XMPP URI as a key to present additional information. E.g. from the user's contact list, by doing a vCard lookup, etc.


Note:

The current specification for References does not have defined child elements, but instead uses a type attribute and URIs. However, Jonas Wielicki Schäfer provided some valuable feedback, suggesting this idea. By using a dedicated element for the target of the reference, each can have their own attributes, making it more explicit. Also, it is a natural extension point, by including a differently namespaced element instead.


Referring to previous messages

<message from="room@muc.this.example/Ge0rG"
         type="groupchat">
  <stanza-id xmlns="urn:xmpp:sid:0"
             id="2019-09-02-3" by="room@muc.this.example"/>
  <reference begin="0" end="6" xmlns="urn:example:reference:0">
    <mention jid="room@muc.this.example/ralphm"/>
  </reference>
  <reference begin="26" end="32" xmlns="urn:example:reference:0">
    <message id="2019-09-02-1"/>
  </reference>
  <body>@ralphm did you see Kev's message earlier?</body>
</message>

Unlike before, this example does not point to another stanza with apply-to. Instead, Ge0rG's client added references to go along with the plain-text body: one for the mention of me, and one for a reference to an earlier message.

Ge0rG 13:16

@ralphm did you see Kev's message earlier?

Emoji Reactions

Instead of reacting with a full message, Slack, like online forum software much earlier, has the ability to attach emoji reactions to messages.

<message from="room@muc.this.example/Kev"
         type="groupchat">
  <stanza-id xmlns="urn:xmpp:sid:0"
            id="2019-09-02-4" by="room@muc.this.example"/>
  <apply-to xmlns="urn:xmpp:fasten:0"
            id="2019-09-02-3">
    <reactions xmlns="urn:example:reactions:0">
      <reaction label=":+1:">👍</reaction>
    </reactions>
  </apply-to>
</message>
<message from="room@muc.this.example/ralphm"
         type="groupchat">
  <stanza-id xmlns="urn:xmpp:sid:0"
             id="2019-09-02-6" by="room@muc.this.example"/>
  <apply-to xmlns="urn:xmpp:fasten:0"
            id="2019-09-02-3">
    <reactions xmlns="urn:example:reactions:0">
      <reaction label=":parrot:"
                img="cid:b729aec3f521694a35c3fc94d7477b32bc6444ca@bob.xmpp.org"/>
    </reactions>
  </apply-to>
</message>

These two examples show two separate instances of a person reacting to the previous message by Ge0rG. It uses the protocol from Message Reactions, another Proto-XEP. However, I expanded on it by introducing two new attributes. The label allows for a textual shorthand, that might be typed by a user. Custom emoji can be represented with the img attribute, that points to a XEP-0231: Bits of Binary object.

Ge0rG 13:16

@ralphm did you see Kev's message earlier?

👍 2 1

The attached emoji are rendered below the original message, and hovering over them reveals who were the respondents. Here my own reaction is highlighted by a squircle border.

Including a link

<message from="room@muc.this.example/ralphm" type="groupchat">
  <stanza-id id="2019-09-02-7" by="room@muc.this.example"
             xmlns="urn:xmpp:sid:0"/>
  <body>Have you seen https://ralphm.net/blog/2013/10/10/logitech_t630?</body>
</message>
<message from="room@muc.this.example"
         type="groupchat">
  <stanza-id xmlns="urn:xmpp:sid:0"
             id="2019-09-02-8" by="room@muc.this.example"/>
  <apply-to xmlns="urn:xmpp:fasten:0"
            id="2019-09-02-7">
    <reference begin="14" end="61" xmlns="urn:example:reference:0">
      <link url="https://ralphm.net/blog/2013/10/10/logitech_t630"/>
    </reference>
  </apply-to>
</message>

Here the MUC service marks up the original messages with an explicit link reference. Possibly, the protocol might be extended so that a service can include shortened versions of the URL for display purposes.

ralphm 13:16

Have you seen https://ralphm.net/blog/2013/10/10/logitech_t630?

Logitech Ultrathin Touch Mouse

Logitech input devices are my favorite. This tiny bluetooth mouse is a nice portable device for every day use or while traveling.

The client has used the markup to fetch meta data on the URL and presents a summary card below the original message. Alternatively, the MUC service could have done this using XEP-0385: Stateless Inline Media Sharing (SIMS):

<message from="room@muc.this.example"
         type="groupchat">
  <stanza-id xmlns="urn:xmpp:sid:0"
             id="2019-09-02-8" by="room@muc.this.example"/>
  <apply-to xmlns="urn:xmpp:fasten:0"
            id="2019-09-02-7">
    <reference begin="14" end="61" xmlns="urn:example:reference:0">
      <link url="https://ralphm.net/blog/2013/10/10/logitech_t630"/>
      <card xmlns="urn:example:card:0">
        <title>Logitech Ultrathin Touch Mouse</ulink></title>
        <description>Logitech input devices are my favorite. This tiny bluetooth mouse is a nice portable device for every day use or while traveling.</description>
      </card>
      <media-sharing xmlns='urn:xmpp:sims:1'>
        <file xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
          <media-type>image/jpeg</media-type>
          <name>ultrathin-touch-mouse-t630.jpg</name>
          <size>23458</size>
          <hash xmlns='urn:xmpp:hashes:2' algo='sha3-256'>5TOeoNI9z6rN5f+cQagnCgxitQE0VUgzCMeQ9JqbhWJT/FzPpDTTFCbbo1jWwOsIoo9u0hQk6CPxH4t/dvTN0Q==</hash>
          <thumbnail xmlns='urn:xmpp:thumbs:1'uri='cid:sha1+21ed723481c24efed81f256c8ed11854a8d47eff@bob.xmpp.org' media-type='image/jpeg' width='116' height='128'/>
        </file>
        <sources>
          <reference xmlns='urn:xmpp:reference:0' type='data' uri='https://test.ralphm.net/images/blog/ultrathin-touch-mouse-t630.jpg' />
        </sources>
      </media-sharing>
    </reference>
  </apply-to>
</message>

Editing a previous message

<message from="room@muc.this.example/ralphm" type="groupchat">
  <stanza-id id="2019-09-02-9" by="room@muc.this.example"
             xmlns="urn:xmpp:sid:0"/>
  <body>Some thoughtful reply</body>
</message>
ralphm 13:19

Some thoughtful reply

After sending that message, I want to add a bit more information:

<message from="room@muc.this.example/ralphm" type="groupchat">
  <stanza-id id="2019-09-02-10" by="room@muc.this.example"
             xmlns="urn:xmpp:sid:0"/>
  <apply-to xmlns="urn:xmpp:fasten:0"
            id="2019-09-02-9">
    <external name='body'/>
    <replace xmlns='urn:example:message-correct:1'/>
  </apply-to>
  <body>Some more thoughtful reply</body>
</message>

Unlike XEP-0308: Last Message Correction, this example uses Fastening to refer to the original message. I would also lift the restriction on correcting just the last message, but allow any previous message to be edited.

ralphm 13:19

Some more thoughtful reply

Upon receiving the correction, the client indicates that the message has been edited. Hovering over the marker reveals when the message was changed.

Editing a previous message that had fastened references

<message from="room@muc.this.example/Kev" type="groupchat">
  <stanza-id id="2019-09-02-11" by="room@muc.this.example"
             xmlns="urn:xmpp:sid:0"/>
  <body>A witty response mentioning @ralphm</body>
</message>
<message from="room@muc.this.example"
         type="groupchat">
  <stanza-id xmlns="urn:xmpp:sid:0"
             id="2019-09-02-12" by="room@muc.this.example"/>
  <apply-to xmlns="urn:xmpp:fasten:0"
            id="2019-09-02-11">
    <reference begin="28" end="34" xmlns="urn:example:reference:0">
      <mention jid="room@muc.this.example/ralphm"/>
    </reference>
  </apply-to>
</message>
Kev 13:26

A witty response mentioning @ralphm

After a bit of consideration, Kev edits his response:

<message from="room@muc.this.example/Kev" type="groupchat">
  <stanza-id id="2019-09-02-13" by="room@muc.this.example"
             xmlns="urn:xmpp:sid:0"/>
  <apply-to xmlns="urn:xmpp:fasten:0"
            id="2019-09-02-11">
    <external name='body'/>
    <replace xmlns='urn:example:message-correct:1'/>
  </apply-to>
  <body>A slighty wittier response mentioning @ralphm</body>
</message>
Kev 13:26

A slightly wittier response mentioning @ralphm

Upon receiving the correction, the client discards all fastened references. The body text was changed, so the reference indices are stale. The room can then send a new stanza marking up the new text:

<message from="room@muc.this.example"
         type="groupchat">
  <stanza-id xmlns="urn:xmpp:sid:0"
             id="2019-09-02-14" by="room@muc.this.example"/>
  <apply-to xmlns="urn:xmpp:fasten:0"
            id="2019-09-02-11">
    <reference begin="40" end="46" xmlns="urn:example:reference:0">
      <mention jid="room@muc.this.example/ralphm"/>
    </reference>
  </apply-to>
</message>
Kev 13:26

A slightly wittier response mentioning @ralphm

Closing notes

09 Sep 2019 2:37pm GMT

16 Aug 2019

feedPlanet Twisted

Twisted Matrix Laboratories: Twisted 19.7.0 Released

On behalf of Twisted Matrix Laboratories and our long-suffering release manager Amber Brown, I am honored to announce1 the release of Twisted 19.7.0!


The highlights of this release include:
  • A full description on the PyPI page! Check it out here: https://pypi.org/project/Twisted/19.7.0/ (and compare to the slightly sad previous version, here: https://pypi.org/project/Twisted/19.2.1/)
  • twisted.test.proto_helpers has been renamed to "twisted.internet.testing"
    • This removes the gross special-case carve-out where it was the only "public" API in a test module, and now the rule is that all test modules are private once again.
  • Conch's SSH server now supports hmac-sha2-512.
  • The XMPP server in Twisted Words will now validate certificates!
  • A nasty data-corruption bug in the IOCP reactor was fixed. If you're doing high-volume I/O on Windows you'll want to upgrade!
  • Twisted Web no longer gives clients a traceback by default, both when you instantiate Site and when you use twist web on the command line. You can turn this behavior back on for local development with twist web --display-tracebacks.
  • Several bugfixes and documentation fixes resolving bytes/unicode type confusion in twisted.web.
  • Python 3.4 is no longer supported.
pip install -U twisted[tls] and enjoy all these enhancements today!

Thanks for using Twisted,

-glyph

1: somewhat belatedly: it came out 10 days ago. Oops!

16 Aug 2019 6:38am GMT

08 Aug 2019

feedPlanet Twisted

Moshe Zadka: Designing Interfaces

One of the items of feedback I got from the article about interface immutability is that it did not give any concrete feedback for how to design interfaces. Given that they are forever, it would be good to have some sort of guidance.

The first item is that you want something that uses the implementation, as well as several distinct implementations. However, this item is too obvious: in almost all cases I have seen in the wild of a bad interface, this guideline was followed.

It was also followed in all cases of a good interface.

I think this guideline is covered well enough that by the time anyone designs a real interface, they understand that. Why am I mentioning this guideline at all, then?

Because I think it is important for the context of the guideline that I do think actually distinguishes good interfaces from bad interfaces. It is almost identical to the non-criterion above!

The real guideline is: something that uses the implementation, as well as several distinct implementations that do not share a superclass (other than object or whatever is in the top of the hierarchy).

This simple addition, preventing the implementations from sharing a superclass, is surprisingly powerful. It means each implementation has to implement the "boring" parts by hand. This will immediately cause pressure to avoid "boring" parts, and instead put them in a wrapper, or in the interface user.

Otherwise, the most common failure mode is that the implementations are all basic variants on what is mostly the "big superclass".

In my experience, just the constraint on not having a "helper superclass" puts appropriate pressure on interfaces to be good.

(Thanks to Tom Most for his encouragement to write this, and the feedback on an earlier draft. Any mistakes that remain are my responsibility.)

08 Aug 2019 5:20am GMT

13 Jul 2019

feedPlanet Twisted

Moshe Zadka: Interfaces are forever

(The following talks about zope.interface interfaces, but applies equally well to Java interfaces, Go interfaces, and probably other similar constructs.)

When we write a function, we can sometimes change it in backwards-compatible ways. For example, we can loosen the type of a variable. We can restrict the type of the return value. We can add an optional argument.

We can even have a backwards compatible path to make an argument required. We add an optional argument, and encourage people to change it. Then, in the next version, we make the default value be one that causes a warning. In a version after that, we make the value required. At each point, someone could write a library that worked with at least two consecutive versions.

In a similar way, we can have a path to remove an argument. First make it optional. Then warn when it is passed in. Finally, remove it and make it an error to pass it in.

As long as we do not intend to support inheritance, making backwards compatible changes to classes also works. For example, to remove a method we first have a version that warns when you call it, and then remove it in a succeeding version.

However, what changes can we make to an interface?

Assume we have an interface like:

from zope.interface import Interface, implements

class IFancyFormat(Interface):

    def fancify_int(value: int) -> str:
        pass

It is a perfectly reasonable, if thin, interface. Implementing it seems like fun:

@implements(IFancyFormat)
@attr.s(auto_attribs=True)
class FancySuffixer:
    suffix: str

    def fancify_int(self, value: int) -> str:
        return str(value) + self.suffix

Using it also seems like fun:

def dashify_fancy_five(fancifier: IFancyFormat) -> str:
    return f"---{fancifier.fancify_int(5)}---"

These are very different kinds of fun, though! Probably the kind of fun that appeals to different people. The first implementation is in the superfancy open-source library. The second one is in the dash_five open-source library. Such is the beauty of open source: it takes all kinds of people.

We cannot add a method to IFancyFormat: the superfancy library has a unit test that uses verifyImplements, which will fail if we add a method. We cannot remove the method fancify_int, since this will break dash_five: the mypy check will fail, since IFancifySuffixer will not have that method.

Similarly, we cannot make the parameter optional without breaking superfancy, or loosen the return type without breaking dash_five. Once we have published IFancyFormat as an API, it cannot change.

The only way to recover from a bad interface is to create a new interface, IAwesomeFancyFormat. Then write conversion functions from and to IFancyFormat and IAwesomeFancyFormat. Then deprecate using the IFancyFormat interface. Finally, we can remove the interface. Then we can alias IFancyFormat == IAwesomeFancyFormat, and eventually, maybe even deprecate the name IAwesomeFancyFormat.

When publishing interfaces, one must be careful: to a first approximation, they are forever.

(Thanks to Glyph Lefkowitz for his helpful suggestions. Any mistakes or issues that are left are my responsibility.)

13 Jul 2019 5:00am GMT