Metadata-Version: 2.5
Name: kloship-utils
Version: 1.0.0
Summary: Helper library for KloShip.
Project-URL: Homepage, https://www.klocel.com
Author-email: Guillaume Candau <guillaume.candau@klocel.com>
License-Expression: MIT
License-File: LICENSE
Keywords: aws-lambda,edi,klocel,kloship,shipping,zpl
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: jinja2>=3.1
Requires-Dist: requests>=2.31
Requires-Dist: unidecode>=1.3
Provides-Extra: all
Requires-Dist: boto3>=1.28; extra == 'all'
Requires-Dist: paramiko>=3.4; extra == 'all'
Provides-Extra: aws
Requires-Dist: boto3>=1.28; extra == 'aws'
Provides-Extra: dev
Requires-Dist: boto3-stubs[dynamodb,s3]>=1.28; extra == 'dev'
Requires-Dist: boto3>=1.28; extra == 'dev'
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: moto[dynamodb,s3]>=5.0; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: paramiko>=3.4; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: responses>=0.25; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: twine>=5.1; extra == 'dev'
Requires-Dist: types-paramiko>=3.4; extra == 'dev'
Requires-Dist: types-requests>=2.31; extra == 'dev'
Provides-Extra: sftp
Requires-Dist: paramiko>=3.4; extra == 'sftp'
Description-Content-Type: text/markdown

# kloship-utils

`kloship-utils` is a helper library for [KloShip](https://www.klocel.com).

## Installation

Requires Python 3.11 or later.

```bash
pip install kloship-utils            # core: encoding, units, text, templates, xml, handle units, CELLA, FTP
pip install "kloship-utils[sftp]"    # + SFTP uploads (paramiko)
pip install "kloship-utils[aws]"     # + boto3, for the S3 / DynamoDB helpers outside AWS Lambda
```

`boto3` is not a mandatory dependency: the AWS Lambda Python runtime already provides it.

### In an AWS Lambda layer

Add the package to the layer `requirements.txt`, pinned to an exact version:

```text
kloship-utils[sftp]==0.1.0
```

Every dependency ships `aarch64` wheels, so the package works with `arm64` Lambdas.

## Modules

| Module | Content |
|---|---|
| `kloship_utils.encoding` | `to_base64_gzip_json`, `from_base64_gzip_json`: payloads exchanged between Step Functions and Lambdas |
| `kloship_utils.helpers` | `to_list`, `empty_if_none`, `chunks`, `get_path` |
| `kloship_utils.units` | `convert_SI` (length, volume, weight, liquid, dates), `convert_date`, `convert_values` |
| `kloship_utils.text` | `strip_diacritics`, `strip_diacritics_in`, `replace_special_chars` |
| `kloship_utils.templates` | `render_template`, `render_zpl_label`, `split_zpl` (Jinja2) |
| `kloship_utils.xml` | `dict_to_xml`, `to_xml_string` (attributes, lists, CDATA) |
| `kloship_utils.handle_units` | `Status`, `global_group_id`, `global_group_id2`, `group_by_delivery`, `next_index_in_order`, `delete_attachments_from_result`, `clear_fields` |
| `kloship_utils.aws.s3` | `build_key`, `put_file`, `get_file`, `save_label`, `save_shipment_file`, file name helpers |
| `kloship_utils.aws.dynamodb` | HandleUnit lookups, `increment_counter`, `get_parameter`, `get_carrier_account_config`, `get_conversion`, `get_packaging_conversion`, `execute_statement_all`, `batch_execute` |
| `kloship_utils.cella` | `CellaClient`, `get_rule_result`, `get_rule_result_from_kloship_front`, `pdf_to_zpl` |
| `kloship_utils.transfers` | `TransferConfig`, `TunnelConfig`, `upload_ftp`, `upload_sftp` |
| `kloship_utils.exceptions` | `KloshipUtilsError` and its subclasses |

### Encoding

```python
from kloship_utils.encoding import from_base64_gzip_json, to_base64_gzip_json

def lambda_handler(event, context):
    handle_units = from_base64_gzip_json(event["compressed_handle_units"])
    ...
    return {"compressed_handle_units": to_base64_gzip_json(enriched_handle_units)}
```

Pass `default=str` to `to_base64_gzip_json` to serialize the `Decimal` values returned by DynamoDB.

### Units and dates

```python
from kloship_utils.units import convert_SI, convert_date

convert_SI(hu["WEIGHT"]["#text"], "weight", hu["WEIGHT"]["@format"], "kg")           # "10.05" (2 decimals)
convert_SI("1234", "length", "mm", "cm", number_decimal=0)                         # "123"
convert_SI("1234", "length", "mm", "m", number_decimal=None)                       # "1.234" (no rounding)
convert_SI("2024/01/31", "date", "YYYY/MM/DD", "%d.%m.%Y")                          # "31.01.2024"
convert_date("2024-01-31 08:05:00", "YYYY-MM-DD HH:MI:SS", "CCYYMMDDHHMM")          # "202401310805"
```

Date formats accept KloShip tokens (`yyyy`, `ccyy`, `yy`, `mm`, `dd`, `hh`, `mi`/`ii`, `ss`; `mm` after `hh` means
minutes) or `strftime` patterns.

### Labels

```python
from kloship_utils.templates import render_zpl_label, split_zpl
from kloship_utils.aws.s3 import save_label

zpl = render_zpl_label(handle_unit, config, "layout_label.zpl")   # templates/ directory of the Lambda
handle_unit["label"] = split_zpl(zpl)                             # {"header", "body", "footer"}
handle_unit["KLOSHIP_LABEL_FILENAME"] = save_label(
    handle_unit, account_id, config, zpl, extension="zpl", content_type="x-application/zpl", carrier="ACME"
)
```

### S3

```python
from kloship_utils.aws.s3 import save_shipment_file, shipment_file_name

file_name = shipment_file_name(config, "edi")             # 20240131_080509_42.edi
save_shipment_file(
    edi, account_id, config, file_name,
    content_type="application/EDI-EDIFACT", carrier="ACME",
    encoding="ISO-8859-1", content_encoding="ISO-8859-1",
)
```

Keys follow the KloShip layout `{account}/{@senderCodeTDM}/{@serviceTagTDM}/{YYYY}/{MM}/{DD}/{file}`, in the
`STANDARD_IA` storage class. Buckets default to the `LABELS_BUCKET` / `SHIPMENT_BUCKET` environment variables.

### DynamoDB

```python
from kloship_utils.aws import dynamodb

handle_units = dynamodb.get_handle_units_by_global_group_id(account_id, config)       # PRE-SHIPPED / SHIPPED HUs
number = dynamodb.increment_counter(config, parameter_name=config.get("ParameterNameHUCounter"))
packaging = dynamodb.get_packaging_conversion(hu["PACKAGING"], config, default=config.get("DefaultPackaging"))
```

Table names default to the standard environment variables (`HANDLE_UNIT_TABLE_NAME`,
`CARRIER_ACCOUNT_CONFIG_TABLE_NAME`, `PARAMETERS_TABLE_NAME`, `CONVERSIONS_TABLE_NAME`) and can be given explicitly.
All PartiQL statements are parameterized.

### CELLA rules

```python
from kloship_utils.cella import get_rule_result

values = get_rule_result(
    config["CreateLabel"]["FrontendConversion"],     # Url, WarehouseWorkerUsername, WarehouseWorkerPwd, WarehouseId, StockOwnerId
    rule_config,                                     # Name + Exit [{name, type, defaultValue}]
    {"packagingDescription": hu["PACKAGING"]},
)
```

### FTP / SFTP

```python
from kloship_utils.transfers import TransferConfig, TunnelConfig, upload_sftp

sftp = TransferConfig.from_carrier_config(config["Shipment"]["Sftp"])
tunnel = TunnelConfig.from_carrier_config(config["Shipment"]["TunnelSSH"], password_base64=True)
upload_sftp(sftp, file_name, edi, tunnel=tunnel)      # raises TransferError (with .code 401/402/404) on failure
```

`TransferConfig.from_carrier_config` reads the usual configuration keys whatever their case (`Host`, `Port`, `Login`,
`Password`, `RemotePath`, `EDIFileEncoding`, `UseCnOptsHostkeys`, `ParamikoKeyData`, `ParamikoSignVerifySSHData`).

## Versioning

The package follows [Semantic Versioning](https://semver.org). Pin an exact version in your requirements.

## License

MIT License, © Groupe Klocel.
