Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: Bastian-Kuhn/wallet
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: NafieAlhilaly/py-pkpass
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: develop
Choose a head ref
Able to merge. These branches can be automatically merged.

Commits on Jul 13, 2022

  1. Copy the full SHA
    ee3cda0 View commit details
  2. test: dummy test

    NafieAlhilaly committed Jul 13, 2022
    Copy the full SHA
    4bc10f1 View commit details
  3. Merge pull request devartis#1 from NafieAlhilaly/style/lint-format-st…

    …ructure
    
    style: lint, format and restructure files
    NafieAlhilaly authored Jul 13, 2022
    Copy the full SHA
    7084904 View commit details
  4. Copy the full SHA
    6ec714f View commit details
  5. Merge pull request devartis#2 from NafieAlhilaly/feat/field-validation

    feat: add Field validation
    NafieAlhilaly authored Jul 13, 2022
    Copy the full SHA
    8327fe4 View commit details
  6. Copy the full SHA
    78d1b83 View commit details
  7. Merge pull request devartis#4 from NafieAlhilaly/refactor/refactor-Ba…

    …rcode-params-and-docs
    
    refactor: refactor Barcode params and update readme
    NafieAlhilaly authored Jul 13, 2022
    Copy the full SHA
    c8d7c31 View commit details
  8. feat: add screenshot

    NafieAlhilaly committed Jul 13, 2022
    Copy the full SHA
    f2f7932 View commit details
  9. Copy the full SHA
    ddc0a73 View commit details
  10. docs: update readme

    NafieAlhilaly committed Jul 13, 2022
    Copy the full SHA
    e4e4cdb View commit details
  11. Copy the full SHA
    bbf29a6 View commit details

Commits on Jul 15, 2022

  1. Copy the full SHA
    fd44285 View commit details
  2. Merge pull request devartis#7 from NafieAlhilaly/test/add-tests

    test: add tests and test assets
    NafieAlhilaly authored Jul 15, 2022
    Copy the full SHA
    f1cf88f View commit details
  3. refactor: pass class

    NafieAlhilaly committed Jul 15, 2022
    Copy the full SHA
    9c702fc View commit details
  4. Copy the full SHA
    a288f59 View commit details
  5. Copy the full SHA
    0db31eb View commit details
  6. Merge pull request devartis#11 from NafieAlhilaly/feat/enhance-pass-r…

    …ead_file
    
    feat: enhance pass add_file function to accept bytes or BufferedReader
    NafieAlhilaly authored Jul 15, 2022
    Copy the full SHA
    89d093e View commit details
  7. Copy the full SHA
    26f93c1 View commit details
  8. Copy the full SHA
    1d47685 View commit details

Commits on Jul 18, 2022

  1. Copy the full SHA
    81c2578 View commit details
  2. Merge pull request devartis#12 from NafieAlhilaly/feat/run-ci-on-mult…

    …ible-python-versions
    
    ci: run test on multible python versions
    NafieAlhilaly authored Jul 18, 2022
    Copy the full SHA
    377bcce View commit details

Commits on Jul 21, 2022

  1. feat: add NFC support

    NafieAlhilaly committed Jul 21, 2022
    Copy the full SHA
    40915d6 View commit details

Commits on Jul 22, 2022

  1. Merge pull request devartis#13 from NafieAlhilaly/feat/add-nfc

    feat: add NFC support
    NafieAlhilaly committed Jul 22, 2022
    Copy the full SHA
    40a2579 View commit details
44 changes: 44 additions & 0 deletions .github/workflows/on_pull_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Checks
on:
pull_request:
branches:
- "develop"
- "master"
workflow_dispatch:
jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v2
codelint:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- name: Lint with flake8
run: |
python -m pip install --upgrade pip
pip install flake8
flake8 . --ignore=E203,W605,W503 --count --show-source --max-complexity=16 --max-line-length=127 \
--statistics --exclude .git,__pycache__,__init__.py,test
unittests:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- name: start the app & run unit tests with python ${{ matrix.python-version }}
shell: bash
run: |
python -m pip install --upgrade pip
pip install pydantic
pip install pytest
pytest
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
*.py[co]
*.py~

# pytest
.pytest_cache
__pycache__

# Packages
MANIFEST
*.egg
@@ -31,3 +35,19 @@ pip-log.txt
.DS_Store
.idea/
*.swp

venv
.vscode
poetry.lock

wallet/tmp

.env

generate_test_pass.py

test_pass.pkpass

icon2x.png
shark-icon.png
sea.jpg
165 changes: 89 additions & 76 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,51 @@
# Wallet-py3k
# py-pkpass

Python library to read/write [Apple Wallet](http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/PassKit_PG/Chapters/Introduction.html#//apple_ref/doc/uid/TP40012195-CH1-SW1) (.pkpass) files
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge&logo=python)](https://github.com/psf/black)
![](https://img.shields.io/badge/Cov-83%25-green?style=for-the-badge&logo=appveyor)
![](https://img.shields.io/github/workflow/status/NafieAlhilaly/py-pkpass/Checks?label=Test&style=for-the-badge)

This is a fork of https://github.com/ofw/wallet-py3k which does not suits my needs

Python library to read/write [Apple Wallet](http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/PassKit_PG/Chapters/Introduction.html#//apple_ref/doc/uid/TP40012195-CH1-SW1) (.pkpass) files, see also [Pass Design and Creation](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/Creating.html)

This is a fork of https://github.com/Bastian-Kuhn/wallet , the original fork https://github.com/devartis/passbook

## Getting started
```bash
git clone https://github.com/NafieAlhilaly/py-pkpass.git
```
move to py-pkpass dir
```bash
cd py-pkpass
```

create python virtual environment
```bash
python3 -m venv venv
```

activate your virtual environmetn
```bash
source <your-env-name>/bin/activate
```


## New Features
* Direct Generation of passes without thee need to store them on a filesystem (if wanted)
* Password less Keys possible if wanted
* Validation of Fields and Passes including own Exception (PassParameterException)
* Complete Refactored and Simplified Code
* Complete Refactored and Simplified Code (Still WIP)
* adding file to pass can be from eather IO(locally) or form a URL.


## ToDos
* Update of Getting Started
* Add docker/docker-compose
* Validate data
* Add NFC support
* Full Example including which Fields are Possible


## Old: Getting Started
This part from here needs to be updated:

## Before creating a pkpass file you need to :
1. Create a Pass Type Id:
1. Visit the [Visit the iOS Provisioning Portal](https://developer.apple.com/account/resources/certificates/list)
2. On the left, click Identifiers
@@ -51,40 +77,64 @@ This part from here needs to be updated:
## Typical Usage
create your .py file on the root and try the following example
```python
from wallet.models import Pass, Barcode, StoreCard
pass_type_identifier = "pass.com.yourcompany.some_name"
team_identifier = "ABCDE1234" # Your Apple team ID
cert_pem = "certficate.pem"
key_pem = "key.pem"
wwdr_pem = "AppleWWDRCA.pem"
key_pem_password = "your_password"
cardInfo = StoreCard()
cardInfo.addPrimaryField('name', 'John Doe', 'Name')
passfile = Pass(cardInfo,
passTypeIdentifier=cls.pass_type_identifier,
organizationName=cls.organization_name,
teamIdentifier=cls.team_identifier)
# charge_response.id is trackable via the Stripe dashboard
passfile.serialNumber = charge_response.id
passfile.barcode = Barcode(message = charge_response.id, format=BarcodeFormat.PDF417)
passfile.description = charge_response.description
# Including the icon and logo is necessary for the passbook to be valid.
passfile.addFile("icon.png", open("icon.png", "rb"))
passfile.addFile("logo.png", open("logo.png", "rb"))
_ = passfile.create(cls.cert_pem,
cls.key_pem,
cls.wwdr_pem,
cls.key_pem_password,
"pass_name.pkpass")
from wallet.PassStyles import StoreCard, EventTicket, Coupon, Generic, BoardingPass
from wallet.Pass import Pass
from wallet.PassProps.Barcode import Barcode
from wallet.Schemas.FieldProps import FieldProps
pass_type_identifier = "pass.com.yourcompany.some_name"
team_identifier = "ABCDE123" # Your Apple team ID
card = StoreCard() # or EventTicket, or Coupon, or Generic, or BoardingPass
card.add_header_field(FieldProps(key="k2", value="69", label="Points"))
card.add_secondary_field(FieldProps(key="k3", value="Small shark", label="Level"))
card.add_back_field(FieldProps(key="k5", value="first backfield", label="bf1"))
optional_pass_info = {
"logo_text": "Sharks",
"description": "some discription",
"background_color": "rgb(38, 93, 205)",
"foreground_color": "rgb(255, 255, 255)",
"label_color": "rgb(189, 189, 189)",
"barcodes": [Barcode(message="testing")]
}
passfile = Pass(
card,
pass_type_identifier,
team_identifier,
"organization_name",
**optional_pass_info
)
passfile.add_file("icon.png", open("shark-icon.png", "rb"))
passfile.add_file("icon@2x.png", open("shark-icon.png", "rb"))
passfile.add_file("icon@3x.png", open("shark-icon.png", "rb"))
passfile.add_file("logo.png", open("shark-icon.png", "rb"))
passfile.add_file("logo@2x.png", open("shark-icon.png", "rb"))
passfile.add_file("logo@3x.png", open("shark-icon.png", "rb"))
# strip image is optional
# you can pass an image url directly to add_file
strip = get("https://images.pexels.com/photos/5967796/pexels-photo-5967796.jpeg").content
passfile.add_file("strip.png", strip)
passfile.create(
"signerCert.pem",
"signerKey.pem",
"wwdr.pem",
password="password",
file_name="test_pass.pkpass",
)
```
### example pass
<img src="https://github.com/NafieAlhilaly/py-pkpass/blob/develop/Screenshot/pass_screenshot.png" alt="drawing" style="width:200px;"/>
### Notes
@@ -93,40 +143,3 @@ This part from here needs to be updated:
* `passfile.create()` returns the name of the generated file, which matches what you pass to it as the fifth parameter.
* Valid `cardInfo` constructors mirror the pass types defined by Apple. For example, `StoreCard()`, `BoardingPass()`, `Coupon()`, etc.
* The various "add field" methods (e.g. `addPrimaryField()`) take three unnamed parameters in the order `key`, `value`, `label`
An example Flask route handler to return the generated pass files:
```python
@application.route("/passes/<path:fname>")
def passes_proxy(fname):
"""static passes serve"""
return send_from_directory("passes", fname, mimetype="application/vnd.apple.pkpass")
```
An example usage in a React app using Stripe and Stripe Elements to process payments and generate a store pass:
```javascript
paymentRequest.on('token', async (ev) => {
try {
const response = await fetch('https://your_server/charge', {
method: 'POST',
body: JSON.stringify({
token: ev.token.id,
amount: totalInCents,
description: purchasedItems.join(',\n')
}),
headers: {'content-type': 'application/json'},
});
if (!response.ok) {
throw new Error('There was a problem processing your payment.');
}
// Report to the browser that the payment was successful, prompting
// it to download the pass file to the user's Wallet
ev.complete('success');
const pkpass = await response.json();
window.location.href = `https://your_server/passes/${pkpass.filename}`;
} catch (error) {
throw new Error("There was a problem processing your payment.");
}
});
```
Binary file added Screenshot/pass_screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[tool.poetry]
name = "py-pkpass"
version = "0.1.1"
description = "python library to create pkpass files (Apple Wallet files)"
authors = []
license = "MIT license"

[tool.poetry.dependencies]
python = "^3.10"
pydantic = "^1.9.1"

[tool.poetry.dev-dependencies]
six = "^1.16.0"
black = "^22.6.0"
flake8 = "^4.0.1"
pytest = "^7.1.2"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@

install_requires=install_requires,

classifiers = [
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Other Environment',
'Intended Audience :: Developers',
5 changes: 5 additions & 0 deletions wallet/.flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[flake8]
max-line-length= 127
ignore = E203,W605,W503
max-complexity= 16
exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache,test
345 changes: 345 additions & 0 deletions wallet/Pass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,345 @@
import decimal
import hashlib
from io import BytesIO, BufferedReader
import json
import subprocess
import zipfile
import tempfile
from uuid import uuid4

from wallet.PassInformation import PassInformation
from typing import Optional, List, Union
from wallet.PassProps import Barcode, Location, IBeacon, NFC
from .exceptions import PassParameterException


def pass_handler(obj):
"""Pass Handler"""
if hasattr(obj, "json_dict"):
return obj.json_dict()
# For Decimal latitude and logitude etc.
if isinstance(obj, decimal.Decimal):
return str(obj)
return obj


class Pass:
def __init__(
self,
pass_information: PassInformation,
pass_type_identifier: str,
team_identifier: str,
organization_name: str,
*,
serial_number: str = str(uuid4()),
description: str = "pass description",
background_color: Optional[str] = None,
foreground_color: Optional[str] = None,
label_color: Optional[str] = None,
logo_text: Optional[str] = None,
barcodes: Optional[Barcode] = None,
show_strip_img: bool = False,
web_service_url: str = None,
authentication_token: str = None,
locations: List[Location] = None,
ibeacons: List[IBeacon] = None,
relevant_date: str = None,
associated_store_identifiers: list = None,
app_launch_url: str = None,
user_info: json = None,
expriration_date: str = None,
voided: bool = False,
nfc: NFC = None
) -> None:
"""
Prepare Pass
:params pass_information: Instance of the following classes :
- StoreCard
- BoardingPass
- Coupon
- EventTicket
- Generic
:params pass_type_identifier: Pass type identifier, as
issued by Apple. The value must
correspond with your signing certificate. Used for grouping.
:params organization_name: Display name of the organization
that originated and signed the pass.
:params team_identifier: Team identifier of the organization
that originated and
signed the pass, as issued by Apple.
:params serial_number: Serial number that
uniquely identifies the pass.
:params description: Brief description of
the pass, used by the iOS
accessibility technologies.
:params background_color: background color of the pass
:params foreground_color: Foreground color of the pass
:params label_color: Color of the label text
:params logo_text: Text displayed next to the logo
:params barcode: Information specific to barcodes.
:params show_strip_img: If true, the strip image is displayed
:params web_service_rul: If present, authenticationToken must
be supplied
:params authentication_token: The authentication token to use with
the web service
:params locations: Locations where the pass is relevant.
:params relevent_date: Date and time when the pass becomes relevant
:params associated_store_identifiers: A list of iTunes Store item
identifiers for
the associated apps.
:params user_info: Additional hidden data in json for the passbook
:params expriration_date: date where pass becomes expired
:params voided: make a pass expire
"""

self._files = {} # Holds the files to include in the .pkpass
self._hashes = {} # Holds the SHAs of the files array

# Standard Keys that required by Apple
self.teamIdentifier = team_identifier
self.passTypeIdentifier = pass_type_identifier
self.organizationName = organization_name
self.serialNumber = serial_number
self.description = description
self.formatVersion = 1

# Visual Appearance Keys, optional attributes
self.backgroundColor = background_color
self.foregroundColor = foreground_color
self.labelColor = label_color
self.logoText = logo_text
if barcodes:
self.barcodes = [*barcodes]
else:
self.barcodes = []
self.suppressStripShine = show_strip_img

# Web Service Keys
self.webServiceURL = web_service_url
self.authenticationToken = authentication_token

# Relevance Keys
self.locations = locations
self.ibeacons = ibeacons
self.relevantDate = relevant_date
self.associatedStoreIdentifiers = associated_store_identifiers
self.appLaunchURL = app_launch_url
self.userInfo = user_info

self.exprirationDate = expriration_date
self.voided = voided
if nfc:
self.NFC = nfc

self.passInformation = pass_information

def add_file(
self, name: str, file_handle: Union[BufferedReader, bytes]
) -> None:
"""
Add new file to the pass files
:params name: String name
:params file_handle: File Handle
"""
if type(file_handle) == bytes:
self._files[name] = file_handle
elif type(file_handle) == BufferedReader:
self._files[name] = file_handle.read()

def create(
self,
certificate: str,
key: str,
wwdr_certificate: str,
password: Optional[str] = False,
file_name: Optional[str] = None,
filemode: bool = True,
):
"""
Create .pkass file
"""
pass_json = self._create_pass_json()
manifest = self._create_manifest(pass_json)
signature = self._create_signature(
manifest, certificate, key, wwdr_certificate, password, filemode
)
if not file_name:
file_name = BytesIO()
pkpass_file = self._create_zip(
pass_json, manifest, signature, file_name=file_name
)
return pkpass_file

def _create_pass_json(self):
"""
Create Json Pass Files
"""
return json.dumps(self, default=pass_handler).encode("utf-8")

def _create_manifest(self, pass_json: bytes):
"""
Creates the hashes for the files and adds them
into a json string
"""
self._hashes["pass.json"] = hashlib.sha1(pass_json).hexdigest()
for filename, filedata in self._files.items():
self._hashes[filename] = hashlib.sha1(filedata).hexdigest()
return json.dumps(self._hashes).encode("utf-8")

def _create_signature(
self,
manifest: bytes,
certificate: str,
key: str,
wwdr_certificate: str,
password: str,
filemode: bool,
) -> bytes:
"""Create and Save Signature"""
if not filemode:
# Use Tempfile
cert_file = tempfile.NamedTemporaryFile(mode="w")
cert_file.write(certificate)
cert_file.flush()
key_file = tempfile.NamedTemporaryFile(mode="w")
key_file.write(key)
key_file.flush()
wwdr_file = tempfile.NamedTemporaryFile(mode="w")
wwdr_file.write(wwdr_certificate)
wwdr_file.flush()
certificate = cert_file.name
key = key_file.name
wwdr_certificate = wwdr_file.name

openssl_cmd = [
"openssl",
"smime",
"-binary",
"-sign",
"-certfile",
wwdr_certificate,
"-signer",
certificate,
"-inkey",
key,
"-outform",
"DER",
"-passin",
f"pass:{password}",
]

process = subprocess.Popen(
openssl_cmd,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
)
process.stdin.write(manifest)
out_data, error = process.communicate()
if process.returncode != 0:
raise Exception(error)

return out_data

def _create_zip(
self,
pass_json: bytes,
manifest: bytes,
signature: bytes,
file_name: Union[BytesIO, str],
) -> Union[BytesIO, str]:
"""
Creats .pkass ZIP Archive
"""
z_file = zipfile.ZipFile(file_name or "pass.pkpass", "w")
z_file.writestr("signature", signature)
z_file.writestr("manifest.json", manifest)
z_file.writestr("pass.json", pass_json)
for filename, filedata in self._files.items():
z_file.writestr(filename, filedata)
z_file.close()
return file_name

def json_dict(self) -> dict:
"""
Return Pass as JSON Dict
"""
simple_fields = [
"description",
"formatVersion",
"organizationName",
"passTypeIdentifier",
"serialNumber",
"teamIdentifier",
"suppressStripShine" "relevantDate",
"backgroundColor",
"foregroundColor",
"labelColor",
"logoText",
"ibeacons",
"userInfo",
"voided",
"associatedStoreIdentifiers",
"appLaunchURL",
"exprirationDate",
"webServiceURL",
"authenticationToken",
]
data = {}
data[self.passInformation.jsonname] = self.passInformation.json_dict()
for field in simple_fields:
if hasattr(self, field):
content = getattr(self, field)
if content:
data[field] = content

if self.barcodes:
data["barcodes"] = []
for barcode in self.barcodes:
data["barcodes"].append(barcode.json_dict())

if self.locations:
data["locations"] = []
for location in self.locations:
data["locations"].append(location.json_dict())
if len(data["locations"]) > 10:
raise PassParameterException("Field locations has<10 entries")

if self.ibeacons:
data["ibeacons"] = []
for ibeacon in self.ibeacons:
data["ibeacons"].append(ibeacon.json_dict())

requied_fields = [
"description",
"formatVersion",
"organizationName",
"organizationName",
"serialNumber",
"teamIdentifier",
]
for field in requied_fields:
if field not in data:
raise PassParameterException(f"Field {field} missing")
return data
77 changes: 77 additions & 0 deletions wallet/PassInformation/PassInformation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from wallet.PassProps import Field
from wallet.Schemas import FieldProps


class PassInformation:
"""
Basic Fields for Wallet Pass
"""

def __init__(self):
self.headerFields = []
self.primaryFields = []
self.secondaryFields = []
self.backFields = []
self.auxiliaryFields = []

def add_header_field(self, field_props: FieldProps):
"""
Add Simple Field to Header
:param key:
:param value:
:param label: optional
"""
self.headerFields.append(Field(field_props))

def add_primary_field(self, field_props: FieldProps):
"""
Add Simple Primary Field
:param key:
:param value:
:param label: optional
"""
self.primaryFields.append(Field(field_props))

def add_secondary_field(self, field_props: FieldProps):
"""
Add Simple Secondary Field
:param key:
:param value:
:param label: optional
"""
self.secondaryFields.append(Field(field_props))

def add_back_field(self, field_props: FieldProps):
"""
Add Simple Back Field
:param key:
:param value:
:param label: optional
"""
self.backFields.append(Field(field_props))

def add_auxiliary_field(self, field_props: FieldProps):
"""
Add Simple Auxilary Field
:param key:
:param value:
:param label: optional
"""
self.auxiliaryFields.append(Field(field_props))

def json_dict(self):
"""
Create Json object of all Fields
"""
data = {}
for what in [
"headerFields",
"primaryFields",
"secondaryFields",
"backFields",
"auxiliaryFields",
]:
if hasattr(self, what):
field_data = [f.json_dict() for f in getattr(self, what)]
data.update({what: field_data})
return data
1 change: 1 addition & 0 deletions wallet/PassInformation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .PassInformation import PassInformation
8 changes: 8 additions & 0 deletions wallet/PassProps/Alignment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Alignment:
"""Text Alignment"""

LEFT = "PKTextAlignmentLeft"
CENTER = "PKTextAlignmentCenter"
RIGHT = "PKTextAlignmentRight"
JUSTIFIED = "PKTextAlignmentJustified"
NATURAL = "PKTextAlignmentNatural"
32 changes: 32 additions & 0 deletions wallet/PassProps/Barcode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class BarcodeFormat:
"""Barcode Format"""

PDF417 = "PKBarcodeFormatPDF417"
QR = "PKBarcodeFormatQR"
AZTEC = "PKBarcodeFormatAztec"


class Barcode:
"""
Barcode Field
"""

def __init__(self, message: str, qr_format=BarcodeFormat.QR, alt_text=''):
"""
Initiate Field
:param message: Message or Payload for Barcdoe
:param format: pdf417/ qr/ aztec
:param encoding: Default utf-8
:param alt_text: Optional Text displayed near the barcode
"""
self.format = qr_format
self.message = message # Required. Message or payload to be displayed
self.messageEncoding = (
"iso-8859-1" # Required. Text encoding
)
self.altText = alt_text # Optional. Text displayed near the barcode

def json_dict(self):
"""Return dict object from class"""
return self.__dict__
8 changes: 8 additions & 0 deletions wallet/PassProps/DateStyle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class DateStyle:
"""Date Style"""

NONE = "PKDateStyleNone"
SHORT = "PKDateStyleShort"
MEDIUM = "PKDateStyleMedium"
LONG = "PKDateStyleLong"
FULL = "PKDateStyleFull"
123 changes: 123 additions & 0 deletions wallet/PassProps/Field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
from .DateStyle import DateStyle
from .NumberStyle import NumberStyle
from wallet.Schemas import FieldProps


class Field:
"""Wallet Text Field"""

def __init__(
self,
feild_props: FieldProps
) -> None:
"""
Initiate Field
:param key: The key must be unique within the scope
:param value: Value of the Field
:param attributed_value: Optional. Attributed value of the field.
:param label: Optional Label Text for field
:param change_message: Optional. update message
:param text_alignment: left/ center/ right, justified, natural
:return: Nothing
"""
self.key = feild_props.key
self.value = feild_props.value
self.label = feild_props.label
self.attributedValue = feild_props.attributed_value
if feild_props.change_message:
self.changeMessage = feild_props.change_message
self.textAlignment = feild_props.text_alignment

def json_dict(self):
"""Return dict object from class"""
return self.__dict__


class DateField(Field):
"""Wallet Date Field"""

def __init__(self, **kwargs):
"""
Initiate Field
:param key: The key must be unique within the scope
:param value: Value of the Field
:param label: Optional Label Text for field
:param change_message: Optional. Supdate message
:param text_alignment: left/ center/ right, justified, natural
:param date_style: none/short/medium/long/full
:param time_style: none/short/medium/long/full
:param is_relativ: True/False
"""

super(DateField, self).__init__(**kwargs)
styles = {
"none": DateStyle.NONE,
"short": DateStyle.SHORT,
"medium": DateStyle.MEDIUM,
"long": DateStyle.LONG,
"full": DateStyle.FULL,
}

self.dateStyle = styles.get(kwargs.get("date_style", "short"))
self.timeStyle = styles.get(kwargs.get("time_style", "short"))
self.isRelative = kwargs.get("is_relativ", False)

def json_dict(self):
"""Return dict object from class"""
return self.__dict__


class NumberField(Field):
"""Number Field"""

def __init__(self, **kwargs):
"""
Initiate Field
:param key: The key must be unique within the scope
:param value: Value of the Field
:param label: Optional Label Text for field
:param change_message: Optional. update message
:param text_alignment: left/ center/ right, justified, natural
:param number_style: decimal/percent/scientific/spellout.
"""

super(NumberField, self).__init__(**kwargs)
self.numberStyle = {
"decimal": NumberStyle.DECIMAL,
"percent": NumberStyle.PERCENT,
"scientific": NumberStyle.SCIENTIFIC,
"spellout": NumberStyle.SPELLOUT,
}.get(kwargs.get("number_style", "decimal"))
self.value = float(self.value)

def json_dict(self):
"""Return dict object from class"""
return self.__dict__


class CurrencyField(Field):
"""Currency Field"""

def __init__(self, **kwargs):
"""
Initiate Field
:param key: The key must be unique within the scope
:param value: Value of the Field
:param label: Optional Label Text for field
:param change_message: Optional. update message
:param text_alignment: left/ center/ right, justified, natural
:param currency_code: ISO 4217 currency Code
"""

super(CurrencyField, self).__init__(**kwargs)
self.currencyCode = kwargs["currency_code"]
self.value = float(self.value)

def json_dict(self):
"""Return dict object from class"""
return self.__dict__
21 changes: 21 additions & 0 deletions wallet/PassProps/IBeacon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class IBeacon:
"""iBeacon"""

def __init__(self, **kwargs):
"""
Create Beacon
:param proximity_uuid:
:param major:
:param minor:
:param relevant_text: Option Text shown when near the ibeacon
"""

self.proximityUUID = kwargs["proximity_uuid"]
self.major = kwargs["major"]
self.minor = kwargs["minor"]

self.relevantText = kwargs.get("relevant_text", "")

def json_dict(self):
"""Return dict object from class"""
return self.__dict__
30 changes: 30 additions & 0 deletions wallet/PassProps/Location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class Location:
"""
Pass Location Object
"""

def __init__(self, **kwargs):
"""
Fill Location Object.
:param latitude: Latitude Float
:param longitude: Longitude Float
:param altitude: optional
:param distance: optional
:param relevant_text: optional
:return: Nothing
"""

for name in ["latitude", "longitude", "altitude"]:
try:
setattr(self, name, float(kwargs[name]))
except (ValueError, TypeError, KeyError):
setattr(self, name, 0.0)
if "distance" in kwargs:
self.distance = kwargs["distance"]
self.relevantText = kwargs.get("relevant_text", "")

def json_dict(self):
"""Return dict object from class"""
return self.__dict__
11 changes: 11 additions & 0 deletions wallet/PassProps/NFC.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class NFC:
def __init__(
self, encryption_public_key, message, requires_authentication=False
) -> None:
self.encryptionPublicKey = encryption_public_key
self.message = message
self.requiresAuthentication = requires_authentication

def json_dict(self):
"""Return dict object from class"""
return self.__dict__
7 changes: 7 additions & 0 deletions wallet/PassProps/NumberStyle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class NumberStyle:
"""Number Style"""

DECIMAL = "PKNumberStyleDecimal"
PERCENT = "PKNumberStylePercent"
SCIENTIFIC = "PKNumberStyleScientific"
SPELLOUT = "PKNumberStyleSpellOut"
8 changes: 8 additions & 0 deletions wallet/PassProps/TransitType.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class TransitType:
"""Transit Type for Boarding Passes"""

AIR = "PKTransitTypeAir"
TRAIN = "PKTransitTypeTrain"
BUS = "PKTransitTypeBus"
BOAT = "PKTransitTypeBoat"
GENERIC = "PKTransitTypeGeneric"
8 changes: 8 additions & 0 deletions wallet/PassProps/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .Alignment import Alignment
from .Barcode import Barcode
from .DateStyle import DateStyle
from .Field import Field
from .IBeacon import IBeacon
from .Location import Location
from .NumberStyle import NumberStyle
from .TransitType import TransitType
16 changes: 16 additions & 0 deletions wallet/PassStyles/BoardingPass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from wallet.PassProps import TransitType
from wallet.PassInformation import PassInformation


class BoardingPass(PassInformation):
"""Wallet Boarding Pass"""

def __init__(self, transitType=TransitType.AIR):
super(BoardingPass, self).__init__()
self.transitType = transitType
self.jsonname = "boardingPass"

def json_dict(self):
data = super(BoardingPass, self).json_dict()
data.update({"transitType": self.transitType})
return data
9 changes: 9 additions & 0 deletions wallet/PassStyles/Coupon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from wallet.PassInformation import PassInformation


class Coupon(PassInformation):
"""Wallet Coupon Pass"""

def __init__(self):
super(Coupon, self).__init__()
self.jsonname = "coupon"
9 changes: 9 additions & 0 deletions wallet/PassStyles/EventTicket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from wallet.PassInformation import PassInformation


class EventTicket(PassInformation):
"""Wallet Event Ticket"""

def __init__(self):
super(EventTicket, self).__init__()
self.jsonname = "eventTicket"
9 changes: 9 additions & 0 deletions wallet/PassStyles/Generic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from wallet.PassInformation import PassInformation


class Generic(PassInformation):
"""Wallet Generic Pass"""

def __init__(self):
super(Generic, self).__init__()
self.jsonname = "generic"
9 changes: 9 additions & 0 deletions wallet/PassStyles/StoreCard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from wallet.PassInformation import PassInformation


class StoreCard(PassInformation):
"""Wallet Store Card"""

def __init__(self):
super(StoreCard, self).__init__()
self.jsonname = "storeCard"
5 changes: 5 additions & 0 deletions wallet/PassStyles/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .BoardingPass import BoardingPass
from .Coupon import Coupon
from .EventTicket import EventTicket
from .Generic import Generic
from .StoreCard import StoreCard
15 changes: 15 additions & 0 deletions wallet/Schemas/FieldProps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from pydantic import BaseModel
from wallet.PassProps.Alignment import Alignment
from typing import Optional, Union


class FieldProps(BaseModel):
key: str
value: str
label: Optional[str] = None
attributed_value: Optional[str] = None
change_message: Optional[str] = None
text_alignment: Optional[Union[Alignment, str]] = Alignment.LEFT

class Config:
arbitrary_types_allowed = True
Empty file added wallet/Schemas/__init__.py
Empty file.
1 change: 1 addition & 0 deletions wallet/exceptions.py
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
Wallet Exceptions
"""


class PassParameterException(Exception):
"""
Parameter based Exception
682 changes: 0 additions & 682 deletions wallet/models.py

This file was deleted.

Binary file added wallet/test/test_assets/_sea.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added wallet/test/test_assets/_shark-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 81 additions & 0 deletions wallet/test/test_pass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from wallet.PassStyles.StoreCard import StoreCard
from wallet.Pass import Pass
from pytest import raises

card = StoreCard()

optional_pass_info = {
"logo_text": "Sharks",
"description": "some discription",
"background_color": "rgb(38, 93, 205)",
"foreground_color": "rgb(255, 255, 255)",
"label_color": "rgb(189, 189, 189)",
"serial_number": "12345"
}

pass_file = Pass(
card,
"pass_type_identifier",
"team_identifier",
"organization_name",
**optional_pass_info
)

pass_file_as_json = b'{"storeCard": {"headerFields": [], "primaryFields": [], "secondaryFields": [], "backFields": [], "auxiliaryFields": []}, "description": "some discription", "formatVersion": 1, "organizationName": "organization_name", "passTypeIdentifier": "pass_type_identifier", "serialNumber": "12345", "teamIdentifier": "team_identifier", "backgroundColor": "rgb(38, 93, 205)", "foregroundColor": "rgb(255, 255, 255)", "labelColor": "rgb(189, 189, 189)", "logoText": "Sharks"}'
pass_file_manifest = b'{"pass.json": "3642041e506fd6a623a0bb00eb4fb8584e0264f9", "icon.png": "f6d49b2c2c03d2ef82e4d11841b60b58c7f18979", "logo.png": "f6d49b2c2c03d2ef82e4d11841b60b58c7f18979", "strip.png": "bf930d6ba371b42a461655c4b9719398e2068702"}'
shark_icon = "wallet/test/test_assets/_shark-icon.png"
sea_img = "wallet/test/test_assets/_sea.jpg"


def test_add_files_to_pass():

pass_file.add_file("icon.png", open(shark_icon, "rb"))
pass_file.add_file("logo.png", open(shark_icon, "rb"))

pass_file.add_file("strip.png", open(sea_img, "rb"))

files = [file for file in pass_file._files.keys()]

assert files[0] == "icon.png"
assert files[1] == "logo.png"
assert files[2] == "strip.png"


def test_add_bad_files_to_pass():
with raises(FileNotFoundError):
pass_file.add_file("icon.png", open("wallet/test/logo.png", "rb"))
with raises(FileNotFoundError):
pass_file.add_file("icon.png", open("", "rb"))


def test_create_pass_json():
pass_as_json = pass_file._create_pass_json()
assert pass_as_json == pass_file_as_json


def test_create_manifest():
pass_manifest = pass_file._create_manifest(pass_file_as_json)
assert pass_manifest == pass_file_manifest


def test_json_dict():
json_pass = pass_file.json_dict()
assert json_pass == {
"storeCard": {
"headerFields": [],
"primaryFields": [],
"secondaryFields": [],
"backFields": [],
"auxiliaryFields": [],
},
"description": "some discription",
"formatVersion": 1,
"organizationName": "organization_name",
"passTypeIdentifier": "pass_type_identifier",
"serialNumber": "12345",
"teamIdentifier": "team_identifier",
"backgroundColor": "rgb(38, 93, 205)",
"foregroundColor": "rgb(255, 255, 255)",
"labelColor": "rgb(189, 189, 189)",
"logoText": "Sharks",
}
2 changes: 2 additions & 0 deletions wallet/test/test_wallet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_dummy():
assert 1 == 1
Empty file added wallet/utils/__init__.py
Empty file.
Empty file added wallet/utils/helpers.py
Empty file.