sanmar_sdk.ftp¶
SanMar’s SFTP server, and readers for the data files on it.
Every reader is a generator: rows are parsed one at a time as the file is read, so even
SanMar’s largest files never sit in memory. Readers take a path to a local file or an open
text stream, so the same code parses a file streamed by SanMarFTP or one
downloaded some other way.
Submodules¶
Classes¶
One style, color and size from |
|
One style, color and size from a SanMarPI file. |
|
The columns every SanMar product file shares. |
|
A connection to SanMar's SFTP server. |
|
One style, color and size at one warehouse. |
|
One style, color and size on sale, from |
|
One style, color and size from |
|
The columns SanMar's older product text files share. |
|
One line of SanMar's acknowledgement of an SFTP order. |
|
The two files that place a batch of orders: names and contents. |
|
One line of one box in SanMar's daily shipment status file. |
|
The account's price for one style, color and size. |
|
A changed price from |
Functions¶
|
Stream |
Stream |
|
Stream a SanMarPI bulk, delta, brand or category file, one product at a time. |
|
|
Stream |
Stream |
|
|
Stream |
|
Stream |
|
Stream |
|
Stream a Holding file, one order line at a time. |
|
Stream a daily shipment status file, one line of one box at a time. |
|
Build the |
|
Build the file that releases some or all of a batch's orders, as |
|
Stream |
|
Stream |
Package Contents¶
- class sanmar_sdk.ftp.CatalogProduct(/, **data: Any)[source]¶
Bases:
ProductRecordOne style, color and size from
SanMar_SDL_N.csvorSanMar_EPDD.csv.- quantity: int | None = None[source]¶
Stock across all warehouses, capped by SanMar. Only the EPDD file has it.
- category: str | None = None[source]¶
The category. In SDL files this holds every category, separated by semicolons.
- suggested_price: sanmar_sdk.base.Price = None[source]¶
- msrp: sanmar_sdk.base.Price = None[source]¶
- class sanmar_sdk.ftp.ProductInformation(/, **data: Any)[source]¶
Bases:
ProductRecordOne style, color and size from a SanMarPI file.
- keywords: sanmar_sdk.base.CommaSeparated = None[source]¶
- piece_sale_price: sanmar_sdk.base.Price = None[source]¶
- case_sale_price: sanmar_sdk.base.Price = None[source]¶
- sale_start_date: sanmar_sdk.base.SanMarDate | None = None[source]¶
- sale_end_date: sanmar_sdk.base.SanMarDate | None = None[source]¶
- class sanmar_sdk.ftp.ProductRecord(/, **data: Any)[source]¶
Bases:
sanmar_sdk.base.RecordThe columns every SanMar product file shares.
- unique_key: str[source]¶
SanMar’s identifier for this style, color and size, and the PromoStandards part id.
- catalog_color: str = None[source]¶
The catalog (mainframe) color, which web services and orders expect.
- status: str | None = None[source]¶
Coming Soon, New, Regular, or Discontinued. Coming Soon rows may be incomplete.
- piece_weight: decimal.Decimal | None = None[source]¶
Approximate weight per piece in pounds.
- piece_price: sanmar_sdk.base.Price = None[source]¶
The price per piece for five pieces or fewer of one style and color.
- case_price: sanmar_sdk.base.Price = None[source]¶
The price per piece when buying by the case.
- map_price: sanmar_sdk.base.Price = None[source]¶
The minimum advertised price.
- sanmar_sdk.ftp.read_catalog(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[CatalogProduct][source]¶
Stream
SanMar_SDL_N.csv(orSanMar_SDL_DI.csv), one product at a time.
- sanmar_sdk.ftp.read_extended_catalog(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[CatalogProduct][source]¶
Stream
SanMar_EPDD.csv, one product at a time, with total stock.
- sanmar_sdk.ftp.read_product_information(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[ProductInformation][source]¶
Stream a SanMarPI bulk, delta, brand or category file, one product at a time.
- class sanmar_sdk.ftp.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.
- class sanmar_sdk.ftp.WarehouseInventory(/, **data: Any)[source]¶
Bases:
sanmar_sdk.base.RecordOne style, color and size at one warehouse.
- warehouse: sanmar_sdk.common.WarehouseNumber = None[source]¶
- unique_key: str | None = None[source]¶
SanMar’s identifier for this style, color and size, and the PromoStandards part id.
- piece_weight: decimal.Decimal | None = None[source]¶
- piece_price: sanmar_sdk.base.Price = None[source]¶
- case_price: sanmar_sdk.base.Price = None[source]¶
- piece_sale_price: sanmar_sdk.base.Price = None[source]¶
- case_sale_price: sanmar_sdk.base.Price = None[source]¶
- sale_start_date: sanmar_sdk.base.SanMarDate | None = None[source]¶
- sale_end_date: sanmar_sdk.base.SanMarDate | None = None[source]¶
- sanmar_sdk.ftp.read_active_products(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[WarehouseInventory][source]¶
Stream
sanmar_activeproductsexport.txt, one row at a time.
- sanmar_sdk.ftp.read_warehouse_inventory(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[WarehouseInventory][source]¶
Stream
sanmar_dip.txt(orsanmar_closeouts_dip.txt), one row at a time.
- class sanmar_sdk.ftp.SaleItem(/, **data: Any)[source]¶
Bases:
TextFileRecordOne style, color and size on sale, from
sanmar_saleItems.txt.- piece_sale_price: sanmar_sdk.base.Price = None[source]¶
- case_sale_price: sanmar_sdk.base.Price = None[source]¶
- sale_start_date: sanmar_sdk.base.SanMarDate | None = None[source]¶
- sale_end_date: sanmar_sdk.base.SanMarDate | None = None[source]¶
- class sanmar_sdk.ftp.TextFileProduct(/, **data: Any)[source]¶
Bases:
TextFileRecordOne style, color and size from
sanmar_pdd.txtorCatalog.txt.- piece_price: sanmar_sdk.base.Price = None[source]¶
- case_price: sanmar_sdk.base.Price = None[source]¶
- class sanmar_sdk.ftp.TextFileRecord(/, **data: Any)[source]¶
Bases:
sanmar_sdk.base.RecordThe columns SanMar’s older product text files share.
- weight: decimal.Decimal | None = None[source]¶
Approximate weight per piece in pounds.
- sanmar_sdk.ftp.read_catalog_txt(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[TextFileProduct][source]¶
Stream
Catalog.txt, one product at a time.
- sanmar_sdk.ftp.read_pdd(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[TextFileProduct][source]¶
Stream
sanmar_pdd.txt, one product at a time.
- sanmar_sdk.ftp.read_sale_items(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[SaleItem][source]¶
Stream
sanmar_saleItems.txt, one sale item at a time.
- class sanmar_sdk.ftp.HoldingLine(/, **data: Any)[source]¶
Bases:
sanmar_sdk.base.RecordOne line of SanMar’s acknowledgement of an SFTP order.
- warehouse: sanmar_sdk.common.WarehouseNumber = None[source]¶
The warehouse SanMar will ship this line from.
- class sanmar_sdk.ftp.OrderFiles[source]¶
The two files that place a batch of orders: names and contents.
- class sanmar_sdk.ftp.ShipmentStatus(/, **data: Any)[source]¶
Bases:
sanmar_sdk.base.RecordOne line of one box in SanMar’s daily shipment status file.
- ship_date: sanmar_sdk.base.SanMarDate[source]¶
- sub_total: sanmar_sdk.base.Price = None[source]¶
- freight: sanmar_sdk.base.Price = None[source]¶
- handling_fee: sanmar_sdk.base.Price = None[source]¶
- invoice_total: sanmar_sdk.base.Price = None[source]¶
- sanmar_sdk.ftp.read_holding(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[HoldingLine][source]¶
Stream a Holding file, one order line at a time.
- sanmar_sdk.ftp.read_shipment_status(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[ShipmentStatus][source]¶
Stream a daily shipment status file, one line of one box at a time.
- sanmar_sdk.ftp.render_order_files(batch: str, orders: collections.abc.Sequence[sanmar_sdk.orders.PurchaseOrder]) OrderFiles[source]¶
Build the
CustInfo.txtandDetails.txtfiles for a batch of orders.- Parameters:
batch – The batch name, unique for every batch ever sent. SanMar suggests the date and a running number for the day, such as
06-07-2022-1.orders – The purchase orders in the batch. Items must be named by
SkuKey, and each order needs a ship-to email.
- sanmar_sdk.ftp.render_release_file(batch: str, po_numbers: collections.abc.Sequence[str], release_number: int = 1) tuple[str, str][source]¶
Build the file that releases some or all of a batch’s orders, as
(name, contents).Each release of a batch gets the next
release_number, starting at 1. PO numbers are checked as aPurchaseOrderchecks its own.
- class sanmar_sdk.ftp.CustomerPrice(/, **data: Any)[source]¶
Bases:
sanmar_sdk.base.RecordThe account’s price for one style, color and size.
- my_price: sanmar_sdk.base.Price[source]¶
The account’s price, which is the case or sale price where there is no special pricing.
- piece_weight: decimal.Decimal | None = None[source]¶
- piece_price: sanmar_sdk.base.Price = None[source]¶
- case_price: sanmar_sdk.base.Price = None[source]¶
- piece_sale_price: sanmar_sdk.base.Price = None[source]¶
- case_sale_price: sanmar_sdk.base.Price = None[source]¶
- sale_start_date: sanmar_sdk.base.SanMarDate | None = None[source]¶
- sale_end_date: sanmar_sdk.base.SanMarDate | None = None[source]¶
- class sanmar_sdk.ftp.PriceChange(/, **data: Any)[source]¶
Bases:
sanmar_sdk.base.RecordA changed price from
sanmar_dpc.csv.- my_price: sanmar_sdk.base.Price[source]¶
- sanmar_sdk.ftp.read_customer_prices(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[CustomerPrice][source]¶
Stream
sanmar_dp.csvorsanmar_dpIncentive.csv, one row at a time.
- sanmar_sdk.ftp.read_price_changes(source: sanmar_sdk.ftp.readers.Source, *, encoding: str = 'utf-8-sig') collections.abc.Iterator[PriceChange][source]¶
Stream
sanmar_dpc.csv, one changed price at a time.