sanmar_sdk.ftp.client¶
SanMar’s SFTP server.
SanMar serves its data files over SFTP at ftp.sanmar.com, port 2200. The username is the
SanMar customer number, and the password is the FTP password SanMar issues during
onboarding, which is not the SanMar.com password the web services use.
The server’s host key is checked like any SSH server’s. Either have it in a known_hosts
file, or pin it: get it once with ssh-keyscan -p 2200 ftp.sanmar.com, check it, and pass
the key (ssh-rsa AAAA...) as host_key. An unknown key is refused.
Everything is read as a stream: a product file is parsed row by row as it downloads, and never held in memory or written to disk.
Attributes¶
Where SanMar keeps the product, inventory and pricing files, refreshed nightly by 6 AM |
|
Where the product information web service writes the files it is asked for. |
|
Where Holding files appear, and where SanMar moves them once an order is processed. |
Classes¶
A connection to SanMar's SFTP server. |
Module Contents¶
- sanmar_sdk.ftp.client.PRODUCT_FOLDER = 'SanMarPDD'[source]¶
Where SanMar keeps the product, inventory and pricing files, refreshed nightly by 6 AM Pacific.
- sanmar_sdk.ftp.client.PRODUCT_INFORMATION_FOLDER = 'SanMarPDD/SanMarPI'[source]¶
Where the product information web service writes the files it is asked for.
- sanmar_sdk.ftp.client.ACKNOWLEDGEMENT_FOLDERS = ('Holding', 'Done')[source]¶
Where Holding files appear, and where SanMar moves them once an order is processed.
- class sanmar_sdk.ftp.client.SanMarFTP(customer_number: int | str, password: str, *, host: str = HOST, port: int = PORT, host_key: str | None = None, known_hosts: pathlib.Path | None = None, timeout: float = 30.0)[source]¶
A connection to SanMar’s SFTP server.
Use it as a context manager, and read files inside the
withblock:with SanMarFTP(123456, "ftp-password", host_key="ssh-rsa AAAA...") as ftp: with ftp.catalog() as products: for product in products: ...
- Parameters:
customer_number – The SanMar customer number, which is the SFTP username.
password – The FTP password SanMar issued. SanMar.com logins do not work here.
host – SanMar’s SFTP server.
port – SanMar’s SFTP server.
host_key – The server’s public key, to trust it without a known_hosts entry.
known_hosts – A known_hosts file to trust instead of the system’s.
timeout – Seconds to wait for the connection.
- __exit__(exc_type: type[BaseException] | None, exc: BaseException | None, traceback: types.TracebackType | None) None[source]¶
Disconnect.
- property sftp: paramiko.sftp_client.SFTPClient[source]¶
The underlying paramiko SFTP session, for anything this class does not cover.
- list_folder(folder: str = '') list[str][source]¶
List a folder’s entries by name, sorted. Folder names match case-insensitively.
- find(folder: str, pattern: str) list[str][source]¶
List the files in a folder whose names match a shell-style pattern, ignoring case.
Names come back sorted by name. SanMar dates the brand and category files it writes on request as
MM-DD-YYYY, which does not sort by date;newest()does:ftp.find("SanMarPDD/SanMarPI", "Brand_OGIO_*.csv")
- newest(folder: str, pattern: str) str[source]¶
Find the most recently dated file in a folder whose name matches a pattern.
Files are compared by the
MM-DD-YYYYdate SanMar puts in the names of the brand and category files it writes on request, then by name:ftp.newest("SanMarPDD/SanMarPI", "Brand_OGIO_*.csv")
- Raises:
NotFoundError – If no file matches.
- resolve(path: str) str[source]¶
Find a path on the server, matching each part of it case-insensitively.
SanMar’s guides spell the same file
SanMar_SDL_N.csvandSanmar_SDL_N.csv. An exact match wins over a case-insensitive one.- Raises:
NotFoundError – If some part of the path does not exist; the error lists what does.
- open(path: str, *, prefetch: bool = True) collections.abc.Iterator[IO[bytes]][source]¶
Open a remote file for reading, as bytes.
prefetchrequests the whole file ahead of reading, which is much faster for reading straight through; turn it off for random access.
- open_text(path: str, *, member: str | None = None, encoding: str = 'utf-8-sig') collections.abc.Iterator[TextIO][source]¶
Open a remote text file, or a text file inside a remote zip archive.
For a
.zippath,membernames the file inside it; it may be left out when the archive holds a single.csvor.txtfile.
- download(path: str, destination: pathlib.Path) pathlib.Path[source]¶
Copy a remote file to
destination(a file, or a folder to put it in).
- stream[R](path: str, reader: collections.abc.Callable[[TextIO], collections.abc.Iterator[R]], *, member: str | None = None, encoding: str = 'utf-8-sig') collections.abc.Iterator[collections.abc.Iterator[R]][source]¶
Stream a remote file through one of this package’s readers.
The rows are read as the file downloads, and only while the
withblock is open:with ftp.stream("SanMarPDD/sanmar_saleItems.txt", read_sale_items) as items: for item in items: ...
- catalog() contextlib.AbstractContextManager[collections.abc.Iterator[sanmar_sdk.ftp.catalog.CatalogProduct]][source]¶
Stream
SanMar_SDL_N.csv, SanMar’s main product file.
- extended_catalog() contextlib.AbstractContextManager[collections.abc.Iterator[sanmar_sdk.ftp.catalog.CatalogProduct]][source]¶
Stream
SanMar_EPDD.csv: the product file, with stock across all warehouses.
- warehouse_inventory() contextlib.AbstractContextManager[collections.abc.Iterator[sanmar_sdk.ftp.inventory.WarehouseInventory]][source]¶
Stream
sanmar_dip.txt: stock by warehouse, with current prices, refreshed hourly.
- product_information(file_name: str) contextlib.AbstractContextManager[collections.abc.Iterator[sanmar_sdk.ftp.catalog.ProductInformation]][source]¶
Stream a file the product information web service wrote to
SanMarPDD/SanMarPI.
- upload_orders(batch: str, orders: collections.abc.Sequence[sanmar_sdk.orders.PurchaseOrder]) sanmar_sdk.ftp.orders.OrderFiles[source]¶
Upload a batch of orders, without releasing them for processing.
SanMar acknowledges the batch with a Holding file (see
holding()) and holds the orders untilrelease_orders()releases them, for up to two weeks. These are production orders; SanMar has no test environment for SFTP ordering.
- release_orders(batch: str, po_numbers: collections.abc.Sequence[str], *, release_number: int = 1, delay: float = 5.0) str[source]¶
Release some or all of an uploaded batch’s orders for processing.
Each release of the same batch needs the next
release_number. SanMar asks for a pause of several seconds between uploading a batch and releasing it;delaywaits that long first. Returns the release file’s name.
- holding(batch: str) collections.abc.Iterator[collections.abc.Iterator[sanmar_sdk.ftp.orders.HoldingLine]][source]¶
Stream SanMar’s acknowledgement of an uploaded batch, once it exists.
SanMar writes it to
Holdingwithin about 15 minutes of the upload, and moves it toDoneonce the orders are processed. The file is the one whose name is the batch name followed byHolding, so batch06-07-2022-1never reads06-07-2022-10Holding.txt.- Raises:
NotFoundError – If there is no Holding file for the batch yet.