Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions barcode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
from barcode.version import version # noqa: F401

if TYPE_CHECKING:
from typing import Any

from barcode.base import Barcode
from barcode.writer import BaseWriter

__BARCODE_MAP: dict[str, type[Barcode]] = {
__BARCODE_MAP: dict[str, type[Barcode[Any]]] = {
"codabar": CODABAR,
"code128": Code128,
"code39": Code39,
Expand Down Expand Up @@ -65,25 +67,28 @@

@overload
def get(
name: str, code: str, writer: BaseWriter | None = None, options: dict | None = None
) -> Barcode: ...
name: str,
code: str,
writer: BaseWriter[Any] | None = None,
options: dict | None = None,
) -> Barcode[Any]: ...


@overload
def get(
name: str,
code: None = None,
writer: BaseWriter | None = None,
writer: BaseWriter[Any] | None = None,
options: dict | None = None,
) -> type[Barcode]: ...
) -> type[Barcode[Any]]: ...


def get(
name: str,
code: str | None = None,
writer: BaseWriter | None = None,
writer: BaseWriter[Any] | None = None,
options: dict | None = None,
) -> Barcode | type[Barcode]:
) -> Barcode[Any] | type[Barcode[Any]]:
"""Helper method for getting a generator or even a generated code.

:param name: The name of the type of barcode desired.
Expand All @@ -96,7 +101,7 @@ def get(
generating.
"""
options = options or {}
barcode: type[Barcode]
barcode: type[Barcode[Any]]
try:
barcode = __BARCODE_MAP[name.lower()]
except KeyError as e:
Expand All @@ -107,14 +112,14 @@ def get(
return barcode


def get_class(name: str) -> type[Barcode]:
def get_class(name: str) -> type[Barcode[Any]]:
return get_barcode(name)


def generate(
name: str,
code: str,
writer: BaseWriter | None = None,
writer: BaseWriter[Any] | None = None,
output: str | os.PathLike | BinaryIO | None = None,
writer_options: dict | None = None,
text: str | None = None,
Expand Down
41 changes: 35 additions & 6 deletions barcode/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@

from typing import TYPE_CHECKING
from typing import ClassVar
from typing import Generic
from typing import cast

from barcode.writer import BaseWriter
from barcode.writer import SVGWriter
from barcode.writer import T_Output

if TYPE_CHECKING:
from collections.abc import Callable
from typing import Any
from typing import BinaryIO


class Barcode:
class Barcode(Generic[T_Output]):
name = ""

digits = 0

default_writer = SVGWriter
default_writer: ClassVar[Callable[[], BaseWriter[Any]]] = SVGWriter

default_writer_options: ClassVar[dict] = {
"module_width": 0.2,
Expand All @@ -31,11 +36,28 @@ class Barcode:
"text": "",
}

writer: BaseWriter
writer: BaseWriter[T_Output]

def __init__(self, code: str, writer: BaseWriter | None = None, **options) -> None:
def __init__(
self,
code: str,
writer: BaseWriter[T_Output] | None = None,
**options,
) -> None:
raise NotImplementedError

def _resolve_writer(
self,
writer: BaseWriter[T_Output] | None,
) -> BaseWriter[T_Output]:
if writer is not None:
return writer
# When no writer is given, T_Output falls back to its default (bytes),
# which matches what the default writer (SVGWriter) renders. Accessing
# default_writer through the class keeps mypy from binding it like a
# method.
return cast("BaseWriter[T_Output]", type(self).default_writer())

def to_ascii(self) -> str:
code_list = self.build()
if not len(code_list) == 1:
Expand All @@ -62,7 +84,10 @@ def get_fullcode(self):
raise NotImplementedError

def save(
self, filename: str, options: dict | None = None, text: str | None = None
self,
filename: str,
options: dict | None = None,
text: str | None = None,
) -> str:
"""Renders the barcode and saves it in `filename`.

Expand Down Expand Up @@ -92,7 +117,11 @@ def write(
output = self.render(options, text)
self.writer.write(output, fp)

def render(self, writer_options: dict | None = None, text: str | None = None):
def render(
self,
writer_options: dict | None = None,
text: str | None = None,
) -> T_Output:
"""Renders the barcode using `self.writer`.

:param writer_options: Options for `self.writer`, see writer docs for details.
Expand Down
18 changes: 15 additions & 3 deletions barcode/codabar.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@

__docformat__ = "restructuredtext en"

from typing import TYPE_CHECKING

from barcode.base import Barcode
from barcode.charsets import codabar
from barcode.errors import BarcodeError
from barcode.errors import IllegalCharacterError
from barcode.writer import T_Output

if TYPE_CHECKING:
from barcode.writer import BaseWriter


class CODABAR(Barcode):
class CODABAR(Barcode[T_Output]):
"""Initializes a new CODABAR instance.

:param code: Codabar (NW-7) string that matches [ABCD][0-9$:/.+-]+[ABCD]
Expand All @@ -25,9 +31,15 @@ class CODABAR(Barcode):

name = "Codabar (NW-7)"

def __init__(self, code, writer=None, narrow=2, wide=5) -> None:
def __init__(
self,
code,
writer: BaseWriter[T_Output] | None = None,
narrow=2,
wide=5,
) -> None:
self.code = code
self.writer = writer or self.default_writer()
self.writer = self._resolve_writer(writer)
self.narrow = narrow
self.wide = wide

Expand Down
41 changes: 27 additions & 14 deletions barcode/codex.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from barcode.errors import BarcodeError
from barcode.errors import IllegalCharacterError
from barcode.errors import NumberOfDigitsError
from barcode.writer import T_Output

if TYPE_CHECKING:
from collections.abc import Collection
Expand All @@ -40,12 +41,17 @@ def check_code(code: str, name: str, allowed: Collection[str]) -> None:
)


class Code39(Barcode):
class Code39(Barcode[T_Output]):
"""A Code39 barcode implementation"""

name = "Code 39"

def __init__(self, code: str, writer=None, add_checksum: bool = True) -> None:
def __init__(
self,
code: str,
writer: BaseWriter[T_Output] | None = None,
add_checksum: bool = True,
) -> None:
r"""
:param code: Code 39 string without \* and without checksum.
:param writer: A ``barcode.writer`` instance used to render the barcode
Expand All @@ -56,7 +62,7 @@ def __init__(self, code: str, writer=None, add_checksum: bool = True) -> None:
self.code = code.upper()
if add_checksum:
self.code += self.calculate_checksum()
self.writer = writer or self.default_writer()
self.writer = self._resolve_writer(writer)
check_code(self.code, self.name, code39.REF)

def __str__(self) -> str:
Expand All @@ -83,13 +89,17 @@ def build(self) -> list[str]:
result = code39.MIDDLE.join(chars)
return [result]

def render(self, writer_options=None, text=None):
def render(
self,
writer_options: dict | None = None,
text: str | None = None,
) -> T_Output:
options = {"module_width": MIN_SIZE, "quiet_zone": MIN_QUIET_ZONE}
options.update(writer_options or {})
return super().render(options, text)


class PZN7(Code39):
class PZN7(Code39[T_Output]):
"""Initializes new German number for pharmaceutical products.

:param pzn: Code to render.
Expand All @@ -100,7 +110,7 @@ class PZN7(Code39):

digits = 6

def __init__(self, pzn, writer=None) -> None:
def __init__(self, pzn, writer: BaseWriter[T_Output] | None = None) -> None:
pzn = pzn[: self.digits]
if not pzn.isdigit():
raise IllegalCharacterError("PZN can only contain numbers.")
Expand All @@ -124,13 +134,13 @@ def calculate_checksum(self):
return checksum


class PZN8(PZN7):
class PZN8(PZN7[T_Output]):
"""Will be fully added in v0.9."""

digits = 7


class Code128(Barcode):
class Code128(Barcode[T_Output]):
"""Initializes a new Code128 instance. The checksum is added automatically
when building the bars.

Expand All @@ -141,12 +151,11 @@ class Code128(Barcode):
name = "Code 128"
_charset: Literal["A", "B", "C"]
code: str
writer: BaseWriter
buffer: str

def __init__(self, code: str, writer=None) -> None:
def __init__(self, code: str, writer: BaseWriter[T_Output] | None = None) -> None:
self.code = code
self.writer = writer or self.default_writer()
self.writer = self._resolve_writer(writer)
self._charset = "C"
self._digit_buffer = "" # Accumulate pairs of digits for charset C
check_code(self.code, self.name, code128.ALL)
Expand Down Expand Up @@ -307,13 +316,17 @@ def build(self) -> list[str]:
code += "11"
return [code]

def render(self, writer_options=None, text=None):
def render(
self,
writer_options: dict | None = None,
text: str | None = None,
) -> T_Output:
options = {"module_width": MIN_SIZE, "quiet_zone": MIN_QUIET_ZONE}
options.update(writer_options or {})
return super().render(options, text)


class Gs1_128(Code128): # noqa: N801
class Gs1_128(Code128[T_Output]): # noqa: N801
"""
following the norm, a gs1-128 barcode is a subset of code 128 barcode,
it can be generated by prepending the code with the FNC1 character
Expand All @@ -325,7 +338,7 @@ class Gs1_128(Code128): # noqa: N801

FNC1_CHAR = "\xf1"

def __init__(self, code, writer=None) -> None:
def __init__(self, code, writer: BaseWriter[T_Output] | None = None) -> None:
code = self.FNC1_CHAR + code
super().__init__(code, writer)

Expand Down
Loading
Loading