Skip to content

Releases: piccolo-orm/piccolo

1.20.0

04 Oct 23:09
Compare
Choose a tag to compare

get_related now works multiple layers deep:

concert = await Concert.objects().first()
manager = await concert.get_related(Concert.band_1._.manager)

1.19.1

01 Oct 08:52
Compare
Choose a tag to compare

Fixed a bug with the get_m2m method, which would raise a ValueError when no objects were found. It now handles this gracefully and returns an empty list instead. Thanks to @nVitius for this fix.

Improved the ASGI templates (including a fix for the latest Litestar version). Thanks to @sinisaos for this.

1.19.0

24 Sep 00:27
Compare
Choose a tag to compare

Added support for row locking (i.e. SELECT ... FOR UPDATE).

For example, if we have this table:

class Concert(Table):
    name = Varchar()
    tickets_available = Integer()

And we want to make sure that tickets_available never goes below 0, we can do the following:

async def book_tickets(ticket_count: int):
    async with Concert._meta.db.transaction():
        concert = await Concert.objects().where(
            Concert.name == "Awesome Concert"
        ).first().lock_rows()

        if concert.tickets_available >= ticket_count:
            await concert.update_self({
                Concert.tickets_available: Concert.tickets_available - ticket_count
            })
        else:
            raise ValueError("Not enough tickets are available!")

This means that when multiple transactions are running at the same time, it isn't possible to book more tickets than are available.

Thanks to @dkopitsa for adding this feature.

1.18.0

21 Sep 21:26
Compare
Choose a tag to compare

update_self

Added the update_self method, which is an alternative to the save method. Here's an example where it's useful:

# If we have a band object:
>>> band = await Band.objects().get(name="Pythonistas")
>>> band.popularity
1000

# We can increment the popularity, based on the current value in the
# database:
>>> await band.update_self({
...     Band.popularity: Band.popularity + 1
... })

# The new value is set on the object:
>>> band.popularity
1001

# It's safer than using the `save` method, because the popularity value on
# the object might be out of date with what's in the database:
band.popularity += 1
await band.save()

Thanks to @trondhindenes for suggesting this feature.

Batch raw queries

The batch method can now be used with raw queries. For example:

async with await MyTable.raw("SELECT * FROM my_table").batch() as batch:
    async for _batch in batch:
        print(_batch)

This is useful when you expect a raw query to return a lot of data.

Thanks to @devsarvesh92 for suggesting this feature.

1.17.1

14 Sep 20:40
Compare
Choose a tag to compare

Fixed a bug with migrations, where altering a column type from Integer to Float could fail. Thanks to @kurtportelli for reporting this issue.

1.17.0

20 Aug 17:47
Compare
Choose a tag to compare

Each migration is automatically wrapped in a transaction - this can now be disabled using the wrap_in_transaction argument:

manager = MigrationManager(
    wrap_in_transaction=False,
    ...
)

This is useful when writing a manual migration, and you want to manage all of the transaction logic yourself (or want multiple transactions).

granian is now a supported server in the ASGI templates. Thanks to @sinisaos for this.

1.16.0

08 Aug 13:02
Compare
Choose a tag to compare

Added custom async TestCase subclasses, to help with testing.

For example AsyncTransactionTest, which wraps each test in a transaction automatically:

class TestBandEndpoint(AsyncTransactionTest):

    async def test_band_response(self):
        """
        Make sure the endpoint returns a 200.
        """
        # This data automatically gets removed from the database when the
        # test finishes:
        band = Band({Band.name: "Pythonistas"})
        await band.save()

        # Using an API testing client, like httpx:
        response = await client.get(f"/bands/{band.id}/")
        self.assertEqual(response.status_code, 200)

And AsyncTableTest, which automatically creates and drops tables:

class TestBand(AsyncTableTest):

    # These tables automatically get created and dropped:
    tables = [Band]

    async def test_band(self):
        ...

1.15.0

30 Jul 09:27
Compare
Choose a tag to compare

Improved refresh - it now works with prefetched objects. For example:

>>> band = await Band.objects(Band.manager).first()
>>> band.manager.name
"Guido"

# If the manager has changed in the database, when we refresh the band, the
# manager object will also be updated:
>>> await band.refresh()
>>> band.manager.name
"New name"

Also, improved the error messages when creating a BaseUser - thanks to @haaavk for this.

1.14.0

21 Jul 08:41
Compare
Choose a tag to compare

Laying the foundations for alterative Postgres database drivers (e.g.psqlpy). Thanks to @insani7y and @chandr-andr for their help with this.

Warning

The SQL generated by Piccolo changed slightly in this release. Aliases used to be like "manager$name" but now they are like "manager.name" (note $ changed to .). If you are using SelectRaw in your queries to refer to these columns, then they will need updating. Please let us know if you encounter any other issues.

1.13.1

04 Jul 13:08
Compare
Choose a tag to compare

In Piccolo 1.6.0 we moved some aggregate functions to a new file. We now re-export them from their original location to keep backwards compatibility. Thanks to @sarvesh-deserve for reporting this issue.