This release is a pre-release and may not be stable for production use.
libciss
Python client for CISS applications. Currently supports CISS Control, with helpers for authentication and pré-carga / separação (warehouse pre-load picking).
Status:
0.1.0a3(alpha) — the API surface may change.
Requirements
- Python 3.10+ (uses
X | Nonetype syntax anddataclass(slots=True)) requests>=2.31.0
Installation
pip install libciss
Everything is imported from the single top-level libciss package:
from libciss import CISSClient
Upgrading from
0.1.0a1: that release was unusable — it shipped its modules under a top-levelservicespackage (which collided with any application that had its ownservices/), leftclientandexceptionsout of the distribution entirely, and imported from asrcpackage that was never packaged.0.1.0a2moves everything underlibciss, sofrom src import ...becomesfrom libciss import ....
Quick start
from datetime import datetime
from libciss import CISSClient
from libciss.services.cisscontrol.models import PreCargaSeparacaoSubmission
client = CISSClient("https://your-ciss-host.example.com")
# Authenticate — stores the bearer token on the shared session
user = client.cisscontrol.auth.login("username", "password")
print(user.id_empresa, user.razao_social)
# List pre-loads
preloads = client.cisscontrol.precarga_separacao.get_all(
id_empresa=user.id_empresa,
num_dias_sync=30,
)
# Fetch the line items of one pre-load
items = client.cisscontrol.precarga_separacao.items(
id_empresa=user.id_empresa,
id_precarga=preloads[0].id_pre_carga,
id_usuario=user.id_user,
)
# Report a separated item back to CISS
item = items[0]
submission = PreCargaSeparacaoSubmission(
id_usuario=user.id_user,
id_empresa=item.id_empresa,
id_documento=item.id_documento,
id_pre_carga=item.id_pre_carga,
id_produto=item.id_produto,
id_subproduto=item.id_subproduto,
id_local_retirada=item.id_local_retirada,
ds_local_retirada=item.ds_local_retirada,
num_sequencia=item.num_sequencia,
qtd_produto=item.qtd_produto,
fg_separado="S",
qtd_contada=item.qtd_produto,
dt_hr_inicio=datetime.now(),
)
response = client.cisscontrol.precarga_separacao.submit([submission])
client.cisscontrol.auth.logout()
Architecture
One CISSClient owns a single requests.Session. Services never hold their own transport — they call back into client.request(...), so the token set at login is automatically applied to every later call.
CISSClient(base_url)
├── session # requests.Session (shared headers / cookies)
├── request(...) # low-level HTTP helper, raises on 4xx/5xx
└── cisscontrol # CISSControl
├── auth # AuthenticationService
│ ├── login / logout
│ ├── token / authenticated
│ └── user → LoginResponse | None
└── precarga_separacao # PreCargaSeparacaoService
├── get_all(...) → list[PreCargaSeparacao]
├── items(...) → list[PreCargaSeparacaoItem]
└── submit(...) → requests.Response
API reference
CISSClient
Entry point for all services.
CISSClient(base_url: str)
| Attribute / method | Description |
|---|---|
base_url |
API root URL; a trailing / is stripped |
session |
Shared requests.Session — holds the Authorization header after login |
cisscontrol |
CISSControl service namespace |
request(method, endpoint, **kwargs) |
Sends method to base_url + endpoint, calls raise_for_status(), returns the Response |
endpoint is concatenated onto base_url verbatim, so it must start with /. Any extra keyword argument is forwarded to requests.Session.request (json=, params=, timeout=, …).
Session-level configuration goes on client.session:
client.session.verify = "/path/to/ca-bundle.pem"
client.session.proxies = {"https": "http://proxy.internal:3128"}
Every request is bounded by a default (connect, read) timeout of (5.0, 30.0) seconds, exposed as libciss.client.DEFAULT_TIMEOUT. The read half is short deliberately: CISS Control tends to stall rather than answer when a company has no pre-load.
Change it per client, or per call:
client = CISSClient("https://ciss.example.com", timeout=(5, 60))
client.timeout = None # wait forever
client.request("GET", "/ciss-control/precarga_separacao", timeout=10) # this call only
A request that exceeds it raises requests.Timeout. Note that setting client.session.timeout does not work — requests.Session has no such attribute; use the constructor argument above.
Authentication — client.cisscontrol.auth
login(username, password) -> LoginResponse
POST /ciss-control/login
Sends {"username": ..., "password": ...} as JSON. On success it writes the returned tokenBearer into session.headers["Authorization"] and caches the parsed payload on auth.user.
The token is stored exactly as the API returns it — the library does not prepend a Bearer prefix. razao_social is right-stripped of trailing whitespace; all other fields are passed through unchanged.
logout() -> None
Removes the Authorization header and clears auth.user. Client-side only — no request is sent, so the token stays valid server-side until it expires.
Properties
| Property | Type | Description |
|---|---|---|
token |
str | None |
Current Authorization header value, or None |
authenticated |
bool |
True when a token is set |
user |
LoginResponse | None |
Payload of the last successful login |
authenticated only reports whether a token is present — it does not validate or check expiry. An expired token still reads as True, and the call then fails with requests.HTTPError (401).
requires_login decorator
libciss.services.cisscontrol.auth.requires_login guards service methods. It reads self.client.cisscontrol.auth.authenticated and raises NotAuthenticatedError when no token is set, so it can only decorate methods of a service that holds a client attribute.
from libciss.services.cisscontrol.auth import requires_login
class MyService:
def __init__(self, client):
self.client = client
@requires_login
def do_something(self):
return self.client.request("GET", "/ciss-control/something")
Pré-carga separação — client.cisscontrol.precarga_separacao
Every method is decorated with @requires_login and raises NotAuthenticatedError when called before login().
get_all(*, id_empresa, ...) -> list[PreCargaSeparacao]
GET /ciss-control/precarga_separacao
Keyword-only. All filter parameters are always sent, empty strings included.
| Parameter | Type | Default | API query key |
|---|---|---|---|
id_empresa |
int |
required | idEmpresa |
num_dias_sync |
int |
99 |
numDiasSync |
ds_separacao |
str |
"" |
dsSeparacao |
ds_precarga |
str |
"" |
dsPreCarga |
ds_ponto_retirada |
str |
"" |
dsPontoRetirada |
dt_inicio |
str |
"" |
dtInicio |
ds_pre_separacao |
str |
"" |
dsPreSeparacao |
dt_fim |
str |
"" |
dtFim |
Returns [] on HTTP 204 or an empty/non-array body, otherwise one PreCargaSeparacao per element of the JSON array.
Date filters (dt_inicio / dt_fim) are typed str and forwarded verbatim — the library does not format date objects for you.
A large
num_dias_synccan make this endpoint stall. It no longer hangs indefinitely — the default read timeout caps it — but a stalled call still costs you that timeout, so prefer a bounded window (e.g.num_dias_sync=30).
items(*, id_empresa, id_precarga, id_usuario, ...) -> list[PreCargaSeparacaoItem]
GET /ciss-control/precarga_separacao/itens
| Parameter | Type | Default | API query key |
|---|---|---|---|
id_empresa |
int |
required | idEmpresa |
id_precarga |
int |
required | idPreCarga |
id_usuario |
int |
required | idUsuario |
ds_separacao |
str |
"" |
dsSeparacao |
ds_precarga |
str |
"" |
dsPreCarga |
ds_ponto_retirada |
str |
"" |
dsPontoRetirada |
dt_inicio |
str |
"" |
dtInicio |
ds_pre_separacao |
str |
"" |
dsPreSeparacao |
dt_fim |
str |
"" |
dtFim |
Returns [] on HTTP 204 or an empty/non-array body, matching get_all.
submit(items) -> requests.Response
POST /ciss-control/precarga_separacao
Takes a positional list[PreCargaSeparacaoSubmission] and posts [item.to_dict() for item in items] as a JSON array. Returns the raw requests.Response — the body is not parsed, so inspect response.status_code / response.json() yourself.
Data models
Models live in libciss.services.cisscontrol.models. They are @dataclass(slots=True), use snake_case in Python, and map to the API's camelCase. Response models expose from_dict; the request model exposes to_dict.
Being slotted dataclasses, they take positional or keyword arguments, compare by value, and reject attributes that are not declared fields.
from_dict requires the keys that identify a record and tolerates the merely descriptive ones, because CISS omits those on some pre-loads and losing a description is not a reason to fail a whole batch. Each model below lists what it tolerates.
LoginResponse
Built by auth.login().
| Field | Type | API field |
|---|---|---|
token_bearer |
str |
tokenBearer |
id_user |
int |
idUser |
id_grupo |
int |
idGrupo |
username |
str |
username |
enabled |
bool |
enabled |
id_empresa |
int |
idEmpresa |
cnpj_empresa |
str |
cnpjEmpresa |
razao_social |
str |
razaoSocial (right-stripped) |
permissions |
list[str] |
permissionsList |
A missing or null razaoSocial becomes "" and a missing permissionsList becomes []. The remaining keys are required.
PreCargaSeparacao
| Field | Type | API field |
|---|---|---|
id_empresa |
int |
idEmpresa |
id_pre_carga |
int |
idPreCarga |
descricao |
str |
descricao |
dt_movimento |
date |
dtMovimento, coerced to a date |
idEmpresa, idPreCarga and dtMovimento are required — a missing one raises KeyError. A missing or null descricao becomes "", and it is right-stripped since CISS pads it to a fixed width.
dtMovimento accepts "2026-08-05", a full "2026-08-05T00:00:00" timestamp, or an already-decoded date/datetime; CISS returns all of these for the same field. Anything else raises ValueError.
PreCargaSeparacaoItem
| Field | Type | API field |
|---|---|---|
id_pre_carga |
int |
idPreCarga |
id_empresa |
int |
idEmpresa |
id_documento |
int |
idDocumento |
id_produto |
int |
idProduto |
id_subproduto |
int |
idSubproduto |
ds_local_retirada |
str |
dsLocalRetirada |
num_sequencia |
int |
numSequencia |
id_lote |
str | None |
idLote — optional, None when absent |
qtd_produto |
float |
qtdProduto |
fg_separado |
str |
fgSeparado |
qtd_contada_divergente |
bool |
qtdContadaDivergente |
id_local_retirada |
int |
idLocalRetirada |
Required: idPreCarga, idEmpresa, idDocumento, idProduto, idSubproduto, numSequencia and qtdProduto — these identify the line, so a missing one raises KeyError. The rest fall back when missing or null:
| Key | Fallback |
|---|---|
dsLocalRetirada |
"" |
idLote |
None |
fgSeparado |
"" |
qtdContadaDivergente |
False |
idLocalRetirada |
0 |
Because of that fallback, id_local_retirada == 0 means "CISS did not say", not pick-up location number zero. qtd_produto is coerced with float() and id_local_retirada with int(), since CISS sometimes sends them as strings.
PreCargaSeparacaoSubmission
Request DTO for submit(). Every field is required — there are no defaults, so all thirteen must be supplied.
| Field | Type | JSON key |
|---|---|---|
id_usuario |
int |
idUsuario |
id_empresa |
int |
idEmpresa |
id_documento |
int |
idDocumento |
id_pre_carga |
int |
idPreCarga |
id_produto |
int |
idProduto |
id_subproduto |
int |
idSubproduto |
id_local_retirada |
int |
idLocalRetirada |
ds_local_retirada |
str |
dsLocalRetirada |
num_sequencia |
int |
numSequencia |
qtd_produto |
float |
qtdProduto |
fg_separado |
str |
fgSeparado |
qtd_contada |
float |
qtdContada |
dt_hr_inicio |
datetime.datetime |
dtHrInicio, serialized with .isoformat() |
to_dict() calls .isoformat() on dt_hr_inicio, so it must be a datetime — passing a string raises AttributeError. Nothing is validated beyond that: fg_separado is sent as-is ("S" / "N"), and a qtd_contada that differs from qtd_produto is accepted and resolved server-side.
Exceptions
Exception
└── CISSClientError
└── AuthenticationError
└── NotAuthenticatedError
Defined in libciss.exceptions.
| Exception | Raised when |
|---|---|
CISSClientError |
Base class for library errors — catch this to catch them all |
AuthenticationError |
Auth-related failures |
NotAuthenticatedError |
A @requires_login method is called with no token set |
Transport and HTTP failures are not wrapped: client.request() calls raise_for_status(), so 4xx/5xx surface as requests.HTTPError, and connection/timeout problems surface as the corresponding requests.RequestException subclasses.
import requests
from libciss.exceptions import NotAuthenticatedError
try:
preloads = client.cisscontrol.precarga_separacao.get_all(id_empresa=1)
except NotAuthenticatedError:
client.cisscontrol.auth.login("user", "pass")
except requests.HTTPError as e:
print(e.response.status_code, e.response.text)
A 401 from an expired token arrives as requests.HTTPError, not NotAuthenticatedError — the local check only sees that a token exists. To recover, call login() again.
Endpoint map
| HTTP | Path | Client method |
|---|---|---|
POST |
/ciss-control/login |
auth.login |
GET |
/ciss-control/precarga_separacao |
precarga_separacao.get_all |
GET |
/ciss-control/precarga_separacao/itens |
precarga_separacao.items |
POST |
/ciss-control/precarga_separacao |
precarga_separacao.submit |
Known issues
These are current limitations of the code, not of the documentation.
| Issue | Impact |
|---|---|
get_all can stall on wide sync windows |
Bounded by the read timeout now, but a stalled call still costs you those seconds — keep num_dias_sync small |
logout() is client-side only |
The token remains valid on the server |
| Transport errors are not wrapped | Callers catch requests exceptions (HTTPError, Timeout) alongside CISSClientError |
| No test suite | Changes are unverified |
Glossary
Portuguese terms used by the CISS API, kept untranslated in the model field names.
| Term | Meaning |
|---|---|
| Empresa | Company / store (id_empresa, CNPJ, razão social) |
| Pré-carga | Pre-load batch for warehouse picking |
| Separação | Product picking / separation |
| Local de retirada | Pick-up location |
| Documento | Related document ID |
| Produto / Subproduto | Product and subproduct IDs |
| Lote | Lot / batch code (optional) |
| Qtd produto | Expected quantity on the pre-load |
| Qtd contada | Quantity actually counted during separation |
| fgSeparado | Separation flag, an API string ("S" / "N") |
| numDiasSync | Days of history to sync (default 99) |
License
MIT © Redson
Metadata
Release files for libciss 0.1.0a4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| libciss-0.1.0a4.tar.gz | 18.0 kB | Without provenance | Details for libciss-0.1.0a4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| libciss-0.1.0a4-py3-none-any.whl | Python 3 | none | any | Details for libciss-0.1.0a4-py3-none-any.whl |
Total release size: 34.7 kB
Release files / libciss-0.1.0a4.tar.gz
| Download URL | libciss-0.1.0a4.tar.gz |
|---|---|
| Size | 18.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
685b4c49ec7a22b058c60f5717805875a9eec2cea414fc2728d6e695346207eb
|
|
BLAKE2b-256 checksum How to use checksums |
8402095d5827bf6565456705b8103e877e23f85260b102ef69c7907bb60fbe76
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / libciss-0.1.0a4-py3-none-any.whl
| Download URL | libciss-0.1.0a4-py3-none-any.whl |
|---|---|
| Size | 16.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6b2d0ae08c35f98ffaecb671ff213fe723b694d7742334453d40ae244a6d1abc
|
|
BLAKE2b-256 checksum How to use checksums |
b1b2532718b22a82829b1ac41c1d5486611c67ed247cd54ebd140ef2cabf05df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Security for libciss 0.1.0a4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.