Skip to main content

Enables human-readable formats like Markdown or Spark's .show() for unit test input/expected tables — easy to document and version, making test data changes simple to track and review

Project description

PyBujia

tests passing pypi version python versions

Enables human-readable formats like Markdown or Spark's .show() for unit test input/expected tables — easy to document and version, making test data changes simple to track and review. Includes helpers to make writing PySpark unit tests easier.

Requirements

  • Python >= 3.9
  • PySpark >= 3.0.0
  • Java JDK => 8, 11 and some PySpark versions 17
  • OS Linux or macOS

Installation

pip install pybujia

Example

This is how readable and simple your unit tests data and documentation could look:

You can add here the requirements and details

Table: my_db.user_actions

user_id integer event_id integer event_type integer event_date timestamp
445 7765 sign-in 2022-05-31 12:00:00
445 3634 like 2022-06-05 12:00:00
648 3124 like 2022-06-18 12:00:00
648 2725 sign-in 2022-06-22 12:00:00
648 8568 comment 2022-07-03 12:00:00
445 4363 sign-in 2022-07-05 12:00:00
445 2425 like 2022-07-06 12:00:00
445 2484 like 2022-07-22 12:00:00
648 1423 sign-in 2022-07-26 12:00:00
445 5235 comment 2022-07-29 12:00:00
742 6458 sign-in 2022-07-03 12:00:00
742 1374 comment 2022-07-19 12:00:00

Table: my_db.output__expected

Input table: my_db.user_actions

month integer monthly_active_users long
6 1
7 8

You can add more clarifications here too.

This is how easy writing unit tests is:

def test_user_actions_method_transformation(self) -> None:
    spark_job = UserActionsJob(self._spark)
    fixtures = PyBujia(
        os.path.join(self.CURRENT_DIR, "solution.tests.md"),
        self._spark,
    )
    spark_method_test(
        spark_job._transformation,
        fixtures,
        input_args=["my_db.user_actions"],
        expected_result="my_db.output__expected",
    )

If you want to test the whole job, end to end, not just a method:

def test_user_actions_job_solution(self):
    fixtures = PyBujia(
        os.path.join(self.CURRENT_DIR, "solution.tests.md"),
        self._spark,
    )
    spark_job_test(
        self._spark,
        UserActionsJob.INPUT_TABLES,
        UserActionsJob.OUTPUT_TABLES,
        lambda: UserActionsJob(self._spark).run(),
        fixtures,
    )

This exact test code works for any spark job, the only difference is the input fixtures file name and Spark Job class

PySpark job implementation:

from typing import Final

from pyspark.sql import DataFrame
import pyspark.sql.functions as F


class UserActionsJob:
    DB_NAME: Final[str] = "my_db"

    # Used in the unit tests and here for reference
    INPUT_TABLES: Final[list[tuple]] = [
        (DB_NAME, "user_actions"),
    ]

    OUTPUT_TABLES: Final[list[tuple]] = [
        (DB_NAME, "output"),
    ]

    def __init__(self, spark: SparkSession) -> None:
        self._spark = spark

    def _transformation(self, user_actions_df: DataFrame) -> DataFrame:
        clean_df = (
            user_actions_df.withColumn("current_year_month", F.date_format("event_date", "yyyy-MM"))
            .withColumn(
                "previous_year_month",
                F.date_format(F.add_months(user_actions_df.event_date, -1), "yyyy-MM"),
            )
            .select("user_id", "current_year_month", "previous_year_month")
        )

        return (
            clean_df.alias("current")
            .join(
                clean_df.alias("previous"),
                (F.col("current.user_id") == F.col("previous.user_id"))
                & (F.col("current.previous_year_month") == F.col("previous.current_year_month")),
            )
            .groupBy(F.month(F.col("current.current_year_month")).alias("month"))
            .agg(F.count(F.lit(1)).alias("monthly_active_users"))
        )

    def run(self) -> None:
        user_actions_df = self._spark.table(f"{self.DB_NAME}.user_actions")
        result_df = self._transformation(user_actions_df)

        result_df.write.mode("overwrite").saveAsTable(f"{self.DB_NAME}.output")

Structure and Guidelines for Data Fixtures

To define a table in a data fixture, begin the line with the keyword "Table:" - any preceding header symbols "###" won't interfere with detection, as the parser will still interpret it correctly.

Test data rows must use the pipe symbol (|) as the leading character. This symbol also separates individual column values, operating much like a comma in a CSV file.

In cases where you need an actual pipe character as part of a column's content, escape it with a backslash: \|.

To denote a NULL value, write it as <NULL>

Markdown formatting is fully supported. You can embed links, images, or add human-friendly notes to make the fixture files easier to understand.

The inline data types must be PySpark valid.

This format plays a dual role - it doubles as test documentation and actual input for executing unit tests.

Note

This project was built independently as a general-purpose utility for PySpark unit testing.

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

pybujia-0.1.0rc9.tar.gz (17.3 kB view details)

Uploaded Source

Built Distribution

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

pybujia-0.1.0rc9-py3-none-any.whl (12.8 kB view details)

Uploaded Python 3

File details

Details for the file pybujia-0.1.0rc9.tar.gz.

File metadata

  • Download URL: pybujia-0.1.0rc9.tar.gz
  • Upload date:
  • Size: 17.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.10.12

File hashes

Hashes for pybujia-0.1.0rc9.tar.gz
Algorithm Hash digest
SHA256 93c31e6a210f289ecf5a54daa7ef4b4666fa24aae89a13400a8913834f6473af
MD5 753a22a5594fbde09ccdb6aca945b984
BLAKE2b-256 9bf88aef5920bda37fe187480cd599264a683398729c3ea01f86b5c45dc24a65

See more details on using hashes here.

File details

Details for the file pybujia-0.1.0rc9-py3-none-any.whl.

File metadata

  • Download URL: pybujia-0.1.0rc9-py3-none-any.whl
  • Upload date:
  • Size: 12.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.10.12

File hashes

Hashes for pybujia-0.1.0rc9-py3-none-any.whl
Algorithm Hash digest
SHA256 c3a40f8f837bf23c24c159bfe92b583b611e50631410f31483896f8833f31071
MD5 aebc591a53ea785c13e739a6dd876bf1
BLAKE2b-256 85f86c79712a481b009c4978524c071e8875fb1b3bc7c30ed12a557f42179c02

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