Skip to main content

Provides functionality for implementing the builder pattern in Python.

Project description

Pyuilder ("pill-der")

Bringing the Builder Design Pattern to Python

Version License Actions Status


Summary

Getting started

Installation

pip install pyuilder

Back to top

Usage

Simple example

Getting started is as easily as inheriting from pyuilder.Buildable:

import pyuilder


class MyBuildable(pyuilder.Buildable):
    def __init__(self, field1: int, field2: str) -> None:
        self.field1 = field1
        self.field2 = field2


MyBuildable.builder().field1(1).field2("hello world").build()

Back to top

Customize the Builder & Add Type Annotations

The last example was great and easy to set up but you'll quickly notice you get no intellisense or typing annotations in your IDE. If that's alright with you: great! However, if you want a little more out of pyuilder it's pretty trivial to provide as much information as you want to pyuilder's Buildable to get auto-complete, type-hinting, and other useful information in your IDE:

from __future__ import annotations

from pyuilder import Buildable, Builder, Setter


class MyBuildable(Buildable):
    class builder(Builder["MyBuildable"]):
        field1: Setter[MyBuildable.builder, int]
        field2: Setter[MyBuildable.builder, str]

    def __init__(self, field1: int, field2: str) -> None:
        self.field1 = field1
        self.field2 = field2


MyBuildable.builder()\
    .field1(1)\
    .field2("hello world")\
    .build()

The setup here ensures correct auto-complete, type hints, etc.:

  • The TypeVar on Builder (Builder["MyBuildable"]) is necessary to correctly annotate the return type of build(): build() -> MyBuildable.
  • The name of the builder class here (builder), is what you will actually will call to invoke the builder: MyBuildable.builder(). Since the Buildable class is annotated to have a method named builder I recommend sticking with the same name.
  • The Setter[...] annotations on the fields take two TypeVars which provide type hinting:
    • The first TypeVar indicates the return type after calling a field setter: .field1() -> MyBuildable.builder. This ensures that type hints aren't lost on chained field setter calls: field1(1).field2("hello world").
    • The second TypeVar indicates the value type the field setter accepts: .field1(value: int).field2(value: str).

Back to top

Set How Strict Static Type Checking Is Done on Builders

If you try out the example above in an IDE you may notice that trying to use the builder with any field name other than the ones you annotated in your custom builder class will end with red squigglies under your code. This is because the Builder class is set up to appear (see note below) to only allow the fields you explicitly declared to static type checkers.

However, you may want to maintain intellisense for annotated fields but want to be able to use any field without warnings from your static type check. pyuilder offers a variant of Bulder called RelaxedBuilder that does just that. The setup is exactly the same as above. Just replace Builder with RelaxedBuilder.

NOTE: Under the hood Builder and RelaxBuilder really do exactly the same thing. There is no behavioral difference between the two classes during runtime. They only differ in how they are processed during static type analysis.

Back to top

Customize the Way Fields Are Set

The other thing you might want to do is have better control how fields are set when you do something like .builder().field(...). If field was a list for example you might want to be able to call field(...) on the builder multiple times to continuously append values to it (similar to Lombok's @Singular in Java). This is certainly possible with pyuilder:

from __future__ import annotations

from pyuilder import Buildable, Builder, Setter


class MyBuildable(Buildable):
    class builder(Builder["MyBuildable"]):
        field: Setter[MyBuildable.builder, str | list[str]]

    def __init__(self, field: list[str]) -> None:
        self.field = field


# Examples

# Setting the field as a list explicitly
MyBuildable.builder().field(["foo", "bar"]).build()

# Specifying how it should be added via str
MyBuildable.builder()\
    .field([])\
    .field("foo", "append")\
    .field("bar", "append")\
    .build()

# Specifying how it should be added via callable
MyBuildable.builder()\
    .field(["foo"])\
    .field("bar", list.append)\
    .build()

If the second parameter to a field setter is a str as it is in the second example it is assumed to be a name of a method on the existing value for that field. In the example above, by the second call of field(), the existing value for field is a list. The setter will find the "append" method on the existing list value and call it with the provided value as the first argument.

If the second parameter to a field setter is a Callable as it is in the third example the setter will call that method. When the setter calls it it will give the existing value as the first argument to the callable and the provided value as the second argument to the callable. In the example above, the provided list.append callable is called with two arguments as described above: list.append(existing, value) where existing = ["foo"] and value = "bar".

Back to top

Change log

See CHANGELOG.

Back to top

License

MIT.

Back to top

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyuilder-0.0.8.tar.gz (7.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyuilder-0.0.8-py3-none-any.whl (6.8 kB view details)

Uploaded Python 3

File details

Details for the file pyuilder-0.0.8.tar.gz.

File metadata

  • Download URL: pyuilder-0.0.8.tar.gz
  • Upload date:
  • Size: 7.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.0.0 CPython/3.12.4

File hashes

Hashes for pyuilder-0.0.8.tar.gz
Algorithm Hash digest
SHA256 24481dae087faa1da3b15e4525c27ba9eb3547e070700309511227a41b0a198b
MD5 00f86ba457ebe0aef11f5772fd1d3925
BLAKE2b-256 cc71b92163bacf4525f683e9d76022665b001c55a6cea2d2b99dc7ac8f14ecf8

See more details on using hashes here.

File details

Details for the file pyuilder-0.0.8-py3-none-any.whl.

File metadata

  • Download URL: pyuilder-0.0.8-py3-none-any.whl
  • Upload date:
  • Size: 6.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.0.0 CPython/3.12.4

File hashes

Hashes for pyuilder-0.0.8-py3-none-any.whl
Algorithm Hash digest
SHA256 4d93cf325b97fe491b57d2d7156ea8f9a06b79c1535b820e001e8ae373987460
MD5 fd748d2534a25b6431146a3dba4759db
BLAKE2b-256 339e2cf6fe3d6bd5afcbe935dbe3f7e1ebe7c7356c18e66ec6f812d505f76858

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page