diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index e7efbbac..00000000
--- a/.gitattributes
+++ /dev/null
@@ -1,2 +0,0 @@
-# Zlib preset dictionaries: exact bytes matter for Adler-32 checksums
-s7commplus/zlib_dicts/*.xml binary
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 34e4b064..adfa9ea8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,7 +12,6 @@ repos:
- id: check-symlinks
- id: check-toml
- id: check-xml
- exclude: ^s7commplus/zlib_dicts/
- id: check-yaml
- id: check-illegal-windows-names
- id: check-merge-conflict
diff --git a/CHANGES.md b/CHANGES.md
index 37016d39..2a375703 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,55 +1,48 @@
CHANGES
========
-4.0.0 (unreleased)
--------------------
-
-Major release: new `s7commplus` package with S7CommPlus protocol support.
-
-* Return S7 item errors when pure-Python server reads target unregistered areas
- or ranges outside registered memory instead of fabricating successful data.
-* Echo calling and called TSAP parameters in pure-Python server connection
- confirmations, and restart the receive deadline after each TPKT header.
-* Correlate S7CommPlus responses by opcode, function, and sequence; discard
- bounded stale replies from earlier requests, preserve interleaved
- notifications, and serialize synchronous wire requests.
-* Decode corroborating CPU execution attributes so S7CommPlus `get_cpu_state()`
- distinguishes RUN from STOP on S7-1500 and returns UNKNOWN for absent or
- inconsistent state attributes, including S7-1200 responses that omit them.
-* Support LOGO reconnection/heartbeat options and preserve explicit TSAPs.
-* Correct legacy GetVarSubStreamed qualifiers and reject unusable authentication challenges.
-* Validate batched symbolic read item coverage and preserve explicit PLC errors.
-* Fix classic S7 TPKT bounds and COTP Class 0 header validation in sync/async clients.
-* Reject incomplete reads and mismatched or missing read/write acknowledgements.
-* Size read/write chunks in whole elements and preserve ctypes write datatypes;
- correct BIT lengths and timer/counter index addressing.
-
-* New `s7commplus` package for S7CommPlus protocol (S7-1200/1500)
-* S7CommPlus V1, V2 (TLS), and V3 support for S7-1200/1500
-* S7CommPlus area read/write (M, I, Q, counters, timers)
-* Require an explicit target datatype in S7CommPlus multi-write tuples; add
- `datatype=` to sync/async symbolic and area writes for scalar PLC targets.
-* S7CommPlus PLC start/stop via INVOKE
-* S7CommPlus object browsing via EXPLORE
-* S7CommPlus live symbol browsing (`client.browse()`) and datablock listing (experimental)
-* Fix V1 SessionKey challenge requests being rejected by S7-1200 FW 4.2 PLCs,
- consume non-fatal SystemEvents while waiting for the matching response, and
- strip per-fragment V3 HMACs from browse responses (#710)
-* S7CommPlus active-alarm browsing and alarm subscriptions (experimental)
-* S7CommPlus symbolic data subscriptions and notification decoding (experimental)
-* TIA Portal XML import for SymbolTable (`SymbolTable.from_tia_xml()`) (experimental)
-* S7CommPlus CPU state reading and block transfer (upload/download)
-* Correct the SessionKey emulator fingerprint encoding and reject missing or
- malformed authentication structures during integration tests.
-* Fix the legacy SecurityKey descriptor to identify the newly generated
- session key instead of an all-zero placeholder.
-* Keep SessionKey activation and public connection state pending until the PLC
- accepts session setup, with complete cleanup on rejection or transport error.
-* **Symbolic (LID-based) access for optimized DBs** (experimental):
- `Tag.from_access_string("8A0E0001.A", "REAL")` creates a symbolic Tag;
- `client.read_tag(tag)` routes to S7CommPlus LID-based access via the
- PLC's symbol tree. Required for S7-1200/1500 DBs with
- "Optimized block access" enabled (the TIA Portal V13+ default).
+3.2.0
+-----
+
+Feature and robustness release for the classic S7 protocol implementation.
+
+### Packaging
+
+* Move S7CommPlus support to the standalone
+ [`s7commplus`](https://github.com/gijzelaerr/s7commplus) package. The `s7`
+ import remains available as an alias for the classic `snap7` package.
+
+### New features
+
+* Add configurable request rate limiting to synchronous and asynchronous
+ clients, including fixed-interval and token-bucket modes (#823).
+* Add synchronous and asynchronous APIs for forcing I/O bits, cancelling
+ forces, and reading the PLC force table (#796, #800).
+* Add synchronous and asynchronous session password set/clear APIs for
+ password-protected PLCs (#792, #799).
+* Support LOGO reconnection, heartbeat, rate-limiting, and connection callback
+ options while preserving explicit TSAPs (#875).
+* Add an experimental serial PPI client for S7-200 PLCs, including V-memory,
+ system-memory, I/O, counter, and timer access (#824).
+
+### Bug fixes
+
+* Parse structured and flat SZL 0x0011 records correctly when reading order
+ codes and firmware versions from S7-1500 and classic PLCs (#783, #789).
+* Dispatch server callbacks and queue protocol events for client requests
+ instead of reporting only the server start event (#853, #856).
+* Validate classic S7 TPKT bounds and COTP Class 0 headers in synchronous and
+ asynchronous clients (#874).
+* Reject incomplete reads and mismatched or missing read/write
+ acknowledgements (#874).
+* Size read/write chunks in whole elements and preserve ctypes write
+ datatypes; correct BIT lengths and timer/counter index addressing (#874).
+* Validate `write_multi_vars()` inputs consistently and preserve each item's
+ declared datatype (#855, #874).
+* Echo the calling and called TSAP values in server connection confirmations,
+ and restart the receive deadline after each complete TPKT header (#893).
+* Return S7 item errors for reads from unregistered server areas or addresses
+ outside registered memory instead of returning fabricated data (#896).
3.1.2
-----
@@ -166,7 +159,6 @@ Feature and bug fix release for the pure Python S7 communication library.
### Thanks
-* [@bonk-dev](https://github.com/bonk-dev) — [HarpoS7](https://github.com/bonk-dev/HarpoS7): the session authentication implementation in `s7commplus/session_auth/` is a Python port of HarpoS7 (MIT license, see `s7commplus/session_auth/LICENSE-HarpoS7`)
* [@hs2bws-hash](https://github.com/hs2bws-hash) — extensive real PLC testing of Partner BSend/BRecv (#668)
* [@QuakeString](https://github.com/QuakeString) — read optimizer inspiration via python-snap7-optimized fork
diff --git a/README.rst b/README.rst
index b88a6d92..daa956ac 100644
--- a/README.rst
+++ b/README.rst
@@ -49,6 +49,3 @@ Documentation
Read the full documentation at
`python-snap7.readthedocs.io `_.
-
-For native communication with S7-1200 and S7-1500 controllers without PUT/GET,
-see the standalone `s7commplus `_ package.
diff --git a/doc/API/client-base.rst b/doc/API/client-base.rst
new file mode 100644
index 00000000..a353d69b
--- /dev/null
+++ b/doc/API/client-base.rst
@@ -0,0 +1,9 @@
+Shared Client Internals
+=======================
+
+Pure-computation and compatibility methods shared by ``Client`` and
+``AsyncClient``. Applications normally access these methods through one of the
+concrete clients.
+
+.. automodule:: snap7.client_base
+ :members:
diff --git a/doc/API/client.rst b/doc/API/client.rst
index 9d5c2fcb..e7777af9 100644
--- a/doc/API/client.rst
+++ b/doc/API/client.rst
@@ -4,15 +4,42 @@ Client
The ``s7`` package implements the classic S7 protocol for S7-300/400 PLCs and
PUT/GET access on S7-1200/1500.
+Use ``Client`` for synchronous applications::
+
+ from s7 import Client
+
+ with Client() as client:
+ client.connect("192.168.1.10", 0, 1)
+ data = client.db_read(1, 0, 4)
+
+Use ``AsyncClient`` when the surrounding application uses ``asyncio``::
+
+ import asyncio
+ from s7 import AsyncClient
+
+ async def main():
+ async with AsyncClient() as client:
+ await client.connect("192.168.1.10", 0, 1)
+ data = await client.db_read(1, 0, 4)
+
+ asyncio.run(main())
+
+Both clients expose the same main PLC operations, including area and DB
+access, block transfer, CPU information and control, clock access, force
+operations, and session passwords. Methods that perform I/O are coroutines on
+``AsyncClient``. See :doc:`../advanced` for the less common operations.
+
s7.Client
---------
.. automodule:: snap7.client
:members:
+ :inherited-members:
s7.AsyncClient
--------------
.. automodule:: snap7.async_client
:members:
+ :inherited-members:
:exclude-members: AsyncISOTCPConnection
diff --git a/doc/API/demo.rst b/doc/API/demo.rst
new file mode 100644
index 00000000..cdf47134
--- /dev/null
+++ b/doc/API/demo.rst
@@ -0,0 +1,9 @@
+Demo Server
+===========
+
+Implementation of the optional live host-metrics demo used by ``s7 demo``.
+Install ``python-snap7[demo]`` before running it. This module is intended for
+examples and integration testing, not production monitoring.
+
+.. automodule:: snap7.demo
+ :members:
diff --git a/doc/API/ppi.rst b/doc/API/ppi.rst
new file mode 100644
index 00000000..c7c1c8e7
--- /dev/null
+++ b/doc/API/ppi.rst
@@ -0,0 +1,10 @@
+PPI
+===
+
+Serial PPI transport and client support for S7-200 PLCs. Install the optional
+dependency with ``pip install "python-snap7[ppi]"`` and see :doc:`../ppi` for
+setup, area semantics, and current limitations.
+
+.. automodule:: snap7.ppi
+ :members:
+ :show-inheritance:
diff --git a/doc/API/rate-limiter.rst b/doc/API/rate-limiter.rst
new file mode 100644
index 00000000..0442461f
--- /dev/null
+++ b/doc/API/rate-limiter.rst
@@ -0,0 +1,10 @@
+Rate Limiter
+============
+
+The synchronous and asynchronous clients create a limiter from their
+``max_requests_per_second`` and ``rate_limit_*`` constructor arguments. Most
+applications should configure the client instead of constructing this class
+directly.
+
+.. automodule:: snap7.rate_limiter
+ :members:
diff --git a/doc/API/szl.rst b/doc/API/szl.rst
new file mode 100644
index 00000000..e546ef32
--- /dev/null
+++ b/doc/API/szl.rst
@@ -0,0 +1,8 @@
+SZL Parsers
+===========
+
+Helpers for decoding System Status List records returned by
+``Client.read_szl()`` and ``AsyncClient.read_szl()``.
+
+.. automodule:: snap7.szl
+ :members:
diff --git a/doc/API/tags.rst b/doc/API/tags.rst
index 9cbf5c7d..5f741adb 100644
--- a/doc/API/tags.rst
+++ b/doc/API/tags.rst
@@ -90,65 +90,6 @@ Supported types
Arrays are supported for any fixed-size type via ``[count]`` suffix.
-Optimized block access (S7CommPlus)
-------------------------------------
-
-.. warning::
-
- Symbolic (LID-based) access is **experimental** and requires real PLC
- testing. The wire-level implementation follows the S7CommPlusDriver
- reference but has not yet been validated against hardware.
-
-S7-1200/1500 DBs with "Optimized block access" enabled (the default in
-TIA Portal V13+) do not use fixed byte offsets. The PLC internally
-relocates variables between downloads, so addresses like ``DB1.DBX0.0``
-are unreliable.
-
-For optimized blocks, use the low-level ``read_symbolic`` and
-``write_symbolic`` methods with access paths discovered via
-:meth:`~s7commplus.client.S7CommPlusClient.browse`. A typed, name-based
-S7CommPlus tag API is not implemented yet; :class:`~snap7.tags.Tag` and the
-``read_tag`` methods above belong to the classic ``s7.Client`` API.
-
-.. code-block:: python
-
- import struct
-
- from s7commplus import Client
- from s7commplus.protocol import DataType
-
- client = Client()
- client.connect("192.168.1.10", use_tls=True)
-
- variables = client.browse()
- speed_info = next(item for item in variables if item["name"] == "Motor.Speed")
- path = [int(part, 16) for part in speed_info["access_sequence"].split(".")]
-
- # Symbolic values are currently exposed as raw wire bytes.
- raw = client.read_symbolic(path[0], path[1:])
- speed = struct.unpack(">f", raw)[0]
- client.write_symbolic(path[0], path[1:], struct.pack(">f", 1500.0), datatype=DataType.REAL)
-
-The write datatype must match the PLC variable's ``data_type`` from browsing.
-For example, an INT requires ``struct.pack(">h", value)`` and
-``datatype=DataType.INT``. BLOB is not a generic scalar datatype. The
-``datatype=`` keyword is supported by both sync and async symbolic and area
-writes; omitting it preserves the legacy BLOB encoding.
-
-Multi-write requires four-element tuples with an explicit datatype. The
-three-element form from development versions is rejected before sending:
-
-.. code-block:: python
-
- client.db_write_multi([
- (7, 0, struct.pack(">f", 2.0), DataType.REAL),
- (7, 4, struct.pack(">H", 512), DataType.WORD),
- ])
-
-These DB/offset addresses must be validated for the target PLC; they are not
-interchangeable with browse-derived symbolic paths. Explicit BLOB remains
-available for targets that accept it, including the raw-byte emulator.
-
API reference
-------------
diff --git a/doc/API/util.rst b/doc/API/util.rst
index 6f03a82f..34986547 100644
--- a/doc/API/util.rst
+++ b/doc/API/util.rst
@@ -13,3 +13,12 @@ REAL, STRING, etc.):
.. automodule:: snap7.util
:members:
+
+DB layout helpers
+-----------------
+
+``DB`` and ``Row`` map a textual DB layout specification onto a byte buffer.
+They are also exported directly from ``s7`` and ``snap7``.
+
+.. automodule:: snap7.util.db
+ :members:
diff --git a/doc/advanced.rst b/doc/advanced.rst
new file mode 100644
index 00000000..4e981bc1
--- /dev/null
+++ b/doc/advanced.rst
@@ -0,0 +1,90 @@
+Advanced PLC Operations
+=======================
+
+The client API includes PLC management operations in addition to memory reads
+and writes. Availability depends on the PLC family, firmware, protection
+level, and current CPU state. A PLC can reject a valid request that it does not
+implement.
+
+The examples below use :class:`~snap7.client.Client`. The corresponding
+:class:`~snap7.async_client.AsyncClient` methods have the same purpose and must
+be awaited.
+
+Identification and diagnostics
+------------------------------
+
+Read common identification and status records after connecting::
+
+ order_code = client.get_order_code()
+ cpu_info = client.get_cpu_info()
+ cp_info = client.get_cp_info()
+ protection = client.get_protection()
+ state = client.get_cpu_state()
+ diagnostics = client.read_diagnostic_buffer()
+
+For records without a convenience method, use ``read_szl(szl_id, index)`` or
+``read_szl_list()``. SZL layouts vary between PLC generations; the typed
+helpers in :mod:`snap7.szl` parse the layouts used by the convenience methods.
+
+CPU state and clock
+-------------------
+
+``plc_stop()``, ``plc_hot_start()``, and ``plc_cold_start()`` change CPU state.
+Clock operations are provided by ``get_plc_datetime()``,
+``set_plc_datetime()``, and ``set_plc_system_datetime()``.
+
+.. warning::
+
+ CPU control and clock changes affect a real controller. Confirm that the
+ operation is safe for the machine and process before issuing it.
+
+Block operations
+----------------
+
+Use ``list_blocks()`` and ``list_blocks_of_type()`` to enumerate blocks,
+``get_block_info()`` to inspect one, ``upload()`` or ``full_upload()`` to read
+one, and ``download()`` or ``delete()`` to modify the PLC. ``compress()`` and
+``copy_ram_to_rom()`` expose the corresponding PLC maintenance operations.
+
+These methods transfer individual PLC blocks; they do not create a complete
+TIA Portal project or full PLC backup.
+
+Session passwords
+-----------------
+
+For classic S7 CPUs configured with a session password, authenticate after
+connecting and clear the password before disconnecting::
+
+ client.connect("192.168.1.10", 0, 2)
+ client.set_session_password("secret")
+ try:
+ data = client.db_read(1, 0, 4)
+ finally:
+ client.clear_session_password()
+ client.disconnect()
+
+Forcing inputs and outputs
+--------------------------
+
+``force_bit()`` and ``cancel_force()`` support the ``Area.PE`` input and
+``Area.PA`` output areas. ``read_force_table()`` returns active force entries
+when the PLC supports SZL 0x0025::
+
+ from s7.type import Area
+
+ client.force_bit(Area.PA, byte_offset=0, bit=1, value=True)
+ active_forces = client.read_force_table()
+ client.cancel_force(Area.PA, byte_offset=0, bit=1)
+
+.. warning::
+
+ A force overrides normal process behavior and can remain active after the
+ application exits. Track every applied force and cancel it deliberately.
+
+Raw protocol and compatibility operations
+-----------------------------------------
+
+``iso_exchange_buffer()`` sends a caller-provided S7 PDU. The ``as_*`` methods
+and completion callbacks preserve compatibility with the historical Snap7 API;
+new asynchronous applications should normally use ``AsyncClient`` instead.
+See :doc:`API/client` for the complete method reference.
diff --git a/doc/cli.rst b/doc/cli.rst
index 21c152d2..f0f675b4 100644
--- a/doc/cli.rst
+++ b/doc/cli.rst
@@ -32,6 +32,19 @@ Start an emulated S7 PLC server with default values::
Port the server will listen on (default: 1102).
+demo
+----
+
+Start a live test server that exposes host CPU, memory, disk, and network
+metrics in DB1 and writable controls in DB2. Install the ``demo`` extra first::
+
+ pip install "python-snap7[demo]"
+ s7 demo --port 10102
+ s7 demo --plain
+
+Use ``--refresh`` to set the sampling interval in seconds. This command is a
+demonstration and test tool, not a production monitoring service.
+
read
----
diff --git a/doc/connecting.rst b/doc/connecting.rst
index 95f6d6bf..fe7a7410 100644
--- a/doc/connecting.rst
+++ b/doc/connecting.rst
@@ -31,24 +31,25 @@ Rack/Slot Reference
* - S7-1200
- 0
- 1
- - PUT/GET access must be enabled in TIA Portal (or use S7CommPlus)
+ - PUT/GET access must be enabled in TIA Portal
* - S7-1500
- 0
- 1
- - PUT/GET access must be enabled in TIA Portal (or use S7CommPlus)
- * - S7-200 / Logo
+ - PUT/GET access must be enabled in TIA Portal
+ * - S7-200 (serial PPI)
- --
- --
- - Use ``set_connection_params`` with TSAP addressing (``s7`` package)
+ - Use ``PPIClient`` and a serial interface; see :doc:`ppi`.
+ * - LOGO! / S7-200 (Ethernet)
+ - --
+ - --
+ - Use ``Logo`` or the controller's documented TSAP values.
.. warning::
S7-1200 and S7-1500 PLCs ship with PUT/GET communication disabled by
- default. Use ``s7commplus.Client`` to communicate via S7CommPlus, which
- does not require PUT/GET to be enabled. If you need to use
- ``s7.Client`` (legacy protocol), enable PUT/GET in TIA Portal
- under the CPU properties. See :doc:`tia-portal-config` for step-by-step
- instructions.
+ default. Enable PUT/GET in TIA Portal under the CPU properties before using
+ ``s7.Client``. See :doc:`tia-portal-config` for step-by-step instructions.
S7-300
@@ -71,18 +72,8 @@ S7-400
client = Client()
client.connect("192.168.1.10", 0, 3)
-S7-1200 / S7-1500 (S7CommPlus)
--------------------------------
-
-.. code-block:: python
-
- from s7commplus import Client
-
- client = Client()
- client.connect("192.168.1.10")
-
-S7-1200 / S7-1500 (Legacy PUT/GET)
-------------------------------------
+S7-1200 / S7-1500 (PUT/GET)
+----------------------------
If PUT/GET access is enabled in TIA Portal, you can also use the legacy
protocol:
@@ -94,160 +85,26 @@ protocol:
client = Client()
client.connect("192.168.1.10", 0, 1)
-See :doc:`API/client` for details on TLS and password authentication.
+S7-200 Serial PPI
+-----------------
-S7CommPlus over TLS (V2/V3, TIA Portal V17+)
----------------------------------------------
+Serial S7-200 connections use :class:`~snap7.ppi.PPIClient`, not rack/slot or
+TSAP addressing::
-S7-1500 firmware ≥ V2.9 and S7-1200 firmware ≥ V4.5 negotiate
-S7CommPlus V2 or V3, which transports the protocol inside a TLS 1.3
-session. Pass ``use_tls=True`` to ``connect`` to activate it:
+ from s7 import PPIClient
-.. code-block:: python
+ with PPIClient("/dev/ttyUSB0", station=2, baudrate=9600) as client:
+ data = client.v_read(0, 4)
- from s7commplus import Client
+Install ``python-snap7[ppi]`` and see :doc:`ppi` for supported areas and
+limitations.
- client = Client()
- client.connect(
- "192.168.1.10",
- use_tls=True,
- )
- data = client.db_read(1, 0, 4)
- client.disconnect()
+LOGO! and Ethernet TSAP Connections
+-----------------------------------
-The client wraps the ISO-on-TCP socket with TLS 1.3 between the
-``InitSSL`` exchange and the ``CreateObject`` request. By default the
-PLC's certificate is not verified — fine for development, not fine in
-production. To verify the PLC against a CA bundle, pass ``tls_ca``:
-
-.. code-block:: python
-
- client.connect(
- "192.168.1.10",
- use_tls=True,
- tls_ca="/path/to/plc-ca.pem",
- )
-
-If the PLC requires mutual TLS (client-side certificate), supply
-``tls_cert`` and ``tls_key`` as well.
-
-The ``cryptography`` package is required for TLS support. Install
-with the ``s7commplus`` extra:
-
-.. code-block:: bash
-
- pip install 'python-snap7[s7commplus]'
-
-For a source checkout, use ``python -m pip install -e '.[s7commplus]'``.
-If the legacy SessionKey authentication path cannot import its dependencies,
-connection setup raises an error with these installation instructions and
-releases the connection; it does not continue with an unauthenticated setup.
-
-.. note::
-
- Older S7-1200 firmware (FW < 4.5) negotiates V1 of the S7CommPlus
- protocol, which predates TLS and uses a SessionKey handshake instead.
- The synchronous ``Client`` performs this handshake when ``use_tls`` is
- false; it does not fall back to the classic PUT/GET protocol. V1 support
- is experimental and the asynchronous client currently supports only the
- TLS path. See `issue #710
- `_ for the original
- V1 hardware report.
-
-TLS handshake rejected by the PLC (connection reset)
-------------------------------------------------------
-
-S7 PLCs have a minimal TLS stack that rejects ``ClientHello`` messages
-containing features it does not recognise. Two common causes:
-
-* **Post-quantum key share (OpenSSL ≥ 3.5)** — the default
- ``ClientHello`` advertises the ``X25519MLKEM768`` hybrid group whose
- ~1.2 KB key share the PLC drops.
-* **Modern signature algorithms** — OpenSSL advertises Ed25519, Ed448,
- and RSA-PSS variants in the ``signature_algorithms`` extension.
- Instead of ignoring unknown algorithms (as TLS 1.2 requires), the PLC
- treats them as a fatal error and sends a TCP RST.
-
-CPython's ``ssl`` module exposes no API for either the
-``supported_groups`` or ``signature_algorithms`` lists. The fix is to
-restrict both through OpenSSL's own configuration via the
-``OPENSSL_CONF`` environment variable.
-
-1. Create an OpenSSL configuration file, e.g. ``s7-openssl.cnf``:
-
- .. code-block:: ini
-
- # Restrict TLS parameters for S7 PLC compatibility.
- openssl_conf = openssl_init
-
- [openssl_init]
- ssl_conf = ssl_configuration
-
- [ssl_configuration]
- system_default = system_default_sect
-
- [system_default_sect]
- # Classic ECDHE curves only — no post-quantum groups.
- Groups = x25519:secp256r1:secp384r1
- # Classic signature algorithms only — no Ed25519, Ed448, or RSA-PSS.
- SignatureAlgorithms = RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA256:ECDSA+SHA384
-
-2. Point ``OPENSSL_CONF`` at it **before** the Python process starts.
- OpenSSL reads this configuration once, when it initialises, so
- setting the variable from inside Python (e.g. via ``os.environ``)
- after ``ssl`` is imported is too late — it must be set in the
- environment:
-
- .. code-block:: bash
-
- # for a single run
- OPENSSL_CONF=/path/to/s7-openssl.cnf python your_script.py
-
- # or for the whole shell session
- export OPENSSL_CONF=/path/to/s7-openssl.cnf
-
-With both settings applied, the ``ClientHello`` contains only
-algorithms that S7 PLCs understand and the handshake completes
-normally.
-
-PLC Password Authentication
-----------------------------
-
-If the PLC has a password configured (``Full access (no protection)``
-disabled in TIA Portal), pass it to the synchronous client's ``connect``
-method. The client applies it to either the legacy SessionKey flow or the
-TLS legitimation flow, as appropriate for the PLC:
-
-.. code-block:: python
-
- from s7commplus import Client
-
- client = Client()
- client.connect(
- "192.168.1.10",
- use_tls=True,
- password="hunter2",
- )
- data = client.db_read(1, 0, 4)
-
-For an older V1 PLC, omit ``use_tls=True``; the password is then used by the
-post-SessionKey legitimation exchange. The asynchronous client currently
-supports password authentication only on TLS connections, using
-``await client.authenticate(...)`` after connecting.
-
-The explicit synchronous ``authenticate`` method is also TLS-only. It
-auto-detects whether the TLS PLC uses the legacy SHA-1 challenge or the newer
-AES-256-CBC challenge. For accounts with a username (TIA Portal V17+
-user-based access control), pass it explicitly:
-
-.. code-block:: python
-
- client.authenticate(password="hunter2", username="operator")
-
-S7-200 / Logo (TSAP Connection)
---------------------------------
-
-S7-200 and Logo PLCs require TSAP addressing via TSAP addressing:
+For LOGO!, prefer the dedicated :class:`~snap7.logo.Logo` class. Controllers
+that expose classic S7 over Ethernet with explicit TSAPs can use
+``set_connection_params()`` before connecting:
.. code-block:: python
@@ -257,6 +114,9 @@ S7-200 and Logo PLCs require TSAP addressing via TSAP addressing:
client.set_connection_params("192.168.1.10", 0x1000, 0x2000)
client.connect("192.168.1.10", 0, 0)
+The TSAP values are controller and project specific; the example values are
+not universal defaults.
+
Using a Non-Standard Port
--------------------------
@@ -305,3 +165,19 @@ unchanged — ``snap7`` is an alias for ``s7``:
client = snap7.Client()
client.connect("192.168.1.10", 0, 1)
+
+Asynchronous Connections
+------------------------
+
+``AsyncClient`` provides native ``asyncio`` I/O and serializes request/response
+cycles on its connection::
+
+ import asyncio
+ from s7 import AsyncClient
+
+ async def main():
+ async with AsyncClient() as client:
+ await client.connect("192.168.1.10", 0, 1)
+ data = await client.db_read(1, 0, 4)
+
+ asyncio.run(main())
diff --git a/doc/connection-issues.rst b/doc/connection-issues.rst
index 33531a4c..fe3479f3 100644
--- a/doc/connection-issues.rst
+++ b/doc/connection-issues.rst
@@ -28,8 +28,7 @@ long-running applications:
client = Client()
client.connect("192.168.1.10", 0, 1)
-For finer control over reconnection parameters, use the legacy ``s7.Client``
-directly:
+Configure reconnection when constructing ``s7.Client``:
.. code-block:: python
@@ -64,6 +63,27 @@ The parameters:
- **on_reconnect**: Callback invoked after a successful reconnection.
+Request Rate Limiting
+---------------------
+
+Both client implementations can limit outbound requests. Fixed mode spaces
+requests evenly; token-bucket mode permits a bounded burst. The default rate of
+zero disables limiting::
+
+ from s7 import Client
+
+ client = Client(
+ max_requests_per_second=10,
+ rate_limit_algorithm="token_bucket",
+ rate_limit_burst=5,
+ rate_limit_behavior="block",
+ )
+
+Set ``rate_limit_behavior="raise"`` to raise
+:class:`~snap7.error.S7RateLimitError` instead of waiting. Rate limiting does
+not make a synchronous client thread-safe; see :doc:`thread-safety`.
+
+
Manual Reconnection
-------------------
diff --git a/doc/development.rst b/doc/development.rst
index addaa71e..bcbb67dd 100644
--- a/doc/development.rst
+++ b/doc/development.rst
@@ -81,8 +81,6 @@ Special thanks to:
* Davide Nardella for creating snap7
* Thomas Hergenhahn for his libnodave
* Thomas W for his S7comm wireshark plugin
-* `bonk-dev `_ for `HarpoS7 `_ — the S7CommPlus session authentication (``s7commplus/session_auth/``) is a Python port of HarpoS7 (MIT)
-* Thomas W (thomas-v2) for `S7CommPlusDriver `_ — the zlib preset dictionaries and S7CommPlus protocol reference (LGPL-3.0)
* `Fabian Beitler `_
* `Nikteliy `_
* `Lautaro Nahuel Dapino `_
diff --git a/doc/index.rst b/doc/index.rst
index 0c9b1f7a..434d785e 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -16,6 +16,8 @@ Welcome to python-snap7's documentation!
connecting
reading-writing
multi-variable
+ advanced
+ ppi
server
cli
tia-portal-config
@@ -43,10 +45,15 @@ Welcome to python-snap7's documentation!
API/server
API/partner
API/logo
+ API/ppi
API/util
API/tags
API/optimizer
+ API/rate-limiter
API/log
+ API/szl
+ API/discovery
+ API/demo
API/type
API/error
@@ -55,9 +62,9 @@ Welcome to python-snap7's documentation!
:caption: Internals
API/connection
+ API/client-base
API/s7protocol
API/datatypes
- API/discovery
Indices and tables
diff --git a/doc/installation.rst b/doc/installation.rst
index eaaccb43..2b6e210e 100644
--- a/doc/installation.rst
+++ b/doc/installation.rst
@@ -1,18 +1,46 @@
Installation
============
-python-snap7 is a pure Python package with no native dependencies. Install it
-using pip::
+The core python-snap7 package is pure Python and has no runtime dependencies.
+Install it with pip::
$ pip install python-snap7
-If you want to use the CLI interface for running an emulator, install it with::
-
- $ pip install "python-snap7[cli]"
-
-That's it! No native libraries or platform-specific setup is required. This works
-on any platform that supports Python 3.10+, including ARM, Alpine Linux, and other
-environments where the old C library was hard to install.
+Optional features
+-----------------
+
+Install only the extras needed by your application:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 20 30 50
+
+ * - Extra
+ - Install command
+ - Purpose
+ * - ``cli``
+ - ``pip install "python-snap7[cli]"``
+ - The ``s7`` command for reading, writing, inspecting, and serving.
+ * - ``demo``
+ - ``pip install "python-snap7[demo]"``
+ - Live demo server with host metrics and terminal display.
+ * - ``discovery``
+ - ``pip install "python-snap7[discovery]"``
+ - PROFINET DCP device discovery.
+ * - ``ppi``
+ - ``pip install "python-snap7[ppi]"``
+ - Experimental S7-200 serial PPI support via pySerial.
+ * - ``doc``
+ - ``pip install "python-snap7[doc]"``
+ - Sphinx and the theme used to build this documentation.
+
+Extras can be combined::
+
+ $ pip install "python-snap7[cli,discovery,ppi]"
+
+No Snap7 shared library or platform-specific binary is required. Optional
+dependencies may have their own platform requirements; for example, PPI needs a
+serial interface and discovery needs access to a supported network interface.
Upgrading from 2.x
-------------------
@@ -29,4 +57,4 @@ If you experience issues after upgrading:
$ pip install "python-snap7<3"
- The latest stable pre-3.0 release is version 2.1.0.
+ The latest stable pre-3.0 release is version 2.1.1.
diff --git a/doc/introduction.rst b/doc/introduction.rst
index e9625931..dcb5298e 100644
--- a/doc/introduction.rst
+++ b/doc/introduction.rst
@@ -1,30 +1,33 @@
Introduction
============
-python-snap7 is a pure Python S7 communication library for interfacing
-natively with Siemens S7 PLCs. The library implements the complete S7
-protocol stack including TPKT (RFC 1006), COTP (ISO 8073), and S7
-protocol layers.
+python-snap7 is a pure Python implementation of the classic Siemens S7
+protocol. It includes the TPKT (RFC 1006), COTP (ISO 8073), and S7 protocol
+layers, synchronous and asynchronous clients, and a server for testing.
The name "python-snap7" is historical: the library originally started as a
Python wrapper around the `Snap7 `_ C library.
As of version 3.0, the C library is no longer used, but the name is kept for
backwards compatibility.
-python-snap7 requires Python 3.10+ and runs on Windows, macOS and Linux
-without any native dependencies.
+The core package requires Python 3.10+ and has no runtime dependencies. It runs
+on Windows, macOS, and Linux. Optional features such as serial PPI, PROFINET
+discovery, and the command-line tools have separate extras; see
+:doc:`installation`.
-S7CommPlus support
-------------------
+Choosing an interface
+---------------------
-Native S7CommPlus support for S7-1200 and S7-1500 PLCs is provided by the
-standalone `s7commplus `_ package.
+Use :class:`s7.Client ` or
+:class:`s7.AsyncClient ` for S7-300/400 PLCs
+and for S7-1200/1500 PLCs configured for PUT/GET. Use
+:class:`s7.PPIClient ` for experimental serial access to
+S7-200 PLCs, and :class:`s7.Logo ` for LOGO! controllers.
-The ``s7`` package (legacy S7)
-------------------------------
+The ``s7`` and ``snap7`` package names
+--------------------------------------
-The ``s7`` package implements the classic S7 protocol. It supports
-S7-300, S7-400, S7-1200 and S7-1500 PLCs via the PUT/GET interface:
+``s7`` is the recommended import name:
.. code-block:: python
@@ -35,10 +38,6 @@ S7-300, S7-400, S7-1200 and S7-1500 PLCs via the PUT/GET interface:
data = client.db_read(1, 0, 4)
client.disconnect()
-Use ``s7.Client`` for S7-300/400 PLCs or when PUT/GET access is enabled
-on S7-1200/1500. For native S7CommPlus communication, install the standalone
-``s7commplus`` package.
-
.. note::
The ``snap7`` package name continues to work as an alias for ``s7``
@@ -58,4 +57,9 @@ on S7-1200/1500. For native S7CommPlus communication, install the standalone
clear description and the version you are using. As a workaround, you can
install the last pre-3.0 release with ``pip install "python-snap7<3"``.
+.. note::
+
+ Looking for S7CommPlus support? It moved to the standalone
+ `s7commplus project `_.
+
The project development is centralized on `github `_.
diff --git a/doc/limitations.rst b/doc/limitations.rst
index 19f4544c..d8dc8daa 100644
--- a/doc/limitations.rst
+++ b/doc/limitations.rst
@@ -1,9 +1,8 @@
Protocol Limitations and FAQ
============================
-python-snap7 implements both the classic S7 protocol and S7CommPlus over
-TCP/IP. The following limitations apply to the classic protocol exposed by
-``s7.Client``; the native S7CommPlus client has different capabilities:
+python-snap7 implements the classic S7 protocol over TCP/IP. The following
+limitations apply to ``s7.Client``:
.. list-table::
:header-rows: 1
@@ -13,12 +12,9 @@ TCP/IP. The following limitations apply to the classic protocol exposed by
- Explanation
* - Read tag/symbol names from PLC
- The classic S7 protocol only addresses data by area, DB number, and byte
- offset. The experimental ``s7commplus.Client.browse()`` API can read the
- symbol tree from supported S7-1200/1500 PLCs.
+ offset; callers must provide the addresses.
* - Get DB structure or layout from PLC
- Classic S7 reads DBs as raw bytes, so callers must supply the layout.
- S7CommPlus browsing can reconstruct type information and optimized
- symbolic access paths on supported S7-1200/1500 PLCs.
* - Discover PLCs on the network
- The classic S7 protocol has no broadcast discovery mechanism. However,
python-snap7 provides PROFINET DCP discovery via the ``s7 discover``
@@ -27,7 +23,7 @@ TCP/IP. The following limitations apply to the classic protocol exposed by
* - Create PLC backups
- Full project backup requires TIA Portal. python-snap7 can upload
individual blocks, but this is not a complete backup.
- * - S7CommPlus V4
- - python-snap7 supports S7CommPlus V1, V2, and V3 via the ``s7commplus``
- package. V4 is not yet supported. For PLCs that require V4, use OPC UA
- as an alternative.
+
+Serial PPI has additional constraints: the current implementation supports one
+PC master and one S7-200 slave, not multimaster token passing or PPI over TCP.
+See :doc:`ppi`.
diff --git a/doc/plc-support.rst b/doc/plc-support.rst
index 91ed2740..b28a114d 100644
--- a/doc/plc-support.rst
+++ b/doc/plc-support.rst
@@ -1,102 +1,57 @@
PLC Support Matrix
==================
-This page documents which Siemens PLC families are supported by python-snap7,
-the communication protocols they use, and any configuration requirements.
+python-snap7 supports PLCs that expose the classic S7 protocol. Newer
+controllers require PUT/GET access to be enabled in TIA Portal.
Supported PLCs
--------------
.. list-table::
:header-rows: 1
- :widths: 20 10 10 10 10 15 25
+ :widths: 25 15 20 40
* - PLC Family
- Introduced
- - S7 (classic)
- - S7CommPlus V1
- - S7CommPlus V2/V3
- - python-snap7 support
+ - S7 support
- Notes
* - S7-300
- ~1994
- - Yes
- - No
- - No
- - **Full**
+ - Full
- Works out of the box with ``s7.Client``.
* - S7-400
- ~1996
- - Yes
- - No
- - No
- - **Full**
+ - Full
- Works out of the box with ``s7.Client``.
- * - S7-1200 (FW ≤3)
+ * - S7-1200
- 2009
- - Yes
- - No
- - No
- - **Full**
- - Use ``s7.Client`` with PUT/GET enabled in TIA Portal.
- * - S7-1200 (FW 4+)
- - ~2014
- - Yes
- - Yes
- - No
- - **Full**
- - Use ``s7commplus.Client`` (S7CommPlus V1) or ``s7.Client`` (PUT/GET).
- * - S7-1500 (FW 1.x)
+ - PUT/GET
+ - Enable PUT/GET in TIA Portal.
+ * - S7-1500
- 2012
- - PUT/GET only
- - Yes
- - No
- - **Full**
- - Use ``s7commplus.Client`` (S7CommPlus V1) or ``s7.Client`` (PUT/GET).
- * - S7-1500 (FW 2.x)
- - ~2016
- - PUT/GET only
- - No
- - V2
- - **Full**
- - Use ``s7commplus.Client`` with TLS, or ``s7.Client`` (PUT/GET).
- * - S7-1500 (FW 3.x+)
- - ~2022
- - PUT/GET only
- - No
- - V3
- - **Full**
- - Use ``s7commplus.Client`` with TLS, or ``s7.Client`` (PUT/GET).
+ - PUT/GET
+ - Enable PUT/GET in TIA Portal.
* - S7-1500R/H
- ~2019
- - No
- - No
- - V2/V3
- - **Not supported**
- - Redundant CPUs; no classic S7 fallback available.
+ - Not supported
+ - Redundant CPUs have no classic S7 fallback.
* - ET 200SP CPU
- ~2014
- - PUT/GET only
- - Yes
- - Yes
- - **PUT/GET only**
- - Same behavior as S7-1500 with matching firmware.
+ - PUT/GET
+ - Same behavior as an S7-1500 with matching firmware.
* - S7-200 SMART
- ~2012
- - Subset
- - No
- - No
- - **Partial**
- - Basic read/write works. Some advanced functions may not be available.
+ - Partial
+ - Basic Ethernet read/write works; advanced functions may be unavailable.
+ * - S7-200 (serial PPI)
+ - ~1994
+ - Experimental
+ - Use :class:`~snap7.ppi.PPIClient`; see :doc:`ppi` for current limits.
* - LOGO! 8
- ~2014
- - Subset
- - No
- - No
- - **Full**
+ - Full
- Use the :class:`~snap7.logo.Logo` class.
-
Enabling PUT/GET Access
-----------------------
@@ -104,52 +59,19 @@ For S7-1200 and S7-1500 PLCs, classic S7 protocol access requires the
**PUT/GET** option to be enabled. See :doc:`tia-portal-config` for
step-by-step instructions.
-When using ``s7commplus.Client``, the S7CommPlus protocol does **not** require
-PUT/GET to be enabled. PUT/GET is only needed when using ``s7.Client``
-(legacy protocol).
-
.. warning::
PUT/GET access provides unauthenticated read/write access to PLC memory.
Only enable this on networks that are properly segmented and secured.
+Feature Availability
+--------------------
-Protocol Overview
------------------
-
-Siemens has evolved their PLC communication protocols over time:
-
-.. list-table::
- :header-rows: 1
- :widths: 20 15 15 50
-
- * - Protocol
- - Encryption
- - Authentication
- - Used by
- * - S7 (classic)
- - None
- - None
- - S7-300, S7-400, S7-1200, S7-1500 (PUT/GET mode)
- * - S7CommPlus V1
- - None
- - Challenge-response
- - S7-1200 FW 4+, S7-1500 FW 1.x
- * - S7CommPlus V2
- - TLS 1.3
- - Challenge-response + TLS
- - S7-1500 FW 2.x
- * - S7CommPlus V3
- - TLS
- - Certificate-based
- - S7-1500 FW 3.x+
-
-python-snap7 implements the **classic S7 protocol** (via ``s7``) and
-**S7CommPlus V1, V2, and V3** (via ``s7commplus``). The classic protocol
-remains available on most PLC families via the PUT/GET mechanism. S7CommPlus V4
-is not yet supported; for PLCs that require it, consider using OPC UA as an
-alternative.
-
+The matrix describes transport-level access, not a guarantee that every client
+method is implemented by every CPU. Block transfer, CPU control, forcing,
+passwords, diagnostic SZLs, and clock operations vary by model, firmware, and
+protection level. See :doc:`advanced` and handle
+:class:`~snap7.error.S7ProtocolError` when probing optional PLC capabilities.
Alternatives for Unsupported PLCs
---------------------------------
diff --git a/doc/ppi.rst b/doc/ppi.rst
new file mode 100644
index 00000000..d3420ddb
--- /dev/null
+++ b/doc/ppi.rst
@@ -0,0 +1,67 @@
+S7-200 Serial PPI
+=================
+
+python-snap7 3.2 adds experimental serial PPI support for a PC master talking
+to one Siemens S7-200 slave. Install the optional serial dependency first::
+
+ pip install "python-snap7[ppi]"
+
+Connect with the serial device and PLC station address::
+
+ from s7 import PPIClient
+
+ with PPIClient("/dev/ttyUSB0", station=2, baudrate=9600) as client:
+ data = client.v_read(0, 4)
+ client.v_write(10, b"\x01\x02")
+
+On Windows, pass a port such as ``"COM3"``. The serial transport uses the PPI
+8E1 format. The adapter must provide the physical interface required by the
+PLC; a serial device name alone does not imply electrical compatibility.
+
+Memory areas
+------------
+
+Use :class:`~snap7.ppi.PPIArea` with ``read_area()`` and ``write_area()``:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 20 30 50
+
+ * - Area
+ - Meaning
+ - Count and data units
+ * - ``S`` / ``SM``
+ - Special memory / special markers
+ - Bytes
+ * - ``I`` / ``Q`` / ``M``
+ - Inputs / outputs / markers
+ - Bytes
+ * - ``V``
+ - Variable memory
+ - Bytes; encoded as DB1 on the wire
+ * - ``AI`` / ``AQ``
+ - Analog inputs / outputs
+ - 16-bit items
+ * - ``C`` / ``T``
+ - Counters / timers
+ - 16-bit items
+
+For example::
+
+ from s7 import PPIArea, PPIClient
+
+ with PPIClient("/dev/ttyUSB0", station=2) as client:
+ marker_bytes = client.read_area(PPIArea.M, start=0, count=8)
+ counters = client.read_area(PPIArea.C, start=0, count=2)
+
+Limits
+------
+
+The initial implementation supports SD1/SD2 request framing, PDU negotiation,
+bounded retries, and a single PC-master/single-PLC exchange. It does not
+implement multimaster token passing or PPI over TCP. Request sizes must fit the
+negotiated PDU and the PPI frame-size limit. Validate the transport against the
+target hardware before relying on it in production.
+
+See :doc:`API/ppi` for all constructor options, frame helpers, and low-level
+transport classes.
diff --git a/doc/reading-writing.rst b/doc/reading-writing.rst
index a57a0106..73484ce7 100644
--- a/doc/reading-writing.rst
+++ b/doc/reading-writing.rst
@@ -201,9 +201,9 @@ LINT (8 bytes, signed -9223372036854775808 to 9223372036854775807)
value = util.get_lint(data, 0)
print(f"LINT = {value}")
- # Write (no set_lint helper -- use struct directly)
- import struct
- data = bytearray(struct.pack(">q", 123456789012345))
+ # Write
+ data = bytearray(8)
+ util.set_lint(data, 0, 123456789012345)
client.db_write(1, 60, data)
ULINT (8 bytes, unsigned 0--18446744073709551615)
@@ -218,9 +218,9 @@ ULINT (8 bytes, unsigned 0--18446744073709551615)
value = util.get_ulint(data, 0)
print(f"ULINT = {value}")
- # Write (no set_ulint helper -- use struct directly)
- import struct
- data = bytearray(struct.pack(">Q", 9876543210))
+ # Write
+ data = bytearray(8)
+ util.set_ulint(data, 0, 9876543210)
client.db_write(1, 68, data)
REAL (4 bytes, IEEE 754 float)
diff --git a/pyproject.toml b/pyproject.toml
index 1582e494..4a34cced 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "python-snap7"
-version = "3.0.0"
+version = "3.2.0"
description = "Pure Python S7 communication library for Siemens PLCs"
readme = "README.rst"
authors = [
@@ -66,6 +66,10 @@ markers =[
]
asyncio_mode = "auto"
+[[tool.uv.index]]
+name = "pypi"
+url = "https://pypi.org/simple"
+
[tool.mypy]
ignore_missing_imports = true
strict = true
diff --git a/s7/__init__.py b/s7/__init__.py
index ec91f7e1..76b0f73e 100644
--- a/s7/__init__.py
+++ b/s7/__init__.py
@@ -5,8 +5,7 @@
from s7 import Client # same as: from snap7 import Client
from s7.type import Area # same as: from snap7.type import Area
-This alias exists as a transitional step toward retiring the ``snap7``
-name. For S7CommPlus (S7-1200/1500), use ``s7commplus`` instead.
+Both import names are supported; ``s7`` is recommended for new code.
"""
import importlib
@@ -21,6 +20,7 @@
"async_client",
"cli",
"client",
+ "client_base",
"connection",
"datatypes",
"demo",
@@ -34,6 +34,7 @@
"rate_limiter",
"s7protocol",
"server",
+ "szl",
"tags",
"type",
"util",
diff --git a/snap7/__init__.py b/snap7/__init__.py
index 1dbcfed8..90d8e93e 100644
--- a/snap7/__init__.py
+++ b/snap7/__init__.py
@@ -1,10 +1,9 @@
"""
-The snap7 package (legacy).
+The snap7 compatibility package.
Pure Python implementation of the classic S7 protocol for communicating with
Siemens S7 PLCs. This package is kept for backwards compatibility. For new
-projects, use the ``s7`` package instead, which supports all PLC models and
-automatically selects the best protocol (S7CommPlus or legacy S7)::
+projects, use the ``s7`` package instead::
from s7 import Client
diff --git a/snap7/async_client.py b/snap7/async_client.py
index eff65830..7124a74c 100644
--- a/snap7/async_client.py
+++ b/snap7/async_client.py
@@ -1,11 +1,9 @@
-"""
-Legacy async S7 client implementation.
+"""Asynchronous client for the classic S7 protocol.
Uses asyncio streams for non-blocking I/O with an asyncio.Lock() to serialize
send/receive cycles, ensuring safe concurrent use via asyncio.gather().
-For new projects, use ``s7.AsyncClient`` instead, which supports all PLC
-models and automatically selects the best protocol.
+``s7.AsyncClient`` and ``snap7.AsyncClient`` expose this same implementation.
"""
import asyncio
@@ -297,8 +295,7 @@ async def __aexit__(
class AsyncClient(ClientMixin):
- """
- Legacy async S7 client for classic PUT/GET communication.
+ """Asynchronous client for classic S7 communication.
Uses asyncio streams for non-blocking I/O. An internal asyncio.Lock
serializes each send+receive cycle so that concurrent coroutines
diff --git a/snap7/cli.py b/snap7/cli.py
index 40dec0fe..485074bd 100644
--- a/snap7/cli.py
+++ b/snap7/cli.py
@@ -15,9 +15,8 @@
try:
import click
-except ImportError:
- print("CLI dependencies not installed. Try: pip install python-snap7[cli]")
- raise
+except ImportError as exc:
+ raise ImportError("CLI dependencies not installed. Install them with: pip install python-snap7[cli]") from exc
from snap7 import __version__
from snap7.client import Client
diff --git a/snap7/client.py b/snap7/client.py
index 40f82886..cd88a8b2 100644
--- a/snap7/client.py
+++ b/snap7/client.py
@@ -1,9 +1,7 @@
-"""
-Legacy S7 client implementation.
+"""Synchronous client for the classic S7 protocol.
-Pure Python implementation of the classic S7 protocol. For new projects,
-use ``s7.Client`` instead, which supports all PLC models and
-automatically selects the best protocol.
+``s7.Client`` and ``snap7.Client`` expose this same implementation. The
+``s7`` import name is recommended for new projects.
"""
import copy
@@ -13,6 +11,7 @@
import sys
import threading
import time
+from collections.abc import Sequence
from typing import List, Any, Optional, Tuple, Union, Callable, cast
from datetime import datetime
from ctypes import (
@@ -270,12 +269,10 @@ def __init__(self, cache_key: tuple[int, ...], packets: list[ReadPacket], read_i
class Client(ClientMixin):
- """
- Legacy S7 client for classic PUT/GET communication.
+ """Synchronous client for classic S7 communication.
Supports S7-300, S7-400, S7-1200 and S7-1500 PLCs via the classic S7
- protocol. For new projects, use ``s7.Client`` instead, which
- automatically selects the best protocol for any supported PLC.
+ protocol. S7-1200 and S7-1500 access requires PUT/GET to be enabled.
Examples:
>>> from s7 import Client
@@ -859,9 +856,7 @@ def read_tag(self, tag: "Union[Tag, str]", encoding: str = "latin-1") -> Any:
"""
resolved = Tag.from_string(tag) if isinstance(tag, str) else tag
if resolved.is_symbolic:
- raise NotImplementedError(
- "Symbolic (LID-based) tag access requires S7CommPlus. Use s7.Client instead of snap7.Client."
- )
+ raise NotImplementedError("Symbolic (LID-based) tag access is not supported by the classic S7 client.")
data = self.read_area(Area(resolved.area), resolved.db_number, resolved.byte_offset, resolved.size)
return _decode_tag(resolved, bytearray(data), encoding=encoding)
@@ -878,9 +873,7 @@ def write_tag(self, tag: "Union[Tag, str]", value: Any, encoding: str = "latin-1
"""
resolved = Tag.from_string(tag) if isinstance(tag, str) else tag
if resolved.is_symbolic:
- raise NotImplementedError(
- "Symbolic (LID-based) tag access requires S7CommPlus. Use s7.Client instead of snap7.Client."
- )
+ raise NotImplementedError("Symbolic (LID-based) tag access is not supported by the classic S7 client.")
size = resolved.size
buf = bytearray(size)
# For BOOL writes, we need the current byte to preserve other bits
@@ -890,14 +883,14 @@ def write_tag(self, tag: "Union[Tag, str]", value: Any, encoding: str = "latin-1
_encode_tag(resolved, buf, value, encoding=encoding)
return self.write_area(Area(resolved.area), resolved.db_number, resolved.byte_offset, buf)
- def read_tags(self, tags: "list[Union[Tag, str]]", encoding: str = "latin-1") -> list[Any]:
+ def read_tags(self, tags: "Sequence[Union[Tag, str]]", encoding: str = "latin-1") -> list[Any]:
"""Read multiple tags in a single optimized request.
Uses the multi-variable read optimizer when available to batch
reads into minimal PDU exchanges.
Args:
- tags: List of :class:`~snap7.tags.Tag` instances or address strings.
+ tags: Sequence of :class:`~snap7.tags.Tag` instances or address strings.
encoding: Character encoding for STRING/FSTRING values (default ``"latin-1"``).
Returns:
diff --git a/snap7/server/__init__.py b/snap7/server/__init__.py
index 5e41e1ff..a7d110b4 100644
--- a/snap7/server/__init__.py
+++ b/snap7/server/__init__.py
@@ -1,9 +1,7 @@
-"""
-Legacy S7 server implementation.
+"""Server implementation for the classic S7 protocol.
-Provides a complete S7 server emulator for the classic S7 protocol. For new
-projects, use ``s7.Server`` instead, which supports both legacy S7 and
-S7CommPlus clients.
+Provides a complete server emulator for the classic S7 protocol. For new
+projects, use ``s7.Server`` instead.
"""
import logging
@@ -57,8 +55,7 @@ class CPUState(IntEnum):
class Server:
- """
- Legacy S7 server implementation.
+ """Classic S7 server implementation.
Emulates a Siemens S7 PLC for testing and development purposes.
For new projects, use ``s7.Server`` instead.
diff --git a/snap7/tags.py b/snap7/tags.py
index efe1775a..ae851a08 100644
--- a/snap7/tags.py
+++ b/snap7/tags.py
@@ -107,7 +107,7 @@ class Tag:
``byte_offset`` and ``bit``. Supported on S7-300/400 and on
S7-1200/1500 DBs with "Optimized block access" disabled.
- 2. **Symbolic (LID-based) access** (S7CommPlus, for optimized DBs) —
+ 2. **Symbolic (LID-based) access** (for optimized DBs) —
uses ``access_sequence`` (a list of LID values navigating the
PLC's symbol tree) and optionally ``symbol_crc``. Required for
S7-1200/1500 DBs with "Optimized block access" enabled.
@@ -122,7 +122,7 @@ class Tag:
bit: Bit index (0-7) for BOOL tags; 0 for others.
count: Array count (1 = scalar, >1 = array).
name: Optional tag name for debugging/logging.
- access_sequence: LID path for S7CommPlus symbolic access (optimized DBs).
+ access_sequence: LID path for symbolic access to optimized DBs.
symbol_crc: Symbol CRC for the PLC to validate layout version (0 = no check).
"""
@@ -138,7 +138,7 @@ class Tag:
@property
def is_symbolic(self) -> bool:
- """Whether this Tag uses S7CommPlus symbolic (LID-based) access."""
+ """Whether this Tag uses symbolic (LID-based) access."""
return bool(self.access_sequence)
@property
@@ -184,7 +184,7 @@ def from_access_string(
symbol_crc: int = 0,
count: int = 1,
) -> "Tag":
- """Create a Tag from an S7CommPlus access string for optimized blocks.
+ """Create a Tag from an LID access string for optimized blocks.
The access string is a dot-separated sequence of hex IDs representing
the path through the PLC's symbol tree, e.g. ``"8A0E0001.A"`` (DB1,
diff --git a/tests/test_logging.py b/tests/test_logging.py
index 395259ba..a144e559 100644
--- a/tests/test_logging.py
+++ b/tests/test_logging.py
@@ -34,9 +34,9 @@ def test_update_context(self) -> None:
def test_update_context_partial(self) -> None:
base = logging.getLogger("test.partial")
adapter = PLCLoggerAdapter(base, plc_host="1.2.3.4", rack=0, slot=1)
- adapter.update_context(protocol="s7commplus")
+ adapter.update_context(protocol="classic-s7")
assert adapter.extra is not None
- assert adapter.extra.get("plc_protocol") == "s7commplus"
+ assert adapter.extra.get("plc_protocol") == "classic-s7"
# Host/rack/slot unchanged
assert adapter._prefix == "[1.2.3.4 R0/S1]"
diff --git a/tests/test_server.py b/tests/test_server.py
index e992e58d..49b66f83 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -5,6 +5,7 @@
from ctypes import c_char
from datetime import datetime
from threading import Thread
+from typing import Any
from unittest.mock import MagicMock, patch
import pytest
@@ -142,6 +143,7 @@ def test_event_queue_is_bounded(self) -> None:
events = [first_event]
while event := self.server.pick_event():
+ assert isinstance(event, SrvEvent)
events.append(event)
self.assertEqual(len(events), 1024)
@@ -962,7 +964,7 @@ def tearDownClass(cls) -> None:
cls.server.stop()
cls.server.destroy()
- def _wait_for_record(self, logs, fragment: str, timeout: float = 10.0) -> None:
+ def _wait_for_record(self, logs: Any, fragment: str, timeout: float = 10.0) -> None:
end = time.monotonic() + timeout
while time.monotonic() < end:
if any(fragment in record.getMessage() for record in logs.records):
@@ -970,7 +972,7 @@ def _wait_for_record(self, logs, fragment: str, timeout: float = 10.0) -> None:
time.sleep(0.02)
self.fail(f"log containing {fragment!r} did not appear, got: {[r.getMessage() for r in logs.records]}")
- def _assert_no_warnings(self, logs) -> None:
+ def _assert_no_warnings(self, logs: Any) -> None:
warnings = [r.getMessage() for r in logs.records if r.levelno >= logging.WARNING]
self.assertEqual(warnings, [])
diff --git a/uv.lock b/uv.lock
index 5ddc1e70..8c73ff0b 100644
--- a/uv.lock
+++ b/uv.lock
@@ -94,116 +94,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
]
-[[package]]
-name = "cffi"
-version = "2.1.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "pycparser", marker = "implementation_name != 'PyPy'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/c0/e9/6d7724983b3d5a0908dbf74f64038ade77c18646ff6636ec7894fd392ce1/cffi-2.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0", size = 183837, upload-time = "2026-07-06T21:32:09.655Z" },
- { url = "https://files.pythonhosted.org/packages/69/aa/24580a278de21fd7322635556334d9b535f1cbc00b0a3919447cdf464c65/cffi-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd", size = 184226, upload-time = "2026-07-06T21:32:11.196Z" },
- { url = "https://files.pythonhosted.org/packages/88/a9/02cae418ec4beb282ace11958d9d4737793439d561fadc7e6d56f2e2b354/cffi-2.1.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46", size = 211107, upload-time = "2026-07-06T21:32:12.328Z" },
- { url = "https://files.pythonhosted.org/packages/3b/30/c806937ed5e4c2c7ac30d9d6b76b5dc57ff8b75d83800d9bb11a8253cf2a/cffi-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2", size = 218733, upload-time = "2026-07-06T21:32:13.67Z" },
- { url = "https://files.pythonhosted.org/packages/f9/cf/398272b8bbfd58aa314fda5a7f1cdbb26d1d78ae324a11211521315dd1f0/cffi-2.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd", size = 205543, upload-time = "2026-07-06T21:32:15.148Z" },
- { url = "https://files.pythonhosted.org/packages/45/ca/f91641185cdd90c36d317a9dc7f85e88ef8682d8b300977baff5e23c35d8/cffi-2.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3", size = 205460, upload-time = "2026-07-06T21:32:16.479Z" },
- { url = "https://files.pythonhosted.org/packages/38/66/04781a77b411f0bb5b234d62c1814754ab75ebe455ccff1b08e8d7aae98f/cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0", size = 218760, upload-time = "2026-07-06T21:32:17.98Z" },
- { url = "https://files.pythonhosted.org/packages/d0/9a/bb1d5ed9c3fcae158e9f6391bf309c95d98c2ac37ed56573228471d0af5e/cffi-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43", size = 221230, upload-time = "2026-07-06T21:32:19.407Z" },
- { url = "https://files.pythonhosted.org/packages/41/aa/3c1409cdd26094efacd1c36c66e0a6eb9d4296e4fd4f9901b8b2042f4323/cffi-2.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c", size = 213524, upload-time = "2026-07-06T21:32:20.828Z" },
- { url = "https://files.pythonhosted.org/packages/fa/75/74dfb7c3fc6ebbd408038476bd4c1d7e925c62614e7b9c534ecc34218288/cffi-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd", size = 220341, upload-time = "2026-07-06T21:32:21.9Z" },
- { url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" },
- { url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" },
- { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" },
- { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" },
- { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" },
- { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" },
- { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" },
- { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" },
- { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" },
- { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" },
- { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" },
- { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" },
- { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" },
- { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" },
- { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" },
- { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" },
- { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" },
- { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" },
- { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" },
- { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" },
- { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" },
- { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" },
- { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" },
- { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" },
- { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" },
- { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" },
- { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" },
- { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" },
- { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" },
- { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" },
- { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" },
- { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" },
- { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" },
- { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" },
- { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" },
- { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" },
- { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" },
- { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" },
- { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" },
- { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" },
- { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" },
- { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" },
- { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" },
- { url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" },
- { url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" },
- { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" },
- { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" },
- { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" },
- { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" },
- { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" },
- { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" },
- { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" },
- { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" },
- { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" },
- { url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" },
- { url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" },
- { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" },
- { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" },
- { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" },
- { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" },
- { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" },
- { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" },
- { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" },
- { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" },
- { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" },
- { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" },
- { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" },
- { url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" },
- { url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" },
- { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" },
- { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" },
- { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" },
- { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" },
- { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" },
- { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" },
- { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" },
- { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" },
- { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" },
- { url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" },
- { url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" },
- { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" },
- { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" },
- { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" },
- { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" },
- { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" },
- { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" },
- { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" },
- { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" },
- { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" },
-]
-
[[package]]
name = "charset-normalizer"
version = "3.4.9"
@@ -412,63 +302,6 @@ toml = [
{ name = "tomli", marker = "python_full_version <= '3.11'" },
]
-[[package]]
-name = "cryptography"
-version = "50.0.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
- { name = "typing-extensions", marker = "python_full_version < '3.11'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/bb/ad/5d6702db60b1e40b41ef513b6967ff5848f307d50f8449baf1634f5908f1/cryptography-50.0.1.tar.gz", hash = "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20", size = 880381, upload-time = "2026-08-25T19:45:45.499Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ba/19/797e2aaac9df6a66f1550f49979dc1b1e39ecd2077501c30efa81e8d5d67/cryptography-50.0.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986", size = 4010153, upload-time = "2026-08-25T19:44:03.155Z" },
- { url = "https://files.pythonhosted.org/packages/90/34/9ce9a62ed9dc82ca9fd6a34445b6904af56e5f38b3eae2ed32e49c36053d/cryptography-50.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f", size = 4723133, upload-time = "2026-08-25T19:44:05.461Z" },
- { url = "https://files.pythonhosted.org/packages/57/26/e6d4fc8512a51a5f9ee7bfdbfb853bce1197087df40c9ad993ad370b846f/cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef", size = 4712478, upload-time = "2026-08-25T19:44:07.375Z" },
- { url = "https://files.pythonhosted.org/packages/e6/de/d3cdc2815697aae84126cbd6a030ca7b6b452e28a88b501b836bd3aa7a86/cryptography-50.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8", size = 4730726, upload-time = "2026-08-25T19:44:09.294Z" },
- { url = "https://files.pythonhosted.org/packages/55/32/38c0d344b98c06d34b5df8946565a9c0d6dbf32c8e0730a7f05f0a3c6cab/cryptography-50.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45", size = 5353524, upload-time = "2026-08-25T19:44:11.96Z" },
- { url = "https://files.pythonhosted.org/packages/e1/1b/82f0f0d8858d4432be1af790477edf62aef90324041aa07c57e57bef1af7/cryptography-50.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad", size = 4746720, upload-time = "2026-08-25T19:44:14.051Z" },
- { url = "https://files.pythonhosted.org/packages/29/ba/042ca458b8c64348c768284b5d23e69b92ed53d057ab779fee628564676d/cryptography-50.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49", size = 4361866, upload-time = "2026-08-25T19:44:16.167Z" },
- { url = "https://files.pythonhosted.org/packages/39/3b/e96c1ef71edef71057c7e3c3d982ce8fda554e0c52d0cc19c18845cde3eb/cryptography-50.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f", size = 4730028, upload-time = "2026-08-25T19:44:18.085Z" },
- { url = "https://files.pythonhosted.org/packages/e3/38/45abd72ef63f2e7d0754a6cacf97bd8b69512ace7f6130d24c39ece65da2/cryptography-50.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527", size = 5308405, upload-time = "2026-08-25T19:44:20.197Z" },
- { url = "https://files.pythonhosted.org/packages/85/66/6ccca4722987ddedaa7fc9c3f4708af7431f5535666c174350830888c6b7/cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a", size = 4746230, upload-time = "2026-08-25T19:44:22.376Z" },
- { url = "https://files.pythonhosted.org/packages/13/0e/b1f92e013228111413f2e6743948b80bc24dfd3c1b87ba98ceea16f5df89/cryptography-50.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959", size = 4862596, upload-time = "2026-08-25T19:44:24.472Z" },
- { url = "https://files.pythonhosted.org/packages/7e/22/c3654cccc856e9d682817b04ac3ee79731cb09ca6f95996a95c904de2883/cryptography-50.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b", size = 5014082, upload-time = "2026-08-25T19:44:26.709Z" },
- { url = "https://files.pythonhosted.org/packages/42/8b/cb12b1b60c91b074ca6bf0fdd59aa8f10d8bc5f73af8faece86ef0421b37/cryptography-50.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648", size = 3842826, upload-time = "2026-08-25T19:44:28.784Z" },
- { url = "https://files.pythonhosted.org/packages/5b/f0/424cb557d99aa86ac55da5e2add02e2882e44047b6264f93ade1b975a993/cryptography-50.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f", size = 3973525, upload-time = "2026-08-25T19:44:30.7Z" },
- { url = "https://files.pythonhosted.org/packages/4d/72/3a2711d967977ab5fc80b782837c7e8d1ac7445e764c20c381a265c57ef3/cryptography-50.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a", size = 4708817, upload-time = "2026-08-25T19:44:32.773Z" },
- { url = "https://files.pythonhosted.org/packages/b4/f2/bb1f56e10815b789df0b409a69fa4992ff3d3fef9c72747f4a6b26fed38e/cryptography-50.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367", size = 4697300, upload-time = "2026-08-25T19:44:35.144Z" },
- { url = "https://files.pythonhosted.org/packages/08/bd/ed5396be499ffcf8807a585bfe38b71a1fbdd1c342b4f9b6d0ef5162a946/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5", size = 4716039, upload-time = "2026-08-25T19:44:37.192Z" },
- { url = "https://files.pythonhosted.org/packages/f6/6e/1cf405c5c8e8df7545378048e954792f00b7f2367af8863ce8b8f3e10607/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9", size = 5332388, upload-time = "2026-08-25T19:44:39.16Z" },
- { url = "https://files.pythonhosted.org/packages/47/92/b4317e8c32c4f47b062f5398bd79106b220a124546f42be83bf32b761e2a/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0", size = 4730293, upload-time = "2026-08-25T19:44:41.298Z" },
- { url = "https://files.pythonhosted.org/packages/39/0d/a1e7633e2c744d0f2983320a27e924ef2264c79c56e1a58d5fb0a1cfd413/cryptography-50.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc", size = 4346031, upload-time = "2026-08-25T19:44:43.245Z" },
- { url = "https://files.pythonhosted.org/packages/88/dd/b215616f9bab3fc18510c78a4e5c9f362d77838503c363dc747c7d4f5c6f/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17", size = 4715344, upload-time = "2026-08-25T19:44:45.291Z" },
- { url = "https://files.pythonhosted.org/packages/b1/1b/ec3ebd31741d0e963612c4fe43caa39341b9b1e031e469820e42e4c83918/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6", size = 5287201, upload-time = "2026-08-25T19:44:47.297Z" },
- { url = "https://files.pythonhosted.org/packages/1a/01/0127d11a762b31a9ee0221894f540318761783f3fdc4bc5d057698caebd5/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3", size = 4730023, upload-time = "2026-08-25T19:44:49.435Z" },
- { url = "https://files.pythonhosted.org/packages/9e/b9/e7425ebfb599241a0c1d7000f1b466c3062da66c19d9525031315dff7213/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6", size = 4847362, upload-time = "2026-08-25T19:44:51.94Z" },
- { url = "https://files.pythonhosted.org/packages/2d/fd/60d0ddf4defa12e482c9d5e0f554384d6e8ab25341fd15f060028fd92e6a/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149", size = 4999247, upload-time = "2026-08-25T19:44:53.876Z" },
- { url = "https://files.pythonhosted.org/packages/4d/56/bc4f2b209e766c93372cfcd59b781a0b2b59700f62a969580415b699c2b2/cryptography-50.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf", size = 3825806, upload-time = "2026-08-25T19:44:56.209Z" },
- { url = "https://files.pythonhosted.org/packages/84/a9/ee16a903f13755e914d1eecc482fe64d1f10761c3960e5d8fa6837377aff/cryptography-50.0.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0", size = 4035307, upload-time = "2026-08-25T19:44:58.305Z" },
- { url = "https://files.pythonhosted.org/packages/5e/a5/9ec7e81e8526c0d7a387d73386b2daed3f39e10d81a85930bd1b6bfba65c/cryptography-50.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23", size = 4751900, upload-time = "2026-08-25T19:45:00.401Z" },
- { url = "https://files.pythonhosted.org/packages/7e/3c/0e77bd5ffcf078e9dd27d3074aad6c030d9b10d0bf69329d573c927a188c/cryptography-50.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733", size = 4738357, upload-time = "2026-08-25T19:45:02.786Z" },
- { url = "https://files.pythonhosted.org/packages/27/3a/3c5f80daa4dcd47323c7af8a2fcb90de27a33564d4fcac69846c0972691a/cryptography-50.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88", size = 4758474, upload-time = "2026-08-25T19:45:04.889Z" },
- { url = "https://files.pythonhosted.org/packages/6e/2b/214cf0cf93db9628c3c20c896b229f327f6fb1b20e4b3743d8ad3f00af8b/cryptography-50.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054", size = 5375862, upload-time = "2026-08-25T19:45:07.163Z" },
- { url = "https://files.pythonhosted.org/packages/d6/51/3f9701867a46b6c1740c9b52fc4d3bed6cbdcfedcc9b6e64305c07f39cff/cryptography-50.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5", size = 4772942, upload-time = "2026-08-25T19:45:09.396Z" },
- { url = "https://files.pythonhosted.org/packages/0d/5c/13ea642e08e2544d0f5396122055f4820cfacb3203562197b5967125ea97/cryptography-50.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361", size = 4383347, upload-time = "2026-08-25T19:45:11.659Z" },
- { url = "https://files.pythonhosted.org/packages/84/d5/7d1fe1cb93f91c428093ff234e128c89ba8ea61a6f26aab406081f9b996e/cryptography-50.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71", size = 4758050, upload-time = "2026-08-25T19:45:13.745Z" },
- { url = "https://files.pythonhosted.org/packages/dd/04/557fc5ead96a829e0bc812a3b9dc4a52a2f27e4f7f5950da7ff27653a805/cryptography-50.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80", size = 5332955, upload-time = "2026-08-25T19:45:16.193Z" },
- { url = "https://files.pythonhosted.org/packages/8c/eb/5d7124083e8d8cda8f5b348f544b71ad6f707ad63193758ef4d8e569da02/cryptography-50.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239", size = 4772694, upload-time = "2026-08-25T19:45:18.315Z" },
- { url = "https://files.pythonhosted.org/packages/63/8e/f1f955e0921dd2b6d22eae7e8d24a4c4b638d10735ffbf6a71f99eb0fcb8/cryptography-50.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558", size = 4888413, upload-time = "2026-08-25T19:45:20.4Z" },
- { url = "https://files.pythonhosted.org/packages/1f/ab/89e2b798d2c3925f82e2bb72d5979f3d2f6da2dd22ef4a8cd8b70d920039/cryptography-50.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e", size = 5044355, upload-time = "2026-08-25T19:45:22.353Z" },
- { url = "https://files.pythonhosted.org/packages/99/89/87ef49ffe383ef4e147d27b7bf2088fb0b54ea409dd87b5a89442e5828a5/cryptography-50.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2", size = 3875429, upload-time = "2026-08-25T19:45:24.418Z" },
- { url = "https://files.pythonhosted.org/packages/c7/27/8d207af749c453ee17ea087340b3f2b4adef75aadd1d277b1b129bdda84e/cryptography-50.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94", size = 3974350, upload-time = "2026-08-25T19:45:26.551Z" },
- { url = "https://files.pythonhosted.org/packages/14/9a/6d3a4d7852e22d657438b7bf51f66102c7d71c0e1fafeec652281d0403e5/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f", size = 4698675, upload-time = "2026-08-25T19:45:28.658Z" },
- { url = "https://files.pythonhosted.org/packages/73/35/5c3717edf9e68a0550ce04e28eab493fe545eccd81742af03f6a75fe260b/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671", size = 4707410, upload-time = "2026-08-25T19:45:30.816Z" },
- { url = "https://files.pythonhosted.org/packages/1d/e0/e786934472e3ac4ecdecc7b129a0ca1a2a40dffdafcf2c3ea9d4397f8def/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e", size = 4698378, upload-time = "2026-08-25T19:45:33.043Z" },
- { url = "https://files.pythonhosted.org/packages/51/cf/5b3f53a0b74d122f023476ede40ba5d3e70d5cf475f73b899740d26a4fb2/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6", size = 4706889, upload-time = "2026-08-25T19:45:35.086Z" },
- { url = "https://files.pythonhosted.org/packages/71/44/711e61f7d014be825ef79b285b047292d1bf893732ac1bc030a351fb517f/cryptography-50.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b", size = 3824006, upload-time = "2026-08-25T19:45:37.281Z" },
-]
-
[[package]]
name = "distlib"
version = "0.4.3"
@@ -1014,15 +847,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
]
-[[package]]
-name = "pycparser"
-version = "3.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
-]
-
[[package]]
name = "pygments"
version = "2.20.0"
@@ -1141,7 +965,7 @@ wheels = [
[[package]]
name = "python-snap7"
-version = "3.0.0"
+version = "3.2.0"
source = { editable = "." }
[package.optional-dependencies]
@@ -1166,9 +990,6 @@ doc = [
ppi = [
{ name = "pyserial" },
]
-s7commplus = [
- { name = "cryptography" },
-]
test = [
{ name = "hypothesis" },
{ name = "mypy" },
@@ -1188,7 +1009,6 @@ test = [
requires-dist = [
{ name = "click", marker = "extra == 'cli'" },
{ name = "click", marker = "extra == 'demo'" },
- { name = "cryptography", marker = "extra == 's7commplus'" },
{ name = "hypothesis", marker = "extra == 'test'" },
{ name = "mypy", marker = "extra == 'test'" },
{ name = "pnio-dcp", marker = "extra == 'discovery'" },
@@ -1209,7 +1029,7 @@ requires-dist = [
{ name = "types-setuptools", marker = "extra == 'test'" },
{ name = "uv", marker = "extra == 'test'" },
]
-provides-extras = ["test", "s7commplus", "cli", "demo", "doc", "discovery", "ppi"]
+provides-extras = ["test", "cli", "demo", "doc", "discovery", "ppi"]
[[package]]
name = "requests"