Skip to main content

Yet Another Brazilian library

Project description

YABRA (Yet Another Brazilian package)

This package was designed to ease out the handling brazilian documents in python code. Maybe in the future this can also support more usecases for brazilian-like usecases (such as zipcodes).

Currently we support the following document types:

Design

Documents are objects that can be created from primitive values such as string, bytes and even int (for the wild users). Why objects? Well, if you ever dealt with python code that handled documents as primitives such as string, you can't be sure if that is a empty string, a invalid document, or anything really. By using a object, you can pass this object around safely knowing that has been validated before.

Why yabra?

Well, you might be wondering why this was created in the first place, and the answer is that there weren't any such libraries for handling documents in such format, there are options, if you don't know them yet, you should check them out:

Installation

In order to use this package, you need to install. You can use the plain old pip:

pip install yabra

Or using modern tools like poetry/pdm/uv:

uv add yabra

If you're going to use this package to validate documents only, then there are no external dependencies. We support python>=3.12.

If you're going to use this package to also generate documents, then you need to install the extra cli dependencies, using:

uv add yabra[cli]

Validating documents

Now that you have the package installed, let's start validating some documents.

None(null) value note

Since None is not a valid document or something that should be treated as a document, you should handle nullable documents as None, and not as instances of a null document.

from yabra.doc.cpf import CPF

# Example for handling nullable document, from a unsafe source, such as a API View/Controller
def a_function_that_might_have_a_nullable_document(primitive_cpf: Any | None) -> None:
    if primitive_cpf is None:
        return do_something()
    # Since the value is not None, then we can create a instance of the CPF
    # If you create a instance of CPF passing None, it will fail validation
    cpf_obj = CPF(primitive_cpf)
    print("Got valid CPF", cpf_obj.validated_value()) # Performs validation
    do_something_with_expected_cpf(cpf_obj)

CPF

A CPF (Cadastro Pessoa Física) is a unique personal document, it identifies a single person on Brazil. A CPF is composed of 11 digits (only numbers), where the first 9 digits are used to identify the person, and the last 2 digits are used to check/verify the previous 9 using a Modulo 11 algorithm. A CPF is considered valid when it contains 11 digits, and the check digits matches the previous 9 digits algorithm calculation.

You can provide several different formats of documents, we only consider the number digits of the string/bytes/int object.

from yabra.doc.cpf import CPF

# A normalized string or bytes
CPF("12345678900")
CPF(b"12345678900")

# A masked/formatted string or bytes
CPF("123.456.789-00")
CPF(b"123.456.789-00")

# And if you're a bad boy using `int`. `int` support is really poor though, we cast the `int` to a `str`
# So you can't really use `int` for anything.
CPF(12345678900)

When you create a instance of the CPF class, nothing happens. The value is only validated if you request. The CPF in the examples above aren't valid, but since we're not requestting for the validated value, then nothing happens. To get the validated value, use the validated_value method.

from yabra.doc.cpf import CPF

CPF("12345678900").validated_value()
# raises InvalidDocumentError("Documento inválido: ...")

CPF("33429493331").validated_value()
# "33429493331"

Now that you had a validated value, you can also mask it, or filter (obsfucate) it. You can use the masked_value and filtered_value methods:

from yabra.doc.cpf import CPF

CPF("12345678900").masked_value()
# Attempting to mask a invalid document
# raises InvalidDocumentError("Documento inválido: ...")

CPF("33429493331").masked_value()
# "334.294.933-31"

# Obfuscating a document
CPF("33429493331").filtered_value()
# "334.***.***-31"

# Obfuscating a document using a custom mask is also possible
# The `mask` argument accepts any string that has placeholders for 0, 1, 2, 3
# where each index is correspoding to a part of the CPF document as if with was masked
CPF("33429493331").filtered_value(mask="{0}.XXX.XXX-{3}")
# "334.XXX.XXX-31"

CPF("33429493331").filtered_value(mask="@@@.{1}.{2}-@@")
# "@@@.294.933-@@"

# It's also possible to have no placeholders at all
CPF("33429493331").filtered_value(mask="@@@.XXX.***-@@")
# "@@@.XXX.***-@@"

If you want to customize the default behavior for validation/filtering check the Customizing section.

CNPJ

A CNPJ (Cadastro Nacional de Pessoa Jurídica) is a unique company document, it identifies a single company on Brazil. A CNPJ is composed of 14 digits (previously only numbers, and starting on June/26 letters and numbers), where the first 8 digits are used to identify the company, the next 4 digits the branch number (such as 0001 the master branch) and the last 2 digits are used to check/verify the previous 12 using a Modulo 11 algorithm. A CNPJ is considered valid when it contains 14 digits, and the check digits matches the previous 12 digits algorithm calculation.

You can provide several different formats of documents, we only consider the number digits of the string/bytes/int object.

from yabra.doc.cnpj import CNPJ

# A normalized string or bytes
CNPJ("83612243000108")
CNPJ(b"83612243000108")

# A masked/formatted string or bytes
CNPJ("83.612.243/0001-08")
CNPJ(b"83.612.243/0001-08")

# And if you're a bad boy using `int`. `int` support is really poor though, we cast the `int` to a `str`
# So you can't really use `int` for anything.
CNPJ(83612243000108)

When you create a instance of the CNPJ class, nothing happens. The value is only validated if you request. To get the validated value, use the validated_value method.

from yabra.doc.cnpj import CNPJ

CNPJ("83612243000100").validated_value()
# raises InvalidDocumentError("Documento inválido: ...")

CNPJ("83612243000108").validated_value()
# "83612243000108"

Now that you had a validated value, you can also mask it, or filter (obsfucate) it. You can use the masked_value and filtered_value methods:

from yabra.doc.cnpj import CNPJ

CNPJ("83612243000100").masked_value()
# Attempting to mask a invalid document
# raises InvalidDocumentError("Documento inválido: ...")

CNPJ("83612243000108").masked_value()
# "83.612.243/0001-08"

# Obfuscating a document
CNPJ("83612243000108").filtered_value()
# "83.***.243/****-08"

# Obfuscating a document using a custom mask is also possible
# The `mask` argument accepts any string that has placeholders for 0, 1, 2, 3, 4
# where each index is correspoding to a part of the CNPJ document as if with was masked
CNPJ("83612243000108").filtered_value(mask="{0}.***.***/{3}-**")
# "83.***.***/0001-**",

CNPJ("83612243000108").filtered_value(mask="@@.{1}.{2}/@@@@-@@")
# "@@.612.243/@@@@-@@"

# It's also possible to have no placeholders at all
CNPJ("83612243000108").filtered_value(mask="@@.@@@.@@@/@@@@-@@")
# "@@.@@@.@@@/@@@@-@@"

By default the CNPJ class accepts alpha-numeric documents. If you want to customize the default behavior for validation/filtering check the Customizing section.

Customizing behavior

Even though you can use yabra classes directly in your code, you probably want and should create your own subclasses of the documents. This is not required, but recommend doing that, because if you ever want to change or customize some behavior, you have only one place to change (DRY). So following this advice, you can create your document classes, just by subclassing our document classes, like:

from yabra.doc.cpf import CPF
from yabra.doc.cnpj import CNPJ

class CPFDocument(CPF):
    pass


class CNPJDocument(CNPJ):
    pass

Now that you have your own classes, how you customize the validation behavior? Read more below on how to customize some of the default behaviors.

Some introduction

Before you get your hands dirty, let's understand how the library was designed. Both CPF, and CNPJ classes inherit from a BaseDoc class, that defines a few methods that are mutual to both document types. When you try to validate a value, what happens under the hood is:

  • We check if the value was previously validated already, if so, return it right away.
  • Call perform_validation
  • If the validation failed, the raise_invalid_document_error method is called, and an Exception is raised.
  • Otherwise, the validated value is stored and returned.

The perform_validation does some checks, based on the spec class, that is set per document class. So if you want to change the behavior, the spec is where you want to change.

Changing the exception raised for invalid documents

Normally in application code you might have a application-wide exception class, that you might want to use instead of our exception class: yabra.doc.exc.InvalidDocumentError. Or maybe you want to change the error messages, or just don't want to import our exception in a lot of different places in your code. In order to use your own exception class, you need to override the method raise_invalid_document_error on each document subclass. This function receives a result parameter, with the type of: yabra.doc.types.PerformValidationErrorResult, that's a TypedDict containing information about the error. More information about inheriting each document class is provided below.

The spec class

The spec attribute available in all document types is used to define the validation behavior of a document, and can be changed by subclasses. We expect that this attribute is a subclass of yabra.doc.base_doc.DocumentSpecification. The available attributes are:

  • MAX_LENGTH type: int The maximum number of digits allowed on this document.

  • NUM_OF_CHECK_DIGITS type: int The number of check digits used to validate this document

  • VALID_DIGITS_RE type: re.Pattern[str] A compiled regular expression that is used to keep only the valid digits of the given document.

  • REPEATED_DIGITS_ALLOWED type: bool A boolean flag that indicates where documents with only repeated digits are allowed, such as 00000000000 CPFs.

  • DEFAULT_FILTERED_VALUE_MASK type: str A string containing a mask that is used to filter/obfuscate a document when no explicit is mask is provided when calling filtered_value. It may contain numeric placeholder for each part of the documents.

With this attributes you can customize most of the behaviors needed from your use case.

Customizing CPF

Using the information we got above, let's customize our CPFDocument class, defined previously:

from yabra.doc.cpf import CPF

# Before, no customization already
class CPFDocument(CPF):
    pass

By default CPFs with repeated digits are not allowed, if you want to allow it, then you need to do the following:

from yabra.doc.cpf import CPF, CPFSpecification

class MyCustomCPFSpecification(CPFSpecification):
    REPEATED_DIGITS_ALLOWED = True


class CPFDocument(CPF):
    spec = MyCustomCPFSpecification

Now you can accept repeated digits. If the government ever change how CPFs are validated, you can change some of the attributes of your MyCustomCPFSpecification, such as: MAX_LENGTH.

Let's say that you want to customize the default filtered/obfuscated mask to use the "X" char instead of the "*" char when calling the filtered_value method with no explicit mask. For that you can do the following:

from yabra.doc.cpf import CPF, CPFSpecification

class MyCustomCPFSpecification(CPFSpecification):
    DEFAULT_FILTERED_VALUE_MASK = "{0}.XXX.{2}-XX"


class CPFDocument(CPF):
    spec = MyCustomCPFSpecification

Note that you can provide placeholders entries for indexes: 0, 1, 2 and 3. Where for the following CPF: 33429493331 the indexes will have the following value:

  • 0: 334
  • 1: 294
  • 2: 933
  • 3: 31

Customizing CNPJ

By default we accept alpha-numeric CNPJs. If that's not what you want, you can easily customize this behavior. Using the same concept as before, we override the spec class on our CNPJDocument class. For that we've already created a few specifications for both classes, since the check digit calculation algorithm is slightly different. For that you do the following:

from yabra.doc.cnpj import CNPJ, NumericCNPJSpecification, AlphaNumericCNPJSpecification

# This is the same as not setting the `spec` attribute, but if you want to be explicit..
class CNPJDocument(CNPJ):
    spec = AlphaNumericCNPJSpecification

# Accept only Numeric CNPJs
class CNPJDocument(CNPJ):
    spec = NumericCNPJSpecification

To customize the default filtered value mask, follow the same instructions from the CPF above. The key difference is that you have 5 available placeholder indexes: 0, 1, 2, 3 and 4. Where for the following CNPJ: 83612243000108 the indexes will have the following value:

  • 0: 83
  • 1: 612
  • 2: 243
  • 3: 0001
  • 4: 08

Generating documents

A common task for development is to use a valid document, since is not easy to a human to create a valid document, we've a CLI utility to generate documents.

The intended usecase for this feature is when on some staging/QA environment you need a specific prefixed document for a mocked/succesful response, any malicious usage of this feature is not intended and against the main goal of this feature. We're not responsible for any of the information associated with any documents generated from this feature.

Make sure you've installed this package with the cli extras, if you haven't install it:

uv add yabra[cli]

To generate a valid CPF document, you can do the following:

python -m yabra.doc generate cpf

# or for CNPJ
python -m yabra.doc generate cnpj

If you need a prefixed document, use the prefix option:

python -m yabra.doc generate cpf --prefix 0

# or for CNPJ
python -m yabra.doc generate cnpj --prefix 40

# The prefix can have several digits
python -m yabra.doc generate cpf --prefix 334294
python -m yabra.doc generate cnpj --prefix 334294

If you need to generate more than one document at once, use the number option

python -m yabra.doc generate cpf --number 10
python -m yabra.doc generate cnpj --number 10

By default, documents are generate using a pseudo-random number generator. You can use different algorithms in order to achieve different results

# Same as omitting
python -m yabra.doc generate cpf --algorithm random

# Follows a sequence that starts at 0
python -m yabra.doc generate cpf --algorithm sequential

# Alpha-numeric versions algorithms are also supported, the document type must support alpha numeric
# digits in order to documents to be generated
python -m yabra.doc generate cnpj --algorithm alpha_random
python -m yabra.doc generate cnpj --algorithm alpha_sequential

You can also mask/format the documents on output:

python -m yabra.doc generate cpf --mask
python -m yabra.doc generate cnpj --mask

All together are also supported:

python -m yabra.doc generate cpf --prefix 9 --number 100 --algorithm sequential --mask
python -m yabra.doc generate cnpj --prefix 9 --number 100 --algorithm alpha_random --mask

Since the documents are outputted to stdout, you can use the shell redirects, to create a file for example:

python -m yabra.doc generate cpf --number 1000 > test.txt

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

yabra-0.1.0.tar.gz (10.3 kB view details)

Uploaded Source

Built Distribution

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

yabra-0.1.0-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

File details

Details for the file yabra-0.1.0.tar.gz.

File metadata

  • Download URL: yabra-0.1.0.tar.gz
  • Upload date:
  • Size: 10.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.22

File hashes

Hashes for yabra-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3775652dce42658ab44d64047acdbc6c43053baa92d9c6e963208e99b4e4ba3e
MD5 c01d78678bbfe110f2ef5eb41bd26cf8
BLAKE2b-256 1ce4e399588b6591e8dbbc02b329294a9a5a2679b30e12fdbd5af0078c031638

See more details on using hashes here.

File details

Details for the file yabra-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: yabra-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 13.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.22

File hashes

Hashes for yabra-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6afd5f8d6ba9921a8a06ee6158a222ca30df4f22cb8febd7a6e8e076cff7fb6e
MD5 5c41b2ea20d033b10136ac3bcfc4da4a
BLAKE2b-256 13717f5a59132a234a07f189d09af921ccb01ac87fd8e75cc43611fded50f662

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