feat: full ISPConfig remote API coverage + re-runnable generator (v0.2)

v0.1 shipped ~15 hand-audited methods across sites/dns/mail/databases/clients.
That's enough for daily ops but every new Tort Host / cWHO feature has been
hitting the wall at the edge of that coverage. This extends the SDK to wrap
every method the panel exposes — 312 of them as of Rackham 2026-04-22,
verified against the live list_functions() introspection call with only one
name-mismatch (``__construct``, a PHP lifecycle artifact, not a real API
method).

The hand-audited helpers stay where they are. Every module now has two
clearly-delimited sections: an auto-generated block at the top (emitted by
tools/gen_methods.py from tools/method_inventory.json), and a HAND-EDIT ONLY
BELOW block at the bottom that survives regeneration. Name collisions
between auto and hand always resolve in favor of the hand version — the
generator emits a ``# skipped foo: hand-audited helper takes precedence``
comment in the auto block for traceability.

Pipeline:
- tools/extract_inventory.py reads remote.*.inc.php + remoting.inc.php,
  pulls docblocks + param defaults, dumps one JSON record per method.
  Regex is balanced-paren aware so ``$params = array()`` defaults don't
  truncate the signature at the wrong close-paren (that footgun hid three
  methods from the first run — sites_aps_available_packages_list,
  sites_aps_instance_delete, openvz_vm_add_from_template).
- tools/method_inventory.json is the committed inventory — future ISPConfig
  upgrades diff against this file to see scope at a glance.
- tools/gen_methods.py groups by method-name prefix onto the module classes
  listed in the README table, emits a 1:1 Python wrapper per method with
  the original PHP filename + line number in the docstring, and ensures
  ``from typing import Any`` is present in preexisting modules before
  emitting ``Any`` type annotations.

New submodules (all auto-generated, wired into ISPConfigClient.__init__):
admin, aps, backups, cron, domains, ftp, misc, monitor, openvz, server,
shell, webdav. Existing modules (sites, dns, mail, databases, clients) got
their auto block filled in and their hand-audited helpers preserved.

Escape hatches on the top-level client:
- raw_call(method, *args) routes an arbitrary method name through the same
  session-management + retry + fault-mapping pipeline the typed wrappers
  use. Fix for "panel shipped a new method, SDK hasn't caught up" —
  callers don't have to reach back into _soap.
- list_functions() wraps get_function_list() for panel introspection.

Fault mapping widened: ``no_client_found`` and "no user account" messages
now map to NotFoundError instead of FaultError, matching the existing
``no_domain_found`` convention. Older code that caught raw FaultError
there will still work (NotFoundError extends ISPConfigError) but callers
can now catch the specific type.

Testing:
- tests/test_unit.py — 12 existing pure-unit tests pass unchanged.
- tests/test_smoke.py — extended from 4 read-only calls to 21. One probe
  per new auto-generated module plus raw_call and list_functions smoke
  tests. Methods gated behind admin permission skip gracefully with a
  documented reason (kayos is a reseller, not admin): monitor_jobqueue_count,
  sites_cron_get, sites_ftp_user_get, openvz_get_free_ip,
  quota_get_by_user. Results against Rackham 2026-04-22:
  28 passed, 5 skipped (all documented admin-only), 0 failed.
- ISPCONFIG_TEST_VERIFY_SSL=0 env-var knob added to conftest for panels
  with self-signed or mismatched certs.

Version bump 0.1.0 -> 0.2.0. README restructured into Hand-audited /
Auto-generated / Escape hatch / Footguns sections with a regeneration
recipe for future ISPConfig upgrades. Ruff per-file SLF001 ignore extended
to every submodule (submodules are all authorized callers of the client's
private ``_call`` dispatcher by design). mypy strict passes; ruff check
passes; ruff format applied across src / tools / tests.
This commit is contained in:
Kayos 2026-04-22 13:58:38 -07:00
parent 9438b4e751
commit 44ce76cb44
28 changed files with 14173 additions and 184 deletions

186
README.md
View file

@ -4,8 +4,10 @@ Python SDK for the ISPConfig remote SOAP API. Internal tooling for the Sulkta
Coop — wraps the panel's SOAP surface so we stop writing throw-away PHP Coop — wraps the panel's SOAP surface so we stop writing throw-away PHP
scripts every time we need to touch a site, zone, or mailbox. scripts every time we need to touch a site, zone, or mailbox.
The SDK covers the methods we actually use today (sites, DNS, mail, databases, v0.2 covers the **full remote API** — every method exposed by ISPConfig's
clients). More methods can be added as needed. `remote/index.php`, 312 of them as of Rackham 2026-04-22. The hand-audited
helpers (stable names, param-order fixes, convenience wrappers) sit on top
of auto-generated wrappers that mirror the PHP surface 1:1.
## Why no zeep? ## Why no zeep?
@ -45,9 +47,35 @@ managed internally — callers don't touch them.
Set `verify_ssl=False` for dev boxes with self-signed certs. Default is `True`. Set `verify_ssl=False` for dev boxes with self-signed certs. Default is `True`.
## Modules at a glance ## Changelog
### `sites` ### 0.2.0 — 2026-04-22
Full-coverage extension. Every remote method ISPConfig exposes is now wrapped
in one of the submodules below. Hand-audited helpers from v0.1 (footgun
fixes, convenience wrappers) are preserved beneath a delimiter and survive
regeneration. New submodules: `admin`, `aps`, `backups`, `cron`, `domains`,
`ftp`, `misc`, `monitor`, `openvz`, `server`, `shell`, `webdav`.
- Code generator at `tools/gen_methods.py` driven by `tools/method_inventory.json`.
- One-command refresh for future ISPConfig upgrades (see below).
- `ISPConfigClient.raw_call(method, *args)` escape hatch for methods not yet
in the inventory.
- `ISPConfigClient.list_functions()` introspects the panel's exposed method list.
- Live smoke test extended from 4 to 21 read-only calls covering every module.
### 0.1.0 — 2026-04-21
Initial release. Hand-audited coverage of sites / dns / mail / databases / clients.
## Modules
### Hand-audited (stable API, verified in prod)
Names normalized for ergonomics; footgun fixes and retries baked in. These
are the wrappers you should prefer.
#### `sites`
```python ```python
c.sites.web_domain_get(156) c.sites.web_domain_get(156)
@ -56,38 +84,94 @@ c.sites.enable_php(156, mode="php-fpm", server_php_id=2, pm="ondemand")
c.sites.enable_letsencrypt(156) c.sites.enable_letsencrypt(156)
``` ```
### `dns` #### `dns`
```python ```python
zone_id = c.dns.zone_get_id("example.com.") # note the trailing dot zone_id = c.dns.zone_get_id("example.com") # trailing dot is stripped either way
zone = c.dns.zone_get(zone_id) zone = c.dns.zone_get(zone_id)
records = c.dns.rr_get_all_by_zone(zone_id) records = c.dns.rr_get_all_by_zone(zone_id)
rr_id = c.dns.a_add(0, {"zone": zone_id, "name": "www", "data": "1.2.3.4", "ttl": 3600, "active": "Y"}) rr_id = c.dns.a_add(0, {"zone": zone_id, "name": "www", "data": "1.2.3.4", "ttl": 3600, "active": "Y"})
``` ```
### `mail` #### `mail`
```python ```python
md = c.mail.domain_get_by_domain("example.com") md = c.mail.domain_get_by_domain("example.com")
users = c.mail.user_get({"email": "%@example.com"}) users = c.mail.user_get({"email": "%@example.com"}) # always a list
new_id = c.mail.create_mailbox(client_id=5, domain="example.com", new_id = c.mail.create_mailbox(client_id=5, domain="example.com",
local_part="info", password="x", quota_mb=2048) local_part="info", password="x", quota_mb=2048)
``` ```
### `databases` #### `databases`
```python ```python
db = c.databases.get(42) db = c.databases.get(42)
c.databases.user_update(client_id=5, primary_id=42, params={"database_password": "x"}) c.databases.user_update(client_id=5, primary_id=42, params={"database_password": "x"})
``` ```
### `clients` #### `clients`
```python ```python
cli = c.clients.get_by_username("jacob") cli = c.clients.get_by_username("jacob")
groupid = c.clients.get_groupid(cli["client_id"]) groupid = c.clients.get_groupid(cli["client_id"])
ids = c.clients.get_all()
``` ```
### Auto-generated (full surface, v0.2)
Wrappers mirror the PHP method names 1:1. Param shapes come from PHPDoc where
available and default to `Any` otherwise. Verified to wire up against Rackham
2026-04-22 but not yet battle-tested in production use — file issues if you
hit one.
| Module | Class | Methods |
|--------------|----------------------|---------|
| `admin` | `AdminModule` | 10 |
| `aps` | `ApsModule` | 10 |
| `backups` | `BackupsModule` | 2 |
| `clients` | `ClientsModule` | 19 |
| `cron` | `CronModule` | 4 |
| `databases` | `DatabasesModule` | 9 |
| `dns` | `DnsModule` | 87 |
| `domains` | `DomainsModule` | 5 |
| `ftp` | `FtpModule` | 5 |
| `mail` | `MailModule` | 82 |
| `misc` | `MiscModule` | 4 |
| `monitor` | `MonitorModule` | 1 |
| `openvz` | `OpenvzModule` | 22 |
| `server` | `ServerModule` | 12 |
| `shell` | `ShellModule` | 4 |
| `sites` | `SitesModule` | 29 |
| `webdav` | `WebdavModule` | 4 |
Every auto-generated method carries a docstring with the original PHP
filename + line number and an `AUTO-GENERATED — param shapes may need
verification` warning. Methods that exist in both the auto and hand-audited
blocks are skipped in the auto block — the hand version wins.
### Escape hatch
For methods not yet in the inventory (e.g. on a newer ISPConfig version than
we've regenerated against):
```python
result = c.raw_call("some_new_method", arg1, arg2)
```
`raw_call` routes through the same session-management + fault-mapping
pipeline as typed methods, so auth/retry still works. To see what the panel
exposes:
```python
funcs = c.list_functions()
```
### Not covered
- `__construct` — PHP constructor, not a real API method.
- Anything gated by ISPConfig plugins we don't have installed — probing via
`raw_call` will return a faultstring like "Method not found".
## Footguns (captured here so nobody has to rediscover them) ## Footguns (captured here so nobody has to rediscover them)
- **`sites_web_domain_update`'s second arg is `client_id`, not `primary_id`.** - **`sites_web_domain_update`'s second arg is `client_id`, not `primary_id`.**
@ -112,9 +196,9 @@ groupid = c.clients.get_groupid(cli["client_id"])
- **`mail_user_get` with a filter dict returns inconsistent shapes.** If the - **`mail_user_get` with a filter dict returns inconsistent shapes.** If the
filter matches multiple rows you get an array; exactly-one match returns a filter matches multiple rows you get an array; exactly-one match returns a
bare map. Our `mail.user_get(filter_dict)` always normalizes to a list. bare map. Our `mail.user_get(filter_dict)` always normalizes to a list.
- **`no_domain_found` fault.** Both `dns_zone_get_id` and (in some paths) - **`no_domain_found` / `no_client_found` faults.** These are typed as
`mail_domain_get_by_domain` return a SOAP fault with `faultcode=no_domain_found` `NotFoundError`; `client_get_by_username("nope")` raises it. Older code
when the record is missing. Mapped to `NotFoundError`. may have caught raw `FaultError` — v0.2 reclassifies both.
- **`dns_a_add` type-column bug.** On some ISPConfig versions (<= ~3.2.11) - **`dns_a_add` type-column bug.** On some ISPConfig versions (<= ~3.2.11)
`dns_a_add` inserts the `dns_rr` row without setting the `type` column, `dns_a_add` inserts the `dns_rr` row without setting the `type` column,
so BIND never emits the record. `DnsModule.a_add(..., fix_type_bug=True)` so BIND never emits the record. `DnsModule.a_add(..., fix_type_bug=True)`
@ -130,6 +214,17 @@ groupid = c.clients.get_groupid(cli["client_id"])
- **Filter dicts on `mail_user_get`.** Pass an int to get one row; pass a - **Filter dicts on `mail_user_get`.** Pass an int to get one row; pass a
dict like `{"email": "%@example.com"}` to get a list. The SOAP method is dict like `{"email": "%@example.com"}` to get a list. The SOAP method is
overloaded and untyped on the wire. overloaded and untyped on the wire.
- **PHP method signatures with `array()` defaults.** ISPConfig's extract
regex used to stop at the first `)` in `$params = array()`, missing three
methods on 3.2.x (`sites_aps_available_packages_list`,
`sites_aps_instance_delete`, `openvz_vm_add_from_template`). Fixed in
`tools/extract_inventory.py` with balanced-paren matching — worth
double-checking on future panel upgrades.
- **Known admin-only methods.** Reseller logins fault with "permission denied"
on a non-admin user. These are skipped in the smoke tests:
`monitor_jobqueue_count`, `sites_cron_get`, `sites_ftp_user_get`,
`openvz_*`, `quota_get_by_user`, `client_templates_get_all`. Use an admin
login if you need them.
## Errors ## Errors
@ -147,7 +242,7 @@ ISPConfigError
## Tests ## Tests
```bash ```bash
pytest # unit tests only, no network pytest # unit tests only, no network (12 tests)
``` ```
To run the live smoke test against a real panel: To run the live smoke test against a real panel:
@ -156,11 +251,47 @@ To run the live smoke test against a real panel:
export ISPCONFIG_TEST_URL="https://panel.example.com:8080/remote/index.php" export ISPCONFIG_TEST_URL="https://panel.example.com:8080/remote/index.php"
export ISPCONFIG_TEST_USER="kayos" export ISPCONFIG_TEST_USER="kayos"
export ISPCONFIG_TEST_PASS="..." export ISPCONFIG_TEST_PASS="..."
export ISPCONFIG_TEST_VERIFY_SSL=0 # for self-signed certs
pytest tests/test_smoke.py pytest tests/test_smoke.py
``` ```
The smoke test is read-only — no `_add` / `_update` / `_delete` calls. Safe 21 read-only calls covering every auto-generated module + the originals. No
against production. `_add` / `_update` / `_delete` calls — safe against production. Methods that
require admin privileges skip gracefully with a documented reason.
## Regenerating for newer ISPConfig versions
When ISPConfig ships a new version on Rackham (or another panel), resync:
```bash
# 1. Pull fresh PHP sources from the panel (sudo required; ask Cobb for creds):
mkdir -p /tmp/ispconfig-php-src
ssh rackham "sudo tar -cz -C /usr/local/ispconfig/interface/lib/classes/remote.d ." \
| tar -xz -C /tmp/ispconfig-php-src/
ssh rackham "sudo cat /usr/local/ispconfig/interface/lib/classes/remoting.inc.php" \
> /tmp/ispconfig-php-src/remoting.inc.php
# 2. Re-extract the method inventory:
python3 tools/extract_inventory.py /tmp/ispconfig-php-src tools/method_inventory.json
# 3. Regenerate wrappers:
python3 tools/gen_methods.py
# 4. Clean up formatting + sanity check:
ruff format src/ tools/
ruff check src/ tools/
mypy src/ispconfig
pytest
# 5. Review and commit:
git diff --stat
git diff tools/method_inventory.json # new methods jump out here
```
Hand-edits below the `HAND-EDIT ONLY BELOW` marker in each module survive
regeneration. Method-name collisions between auto and hand are resolved in
favor of the hand version; the generator emits a `# skipped ...` comment
in the auto block for traceability.
## Development ## Development
@ -171,29 +302,6 @@ mypy src/ispconfig
pytest pytest
``` ```
## Not yet covered
The remote API surface is huge. These are intentionally left out of v0.1 —
add as needed:
- `sites_web_aliasdomain_*`, `sites_web_subdomain_*`,
`sites_web_vhost_{subdomain,aliasdomain}_*`
- `sites_ftp_user_*`, `sites_shell_user_*`, `sites_webdav_user_*`
- `sites_cron_*`
- `sites_web_domain_backup`, `sites_web_domain_backup_list`,
`mail_user_backup`, `mail_user_backup_list`
- `dns_{aaaa,ns,srv,ptr,tlsa,ds,caa,sshfp,dname,loc,hinfo,naptr,rp,alias}_*`
- `dns_slave_*`, `dns_zone_set_dnssec`, `dns_zone_get_by_user`, `dns_templatezone_*`
- `mail_alias_*`, `mail_aliasdomain_*`, `mail_catchall_*`, `mail_filter_*`,
`mail_fetchmail_*`, `mail_mailinglist_*`, `mail_policy_*`,
`mail_relay_{domain,recipient}_*`, `mail_transport_*`,
`mail_{whitelist,blacklist}_*`, `mail_spamfilter_*`, `mail_user_filter_*`
- `client_add`, `client_update`, `client_delete`, `client_change_password`,
`client_template_additional_*`, `client_templates_get_all`,
`client_login_get`
- `server_get`, `server_get_all`, `admin.*`, `monitor.*`, `aps.*`,
`openvz.*`, `domains.*`
## License ## License
MIT — see `LICENSE`. MIT — see `LICENSE`.

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "ispconfig" name = "ispconfig"
version = "0.1.0" version = "0.2.0"
description = "Python SDK for the ISPConfig remote SOAP API — Sulkta Coop internal tooling." description = "Python SDK for the ISPConfig remote SOAP API — Sulkta Coop internal tooling."
readme = "README.md" readme = "README.md"
license = { text = "MIT" } license = { text = "MIT" }
@ -83,10 +83,24 @@ ignore = [
"tests/*" = ["SLF001", "N802"] "tests/*" = ["SLF001", "N802"]
# Submodules call back into the client's dispatcher (`_call`) by design — # Submodules call back into the client's dispatcher (`_call`) by design —
# it's the single chokepoint for session management and retry logic. # it's the single chokepoint for session management and retry logic.
"src/ispconfig/sites.py" = ["SLF001"] # Every submodule needs this waiver; generator output follows the same pattern.
"src/ispconfig/dns.py" = ["SLF001"] "src/ispconfig/admin.py" = ["SLF001"]
"src/ispconfig/mail.py" = ["SLF001"] "src/ispconfig/aps.py" = ["SLF001"]
"src/ispconfig/backups.py" = ["SLF001"]
"src/ispconfig/clients.py" = ["SLF001"] "src/ispconfig/clients.py" = ["SLF001"]
"src/ispconfig/cron.py" = ["SLF001"]
"src/ispconfig/databases.py" = ["SLF001"]
"src/ispconfig/dns.py" = ["SLF001"]
"src/ispconfig/domains.py" = ["SLF001"]
"src/ispconfig/ftp.py" = ["SLF001"]
"src/ispconfig/mail.py" = ["SLF001"]
"src/ispconfig/misc.py" = ["SLF001"]
"src/ispconfig/monitor.py" = ["SLF001"]
"src/ispconfig/openvz.py" = ["SLF001"]
"src/ispconfig/server.py" = ["SLF001"]
"src/ispconfig/shell.py" = ["SLF001"]
"src/ispconfig/sites.py" = ["SLF001"]
"src/ispconfig/webdav.py" = ["SLF001"]
# ---- pytest --------------------------------------------------------- # ---- pytest ---------------------------------------------------------

View file

@ -95,14 +95,14 @@ class SoapTransport:
arg_xml = "".join(_encode_arg(name, value) for name, value in args) arg_xml = "".join(_encode_arg(name, value) for name, value in args)
return ( return (
'<?xml version="1.0" encoding="UTF-8"?>' '<?xml version="1.0" encoding="UTF-8"?>'
'<SOAP-ENV:Envelope' "<SOAP-ENV:Envelope"
' xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"' ' xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"'
' xmlns:ns1="urn:ispconfig"' ' xmlns:ns1="urn:ispconfig"'
' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"'
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
' xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">' ' xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">'
f'<SOAP-ENV:Body><ns1:{method}>{arg_xml}</ns1:{method}></SOAP-ENV:Body>' f"<SOAP-ENV:Body><ns1:{method}>{arg_xml}</ns1:{method}></SOAP-ENV:Body>"
'</SOAP-ENV:Envelope>' "</SOAP-ENV:Envelope>"
) )
@staticmethod @staticmethod
@ -148,7 +148,7 @@ def _encode_arg(name: str, value: Any) -> str:
if isinstance(value, Mapping): if isinstance(value, Mapping):
items = "".join( items = "".join(
f'<item><key xsi:type="xsd:string">{xml_escape(str(k))}</key>' f'<item><key xsi:type="xsd:string">{xml_escape(str(k))}</key>'
f'{_encode_value_tag("value", v)}</item>' f"{_encode_value_tag('value', v)}</item>"
for k, v in value.items() for k, v in value.items()
) )
return f'<{name} xsi:type="ns2:Map" xmlns:ns2="http://xml.apache.org/xml-soap">{items}</{name}>' return f'<{name} xsi:type="ns2:Map" xmlns:ns2="http://xml.apache.org/xml-soap">{items}</{name}>'
@ -223,10 +223,7 @@ def _is_map(el: ET.Element) -> bool:
return False return False
if not all(_local(k.tag) == "item" for k in kids): if not all(_local(k.tag) == "item" for k in kids):
return False return False
return any( return any(any(_local(gk.tag) == "key" for gk in item) for item in kids)
any(_local(gk.tag) == "key" for gk in item)
for item in kids
)
def _is_array(el: ET.Element) -> bool: def _is_array(el: ET.Element) -> bool:

177
src/ispconfig/admin.py Normal file
View file

@ -0,0 +1,177 @@
"""``admin.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class AdminModule:
"""Auto-generated module: Admin.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def config_value_add(self, group: Any, name: Any, value: Any) -> Any:
"""
Auto-generated wrapper for ``config_value_add``.
Source: ``admin.inc.php`` line 181.
PHP signature: ``config_value_add($session_id, $group, $name, $value)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("config_value_add", ("group", group), ("name", name), ("value", value))
def config_value_delete(self, group: Any, name: Any) -> Any:
"""
Auto-generated wrapper for ``config_value_delete``.
Source: ``admin.inc.php`` line 252.
PHP signature: ``config_value_delete($session_id, $group, $name)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("config_value_delete", ("group", group), ("name", name))
def config_value_get(self, group: Any, name: Any) -> Any:
"""
Auto-generated wrapper for ``config_value_get``.
Source: ``admin.inc.php`` line 162.
PHP signature: ``config_value_get($session_id, $group, $name)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("config_value_get", ("group", group), ("name", name))
def config_value_replace(self, group: Any, name: Any, value: Any) -> Any:
"""
Auto-generated wrapper for ``config_value_replace``.
Source: ``admin.inc.php`` line 229.
PHP signature: ``config_value_replace($session_id, $group, $name, $value)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("config_value_replace", ("group", group), ("name", name), ("value", value))
def config_value_update(self, group: Any, name: Any, value: Any) -> Any:
"""
Auto-generated wrapper for ``config_value_update``.
Source: ``admin.inc.php`` line 205.
PHP signature: ``config_value_update($session_id, $group, $name, $value)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("config_value_update", ("group", group), ("name", name), ("value", value))
def sys_datalog_get(self, datalog_id: Any, newer: Any = False) -> Any:
"""
Auto-generated wrapper for ``sys_datalog_get``.
Source: ``admin.inc.php`` line 294.
PHP signature: ``sys_datalog_get($session_id, $datalog_id, $newer = false)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sys_datalog_get", ("datalog_id", datalog_id), ("newer", newer))
def sys_datalog_get_by_tstamp(self, tstamp: Any) -> Any:
"""
Auto-generated wrapper for ``sys_datalog_get_by_tstamp``.
Source: ``admin.inc.php`` line 276.
PHP signature: ``sys_datalog_get_by_tstamp($session_id, $tstamp)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sys_datalog_get_by_tstamp", ("tstamp", tstamp))
def system_config_get(self, section: str, key: str | None = None) -> Any:
"""
Get the values of the system configuration
Source: ``admin.inc.php`` line 137.
PHP signature: ``system_config_get($session_id, $section, $key = null)``.
Params (from PHPDoc):
session (int): id
section (string): of the config field in the table. Could be 'web', 'dns', 'mail', 'dns', 'cron', etc
key (string|null): of the option that you want to get
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("system_config_get", ("section", section), ("key", key))
def system_config_set(self, section: str, key: str, value: Any) -> Any:
"""
Set a value in the system configuration
Source: ``admin.inc.php`` line 113.
PHP signature: ``system_config_set($session_id, $section, $key, $value)``.
Params (from PHPDoc):
session (int): id
section (string): of the config field in the table. Could be 'web', 'dns', 'mail', 'dns', 'cron', etc
key (string): of the option that you want to set
option (string): value that you want to set
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("system_config_set", ("section", section), ("key", key), ("value", value))
def update_record_permissions(
self, tablename: Any, index_field: str, index_value: str, permissions: dict[str, Any] | list[Any]
) -> Any:
"""
set record permissions in any table
Source: ``admin.inc.php`` line 51.
PHP signature: ``update_record_permissions($session_id, $tablename, $index_field, $index_value, $permissions)``.
Params (from PHPDoc):
index_field (string)
index_value (string)
permissions (array)
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"update_record_permissions",
("tablename", tablename),
("index_field", index_field),
("index_value", index_value),
("permissions", permissions),
)
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

154
src/ispconfig/aps.py Normal file
View file

@ -0,0 +1,154 @@
"""``aps.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class ApsModule:
"""Auto-generated module: Aps.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def sites_aps_available_packages_list(self, params: Any = None) -> Any:
"""
Auto-generated wrapper for ``sites_aps_available_packages_list``.
Source: ``aps.inc.php`` line 56.
PHP signature: ``sites_aps_available_packages_list($session_id, $params = array())``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_aps_available_packages_list", ("params", params))
def sites_aps_change_package_status(self, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_aps_change_package_status``.
Source: ``aps.inc.php`` line 201.
PHP signature: ``sites_aps_change_package_status($session_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_aps_change_package_status", ("primary_id", primary_id), ("params", params)
)
def sites_aps_get_package_details(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_aps_get_package_details``.
Source: ``aps.inc.php`` line 78.
PHP signature: ``sites_aps_get_package_details($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_aps_get_package_details", ("primary_id", primary_id))
def sites_aps_get_package_file(self, primary_id: Any, filename: Any) -> Any:
"""
Auto-generated wrapper for ``sites_aps_get_package_file``.
Source: ``aps.inc.php`` line 118.
PHP signature: ``sites_aps_get_package_file($session_id, $primary_id, $filename)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_aps_get_package_file", ("primary_id", primary_id), ("filename", filename))
def sites_aps_get_package_settings(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_aps_get_package_settings``.
Source: ``aps.inc.php`` line 163.
PHP signature: ``sites_aps_get_package_settings($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_aps_get_package_settings", ("primary_id", primary_id))
def sites_aps_install_package(self, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_aps_install_package``.
Source: ``aps.inc.php`` line 231.
PHP signature: ``sites_aps_install_package($session_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_aps_install_package", ("primary_id", primary_id), ("params", params))
def sites_aps_instance_delete(self, primary_id: Any, params: Any = None) -> Any:
"""
Auto-generated wrapper for ``sites_aps_instance_delete``.
Source: ``aps.inc.php`` line 331.
PHP signature: ``sites_aps_instance_delete($session_id, $primary_id, $params = array())``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_aps_instance_delete", ("primary_id", primary_id), ("params", params))
def sites_aps_instance_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_aps_instance_get``.
Source: ``aps.inc.php`` line 303.
PHP signature: ``sites_aps_instance_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_aps_instance_get", ("primary_id", primary_id))
def sites_aps_instance_settings_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_aps_instance_settings_get``.
Source: ``aps.inc.php`` line 317.
PHP signature: ``sites_aps_instance_settings_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_aps_instance_settings_get", ("primary_id", primary_id))
def sites_aps_update_package_list(self) -> Any:
"""
Auto-generated wrapper for ``sites_aps_update_package_list``.
Source: ``aps.inc.php`` line 38.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_aps_update_package_list")
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

59
src/ispconfig/backups.py Normal file
View file

@ -0,0 +1,59 @@
"""``backups.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class BackupsModule:
"""Auto-generated module: Backups.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def sites_web_domain_backup(self, primary_id: Any, action_type: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_domain_backup``.
Source: ``sites.inc.php`` line 926.
PHP signature: ``sites_web_domain_backup($session_id, $primary_id, $action_type)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_web_domain_backup", ("primary_id", primary_id), ("action_type", action_type)
)
def sites_web_domain_backup_list(self, site_id: Any = None) -> Any:
"""
Auto-generated wrapper for ``sites_web_domain_backup_list``.
Source: ``sites.inc.php`` line 912.
PHP signature: ``sites_web_domain_backup_list($session_id, $site_id = null)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_domain_backup_list", ("site_id", site_id))
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

View file

@ -8,11 +8,23 @@ from typing import Any
from . import exceptions as _exc from . import exceptions as _exc
from ._soap import SoapFault, SoapTransport from ._soap import SoapFault, SoapTransport
from .admin import AdminModule
from .aps import ApsModule
from .backups import BackupsModule
from .clients import ClientsModule from .clients import ClientsModule
from .cron import CronModule
from .databases import DatabasesModule from .databases import DatabasesModule
from .dns import DnsModule from .dns import DnsModule
from .domains import DomainsModule
from .ftp import FtpModule
from .mail import MailModule from .mail import MailModule
from .misc import MiscModule
from .monitor import MonitorModule
from .openvz import OpenvzModule
from .server import ServerModule
from .shell import ShellModule
from .sites import SitesModule from .sites import SitesModule
from .webdav import WebdavModule
log = logging.getLogger("ispconfig") log = logging.getLogger("ispconfig")
@ -48,11 +60,26 @@ class ISPConfigClient:
self._transport = SoapTransport(url, verify_ssl=verify_ssl, timeout=timeout) self._transport = SoapTransport(url, verify_ssl=verify_ssl, timeout=timeout)
self._session_id: str | None = None self._session_id: str | None = None
# Hand-audited modules (stable API).
self.sites = SitesModule(self) self.sites = SitesModule(self)
self.dns = DnsModule(self) self.dns = DnsModule(self)
self.mail = MailModule(self) self.mail = MailModule(self)
self.databases = DatabasesModule(self) self.databases = DatabasesModule(self)
self.clients = ClientsModule(self) self.clients = ClientsModule(self)
# Auto-generated modules (full surface, param shapes not yet
# verified in prod use — see per-method docstrings).
self.admin = AdminModule(self)
self.aps = ApsModule(self)
self.backups = BackupsModule(self)
self.cron = CronModule(self)
self.domains = DomainsModule(self)
self.ftp = FtpModule(self)
self.misc = MiscModule(self)
self.monitor = MonitorModule(self)
self.openvz = OpenvzModule(self)
self.server = ServerModule(self)
self.shell = ShellModule(self)
self.webdav = WebdavModule(self)
# ---- context manager --------------------------------------------- # ---- context manager ---------------------------------------------
@ -105,6 +132,38 @@ class ISPConfigClient:
"""Read-only accessor — exposed for debugging, not for API calls.""" """Read-only accessor — exposed for debugging, not for API calls."""
return self._session_id return self._session_id
# ---- escape hatches ----------------------------------------------
def raw_call(self, method: str, *args: Any) -> Any:
"""Invoke an arbitrary ISPConfig remote method by name.
Use this when the SDK doesn't yet wrap the method you need —
newer ISPConfig versions may expose calls our inventory hasn't
caught up with. Args are passed positionally; names are cosmetic
on the wire, so we auto-number them as ``arg1``, ``arg2``, ...
If the call fails, capture ``FaultError.faultcode`` /
``FaultError.faultstring`` and file an issue against
``Sulkta-Coop/ispconfig-py`` so we can add the method properly.
"""
named_args = tuple((f"arg{i + 1}", v) for i, v in enumerate(args))
return self._call(method, *named_args)
def list_functions(self) -> list[str]:
"""Introspect the panel: return the list of remote methods it exposes.
Wrapper for ISPConfig's own ``get_function_list``. Handy when
checking whether your panel version supports a given call before
attempting it via :meth:`raw_call`.
"""
result = self._call("get_function_list")
if isinstance(result, list):
return [str(x) for x in result]
if isinstance(result, dict):
# Some versions return a map keyed by integer index.
return [str(v) for v in result.values()]
return []
# ---- the hot path ------------------------------------------------ # ---- the hot path ------------------------------------------------
def _call(self, method: str, *args: tuple[str, Any]) -> Any: def _call(self, method: str, *args: tuple[str, Any]) -> Any:

View file

@ -11,7 +11,7 @@ ISPConfig's client model has two IDs you'll trip over:
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, cast from typing import TYPE_CHECKING, Any, cast
from .types import Client from .types import Client
@ -23,6 +23,284 @@ class ClientsModule:
def __init__(self, client: ISPConfigClient) -> None: def __init__(self, client: ISPConfigClient) -> None:
self._c = client self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def client_add(self, reseller_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``client_add``.
Source: ``client.inc.php`` line 157.
PHP signature: ``client_add($session_id, $reseller_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_add", ("reseller_id", reseller_id), ("params", params))
def client_change_password(self, client_id: Any, new_password: Any) -> Any:
"""
Changes client password
Source: ``client.inc.php`` line 537.
PHP signature: ``client_change_password($session_id, $client_id, $new_password)``.
Params (from PHPDoc):
session (int): id
client (int): id
new (string): password
Returns: bool - true if success
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"client_change_password", ("client_id", client_id), ("new_password", new_password)
)
def client_delete(self, client_id: Any) -> Any:
"""
Auto-generated wrapper for ``client_delete``.
Source: ``client.inc.php`` line 372.
PHP signature: ``client_delete($session_id, $client_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_delete", ("client_id", client_id))
def client_delete_everything(self, client_id: Any) -> Any:
"""
Auto-generated wrapper for ``client_delete_everything``.
Source: ``client.inc.php`` line 390.
PHP signature: ``client_delete_everything($session_id, $client_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_delete_everything", ("client_id", client_id))
def client_get(self, client_id: Any) -> Any:
"""
Auto-generated wrapper for ``client_get``.
Source: ``client.inc.php`` line 51.
PHP signature: ``client_get($session_id, $client_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_get", ("client_id", client_id))
def client_get_all(self) -> Any:
"""
Get All client_id's from database
Source: ``client.inc.php`` line 512.
Params (from PHPDoc):
Returns: Array - of all client_id's
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_get_all")
def client_get_by_customer_no(self, customer_no: Any) -> Any:
"""
Auto-generated wrapper for ``client_get_by_customer_no``.
Source: ``client.inc.php`` line 485.
PHP signature: ``client_get_by_customer_no($session_id, $customer_no)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_get_by_customer_no", ("customer_no", customer_no))
def client_get_by_groupid(self, group_id: Any) -> Any:
"""
Auto-generated wrapper for ``client_get_by_groupid``.
Source: ``client.inc.php`` line 688.
PHP signature: ``client_get_by_groupid($session_id, $group_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_get_by_groupid", ("group_id", group_id))
def client_get_by_username(self, username: Any) -> Any:
"""
Get sys_user information by username
Source: ``client.inc.php`` line 469.
PHP signature: ``client_get_by_username($session_id, $username)``.
Params (from PHPDoc):
session (int): id
user (string): 's name
Returns: mixed - false if error
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_get_by_username", ("username", username))
def client_get_emailcontact(self, client_id: Any) -> Any:
"""
Auto-generated wrapper for ``client_get_emailcontact``.
Source: ``client.inc.php`` line 118.
PHP signature: ``client_get_emailcontact($session_id, $client_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_get_emailcontact", ("client_id", client_id))
def client_get_groupid(self, client_id: Any) -> Any:
"""
Auto-generated wrapper for ``client_get_groupid``.
Source: ``client.inc.php`` line 137.
PHP signature: ``client_get_groupid($session_id, $client_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_get_groupid", ("client_id", client_id))
def client_get_id(self, sys_userid: Any) -> Any:
"""
Auto-generated wrapper for ``client_get_id``.
Source: ``client.inc.php`` line 96.
PHP signature: ``client_get_id($session_id, $sys_userid)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_get_id", ("sys_userid", sys_userid))
def client_get_sites_by_user(self, sys_userid: Any, sys_groupid: Any) -> Any:
"""
Gets sites by $sys_userid & $sys_groupid
Source: ``sites.inc.php`` line 835.
PHP signature: ``client_get_sites_by_user($session_id, $sys_userid, $sys_groupid)``.
Params (from PHPDoc):
session (int): id
user (int): id
list (array): of groups
Returns: mixed - array with sites by user
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"client_get_sites_by_user", ("sys_userid", sys_userid), ("sys_groupid", sys_groupid)
)
def client_login_get(self, username: Any, password: Any, remote_ip: Any = "") -> Any:
"""
Auto-generated wrapper for ``client_login_get``.
Source: ``client.inc.php`` line 576.
PHP signature: ``client_login_get($session_id,$username,$password,$remote_ip = '')``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"client_login_get", ("username", username), ("password", password), ("remote_ip", remote_ip)
)
def client_template_additional_add(self, client_id: Any, template_id: Any) -> Any:
"""
Auto-generated wrapper for ``client_template_additional_add``.
Source: ``client.inc.php`` line 296.
PHP signature: ``client_template_additional_add($session_id, $client_id, $template_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"client_template_additional_add", ("client_id", client_id), ("template_id", template_id)
)
def client_template_additional_delete(self, client_id: Any, assigned_template_id: Any) -> Any:
"""
Auto-generated wrapper for ``client_template_additional_delete``.
Source: ``client.inc.php`` line 334.
PHP signature: ``client_template_additional_delete($session_id, $client_id, $assigned_template_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"client_template_additional_delete",
("client_id", client_id),
("assigned_template_id", assigned_template_id),
)
def client_template_additional_get(self, client_id: Any) -> Any:
"""
Auto-generated wrapper for ``client_template_additional_get``.
Source: ``client.inc.php`` line 258.
PHP signature: ``client_template_additional_get($session_id, $client_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_template_additional_get", ("client_id", client_id))
def client_templates_get_all(self) -> Any:
"""
Get all client templates
Source: ``client.inc.php`` line 566.
Params (from PHPDoc):
session (int): id
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("client_templates_get_all")
def client_update(self, client_id: Any, reseller_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``client_update``.
Source: ``client.inc.php`` line 187.
PHP signature: ``client_update($session_id, $client_id, $reseller_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"client_update", ("client_id", client_id), ("reseller_id", reseller_id), ("params", params)
)
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----
# ---- HAND-EDIT ONLY BELOW ----
# ---- HAND-EDIT ONLY BELOW ----
def get(self, primary_id: int) -> Client: def get(self, primary_id: int) -> Client:
return cast(Client, self._c._call("client_get", ("primary_id", int(primary_id)))) return cast(Client, self._c._call("client_get", ("primary_id", int(primary_id))))

83
src/ispconfig/cron.py Normal file
View file

@ -0,0 +1,83 @@
"""``cron.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class CronModule:
"""Auto-generated module: Cron.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def sites_cron_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_cron_add``.
Source: ``sites.inc.php`` line 59.
PHP signature: ``sites_cron_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_cron_add", ("client_id", client_id), ("params", params))
def sites_cron_delete(self, cron_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_cron_delete``.
Source: ``sites.inc.php`` line 80.
PHP signature: ``sites_cron_delete($session_id, $cron_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_cron_delete", ("cron_id", cron_id))
def sites_cron_get(self, cron_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_cron_get``.
Source: ``sites.inc.php`` line 45.
PHP signature: ``sites_cron_get($session_id, $cron_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_cron_get", ("cron_id", cron_id))
def sites_cron_update(self, client_id: Any, cron_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_cron_update``.
Source: ``sites.inc.php`` line 69.
PHP signature: ``sites_cron_update($session_id, $client_id, $cron_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_cron_update", ("client_id", client_id), ("cron_id", cron_id), ("params", params)
)
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

View file

@ -20,6 +20,132 @@ class DatabasesModule:
def __init__(self, client: ISPConfigClient) -> None: def __init__(self, client: ISPConfigClient) -> None:
self._c = client self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def sites_database_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_database_add``.
Source: ``sites.inc.php`` line 108.
PHP signature: ``sites_database_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_database_add", ("client_id", client_id), ("params", params))
def sites_database_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_database_delete``.
Source: ``sites.inc.php`` line 185.
PHP signature: ``sites_database_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_database_delete", ("primary_id", primary_id))
def sites_database_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_database_get``.
Source: ``sites.inc.php`` line 93.
PHP signature: ``sites_database_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_database_get", ("primary_id", primary_id))
def sites_database_get_all_by_user(self, client_id: Any) -> Any:
"""
Get all databases by user
Source: ``sites.inc.php`` line 898.
PHP signature: ``sites_database_get_all_by_user($session_id, $client_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_database_get_all_by_user", ("client_id", client_id))
def sites_database_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_database_update``.
Source: ``sites.inc.php`` line 151.
PHP signature: ``sites_database_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_database_update", ("client_id", client_id), ("primary_id", primary_id), ("params", params)
)
def sites_database_user_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_database_user_add``.
Source: ``sites.inc.php`` line 217.
PHP signature: ``sites_database_user_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_database_user_add", ("client_id", client_id), ("params", params))
def sites_database_user_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_database_user_delete``.
Source: ``sites.inc.php`` line 258.
PHP signature: ``sites_database_user_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_database_user_delete", ("primary_id", primary_id))
def sites_database_user_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_database_user_get``.
Source: ``sites.inc.php`` line 203.
PHP signature: ``sites_database_user_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_database_user_get", ("primary_id", primary_id))
def sites_database_user_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_database_user_update``.
Source: ``sites.inc.php`` line 228.
PHP signature: ``sites_database_user_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_database_user_update",
("client_id", client_id),
("primary_id", primary_id),
("params", params),
)
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----
# ---- HAND-EDIT ONLY BELOW ----
# ---- HAND-EDIT ONLY BELOW ----
def get(self, primary_id: int) -> Database: def get(self, primary_id: int) -> Database:
return self._c.sites.database_get(primary_id) return self._c.sites.database_get(primary_id)

File diff suppressed because it is too large Load diff

95
src/ispconfig/domains.py Normal file
View file

@ -0,0 +1,95 @@
"""``domains.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class DomainsModule:
"""Auto-generated module: Domains.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def domains_domain_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``domains_domain_add``.
Source: ``domains.inc.php`` line 59.
PHP signature: ``domains_domain_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("domains_domain_add", ("client_id", client_id), ("params", params))
def domains_domain_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``domains_domain_delete``.
Source: ``domains.inc.php`` line 79.
PHP signature: ``domains_domain_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("domains_domain_delete", ("primary_id", primary_id))
def domains_domain_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``domains_domain_get``.
Source: ``domains.inc.php`` line 45.
PHP signature: ``domains_domain_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("domains_domain_get", ("primary_id", primary_id))
def domains_domain_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``domains_domain_update``.
Source: ``domains.inc.php`` line 69.
PHP signature: ``domains_domain_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"domains_domain_update", ("client_id", client_id), ("primary_id", primary_id), ("params", params)
)
def domains_get_all_by_user(self, group_id: Any) -> Any:
"""
Auto-generated wrapper for ``domains_get_all_by_user``.
Source: ``domains.inc.php`` line 90.
PHP signature: ``domains_get_all_by_user($session_id, $group_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("domains_get_all_by_user", ("group_id", group_id))
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

View file

@ -47,11 +47,14 @@ def map_fault(faultcode: str, faultstring: str) -> ISPConfigError:
return AuthError(faultstring) return AuthError(faultstring)
if "permission denied" in msg or "you do not have the permissions" in msg or "not allowed" in msg: if "permission denied" in msg or "you do not have the permissions" in msg or "not allowed" in msg:
return PermissionError(faultstring) return PermissionError(faultstring)
code_low = faultcode.lower()
if ( if (
"no records found" in msg "no records found" in msg
or "not found" in msg or "not found" in msg
or "no record found" in msg or "no record found" in msg
or "no_domain_found" in faultcode.lower() or "no_domain_found" in code_low
or "no_client_found" in code_low
or "no user account" in msg
or "invalid domain name" in msg or "invalid domain name" in msg
): ):
return NotFoundError(faultstring) return NotFoundError(faultstring)

95
src/ispconfig/ftp.py Normal file
View file

@ -0,0 +1,95 @@
"""``ftp.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class FtpModule:
"""Auto-generated module: Ftp.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def sites_ftp_user_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_ftp_user_add``.
Source: ``sites.inc.php`` line 300.
PHP signature: ``sites_ftp_user_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_ftp_user_add", ("client_id", client_id), ("params", params))
def sites_ftp_user_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_ftp_user_delete``.
Source: ``sites.inc.php`` line 321.
PHP signature: ``sites_ftp_user_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_ftp_user_delete", ("primary_id", primary_id))
def sites_ftp_user_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_ftp_user_get``.
Source: ``sites.inc.php`` line 286.
PHP signature: ``sites_ftp_user_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_ftp_user_get", ("primary_id", primary_id))
def sites_ftp_user_server_get(self, ftp_user: Any) -> Any:
"""
Auto-generated wrapper for ``sites_ftp_user_server_get``.
Source: ``sites.inc.php`` line 332.
PHP signature: ``sites_ftp_user_server_get($session_id, $ftp_user)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_ftp_user_server_get", ("ftp_user", ftp_user))
def sites_ftp_user_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_ftp_user_update``.
Source: ``sites.inc.php`` line 310.
PHP signature: ``sites_ftp_user_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_ftp_user_update", ("client_id", client_id), ("primary_id", primary_id), ("params", params)
)
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

File diff suppressed because it is too large Load diff

81
src/ispconfig/misc.py Normal file
View file

@ -0,0 +1,81 @@
"""``misc.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class MiscModule:
"""Auto-generated module: Misc.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def databasequota_get_by_user(self, client_id: Any) -> Any:
"""
Auto-generated wrapper for ``databasequota_get_by_user``.
Source: ``sites.inc.php`` line 1012.
PHP signature: ``databasequota_get_by_user($session_id, $client_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("databasequota_get_by_user", ("client_id", client_id))
def ftptrafficquota_data(self, client_id: Any, lastdays: Any = 0) -> Any:
"""
Auto-generated wrapper for ``ftptrafficquota_data``.
Source: ``sites.inc.php`` line 997.
PHP signature: ``ftptrafficquota_data($session_id, $client_id, $lastdays = 0)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("ftptrafficquota_data", ("client_id", client_id), ("lastdays", lastdays))
def quota_get_by_user(self, client_id: Any) -> Any:
"""
Auto-generated wrapper for ``quota_get_by_user``.
Source: ``sites.inc.php`` line 970.
PHP signature: ``quota_get_by_user($session_id, $client_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("quota_get_by_user", ("client_id", client_id))
def trafficquota_get_by_user(self, client_id: Any, lastdays: Any = 0) -> Any:
"""
Auto-generated wrapper for ``trafficquota_get_by_user``.
Source: ``sites.inc.php`` line 982.
PHP signature: ``trafficquota_get_by_user($session_id, $client_id, $lastdays = 0)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("trafficquota_get_by_user", ("client_id", client_id), ("lastdays", lastdays))
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

45
src/ispconfig/monitor.py Normal file
View file

@ -0,0 +1,45 @@
"""``monitor.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class MonitorModule:
"""Auto-generated module: Monitor.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def monitor_jobqueue_count(self, server_id: Any = 0) -> Any:
"""
Auto-generated wrapper for ``monitor_jobqueue_count``.
Source: ``monitor.inc.php`` line 36.
PHP signature: ``monitor_jobqueue_count($session_id, $server_id = 0)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("monitor_jobqueue_count", ("server_id", server_id))
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

319
src/ispconfig/openvz.py Normal file
View file

@ -0,0 +1,319 @@
"""``openvz.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class OpenvzModule:
"""Auto-generated module: Openvz.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def openvz_get_free_ip(self, server_id: Any = 0) -> Any:
"""
Auto-generated wrapper for ``openvz_get_free_ip``.
Source: ``openvz.inc.php`` line 151.
PHP signature: ``openvz_get_free_ip($session_id, $server_id = 0)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_get_free_ip", ("server_id", server_id))
def openvz_ip_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_ip_add``.
Source: ``openvz.inc.php`` line 175.
PHP signature: ``openvz_ip_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_ip_add", ("client_id", client_id), ("params", params))
def openvz_ip_delete(self, ip_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_ip_delete``.
Source: ``openvz.inc.php`` line 196.
PHP signature: ``openvz_ip_delete($session_id, $ip_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_ip_delete", ("ip_id", ip_id))
def openvz_ip_get(self, ip_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_ip_get``.
Source: ``openvz.inc.php`` line 137.
PHP signature: ``openvz_ip_get($session_id, $ip_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_ip_get", ("ip_id", ip_id))
def openvz_ip_update(self, client_id: Any, ip_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_ip_update``.
Source: ``openvz.inc.php`` line 185.
PHP signature: ``openvz_ip_update($session_id, $client_id, $ip_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"openvz_ip_update", ("client_id", client_id), ("ip_id", ip_id), ("params", params)
)
def openvz_ostemplate_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_ostemplate_add``.
Source: ``openvz.inc.php`` line 59.
PHP signature: ``openvz_ostemplate_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_ostemplate_add", ("client_id", client_id), ("params", params))
def openvz_ostemplate_delete(self, ostemplate_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_ostemplate_delete``.
Source: ``openvz.inc.php`` line 80.
PHP signature: ``openvz_ostemplate_delete($session_id, $ostemplate_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_ostemplate_delete", ("ostemplate_id", ostemplate_id))
def openvz_ostemplate_get(self, ostemplate_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_ostemplate_get``.
Source: ``openvz.inc.php`` line 45.
PHP signature: ``openvz_ostemplate_get($session_id, $ostemplate_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_ostemplate_get", ("ostemplate_id", ostemplate_id))
def openvz_ostemplate_update(self, client_id: Any, ostemplate_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_ostemplate_update``.
Source: ``openvz.inc.php`` line 69.
PHP signature: ``openvz_ostemplate_update($session_id, $client_id, $ostemplate_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"openvz_ostemplate_update",
("client_id", client_id),
("ostemplate_id", ostemplate_id),
("params", params),
)
def openvz_template_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_template_add``.
Source: ``openvz.inc.php`` line 105.
PHP signature: ``openvz_template_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_template_add", ("client_id", client_id), ("params", params))
def openvz_template_delete(self, template_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_template_delete``.
Source: ``openvz.inc.php`` line 126.
PHP signature: ``openvz_template_delete($session_id, $template_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_template_delete", ("template_id", template_id))
def openvz_template_get(self, template_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_template_get``.
Source: ``openvz.inc.php`` line 91.
PHP signature: ``openvz_template_get($session_id, $template_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_template_get", ("template_id", template_id))
def openvz_template_update(self, client_id: Any, template_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_template_update``.
Source: ``openvz.inc.php`` line 115.
PHP signature: ``openvz_template_update($session_id, $client_id, $template_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"openvz_template_update",
("client_id", client_id),
("template_id", template_id),
("params", params),
)
def openvz_vm_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_vm_add``.
Source: ``openvz.inc.php`` line 241.
PHP signature: ``openvz_vm_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_vm_add", ("client_id", client_id), ("params", params))
def openvz_vm_add_from_template(
self, client_id: Any, ostemplate_id: Any, template_id: Any, override_params: Any = None
) -> Any:
"""
Auto-generated wrapper for ``openvz_vm_add_from_template``.
Source: ``openvz.inc.php`` line 251.
PHP signature: ``openvz_vm_add_from_template($session_id, $client_id, $ostemplate_id, $template_id, $override_params = array())``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"openvz_vm_add_from_template",
("client_id", client_id),
("ostemplate_id", ostemplate_id),
("template_id", template_id),
("override_params", override_params),
)
def openvz_vm_delete(self, vm_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_vm_delete``.
Source: ``openvz.inc.php`` line 342.
PHP signature: ``openvz_vm_delete($session_id, $vm_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_vm_delete", ("vm_id", vm_id))
def openvz_vm_get(self, vm_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_vm_get``.
Source: ``openvz.inc.php`` line 207.
PHP signature: ``openvz_vm_get($session_id, $vm_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_vm_get", ("vm_id", vm_id))
def openvz_vm_get_by_client(self, client_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_vm_get_by_client``.
Source: ``openvz.inc.php`` line 221.
PHP signature: ``openvz_vm_get_by_client($session_id, $client_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_vm_get_by_client", ("client_id", client_id))
def openvz_vm_restart(self, vm_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_vm_restart``.
Source: ``openvz.inc.php`` line 437.
PHP signature: ``openvz_vm_restart($session_id, $vm_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_vm_restart", ("vm_id", vm_id))
def openvz_vm_start(self, vm_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_vm_start``.
Source: ``openvz.inc.php`` line 353.
PHP signature: ``openvz_vm_start($session_id, $vm_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_vm_start", ("vm_id", vm_id))
def openvz_vm_stop(self, vm_id: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_vm_stop``.
Source: ``openvz.inc.php`` line 395.
PHP signature: ``openvz_vm_stop($session_id, $vm_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("openvz_vm_stop", ("vm_id", vm_id))
def openvz_vm_update(self, client_id: Any, vm_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``openvz_vm_update``.
Source: ``openvz.inc.php`` line 331.
PHP signature: ``openvz_vm_update($session_id, $client_id, $vm_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"openvz_vm_update", ("client_id", client_id), ("vm_id", vm_id), ("params", params)
)
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

215
src/ispconfig/server.py Normal file
View file

@ -0,0 +1,215 @@
"""``server.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class ServerModule:
"""Auto-generated module: Server.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def server_config_set(self, server_id: Any, section: str, key: str, value: Any) -> Any:
"""
Set a value in the server configuration
Source: ``server.inc.php`` line 153.
PHP signature: ``server_config_set($session_id, $server_id, $section, $key, $value)``.
Params (from PHPDoc):
session (int): id
server (int): id
section (string): of the config field in the server table. Could be 'web', 'dns', 'mail', 'dns', 'cron', etc
key (string): of the option that you want to set
option (string): value that you want to set
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"server_config_set",
("server_id", server_id),
("section", section),
("key", key),
("value", value),
)
def server_get(self, server_id: Any = None, section: str = "") -> Any:
"""
Gets the server configuration
Source: ``server.inc.php`` line 116.
PHP signature: ``server_get($session_id, $server_id = null, $section ='')``.
Params (from PHPDoc):
session (int): id
server (int): id
section (string): of the config field in the server table. Could be 'web', 'dns', 'mail', 'dns', 'cron', etc
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("server_get", ("server_id", server_id), ("section", section))
def server_get_all(self) -> Any:
"""
Gets a list of all servers
Source: ``server.inc.php`` line 179.
Params (from PHPDoc):
server_name (int)
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("server_get_all")
def server_get_app_version(self, server_id: Any = 0) -> Any:
"""
Auto-generated wrapper for ``server_get_app_version``.
Source: ``server.inc.php`` line 238.
PHP signature: ``server_get_app_version($session_id, $server_id = 0)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("server_get_app_version", ("server_id", server_id))
def server_get_functions(self, server_id: int) -> Any:
"""
Gets the functions of a server by server_id
Source: ``server.inc.php`` line 223.
PHP signature: ``server_get_functions($session_id, $server_id)``.
Params (from PHPDoc):
server_id (int)
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("server_get_functions", ("server_id", server_id))
def server_get_php_versions(self, server_id: Any, php: Any, get_full_data: Any = False) -> Any:
"""
Auto-generated wrapper for ``server_get_php_versions``.
Source: ``server.inc.php`` line 259.
PHP signature: ``server_get_php_versions($session_id, $server_id, $php, $get_full_data = false)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"server_get_php_versions",
("server_id", server_id),
("php", php),
("get_full_data", get_full_data),
)
def server_get_serverid_by_ip(self, ipaddress: Any) -> Any:
"""
Gets the server configuration
Source: ``server.inc.php`` line 49.
PHP signature: ``server_get_serverid_by_ip($session_id, $ipaddress)``.
Params (from PHPDoc):
session (int): id
server (int): id
section (string): of the config field in the server table. Could be 'web', 'dns', 'mail', 'dns', 'cron', etc
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("server_get_serverid_by_ip", ("ipaddress", ipaddress))
def server_get_serverid_by_name(self, server_name: int) -> Any:
"""
Gets the server_id by server_name
Source: ``server.inc.php`` line 201.
PHP signature: ``server_get_serverid_by_name($session_id, $server_name)``.
Params (from PHPDoc):
server_name (int)
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("server_get_serverid_by_name", ("server_name", server_name))
def server_ip_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``server_ip_add``.
Source: ``server.inc.php`` line 78.
PHP signature: ``server_ip_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("server_ip_add", ("client_id", client_id), ("params", params))
def server_ip_delete(self, ip_id: Any) -> Any:
"""
Auto-generated wrapper for ``server_ip_delete``.
Source: ``server.inc.php`` line 99.
PHP signature: ``server_ip_delete($session_id, $ip_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("server_ip_delete", ("ip_id", ip_id))
def server_ip_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``server_ip_get``.
Source: ``server.inc.php`` line 64.
PHP signature: ``server_ip_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("server_ip_get", ("primary_id", primary_id))
def server_ip_update(self, client_id: Any, ip_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``server_ip_update``.
Source: ``server.inc.php`` line 88.
PHP signature: ``server_ip_update($session_id, $client_id, $ip_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"server_ip_update", ("client_id", client_id), ("ip_id", ip_id), ("params", params)
)
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

86
src/ispconfig/shell.py Normal file
View file

@ -0,0 +1,86 @@
"""``shell.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class ShellModule:
"""Auto-generated module: Shell.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def sites_shell_user_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_shell_user_add``.
Source: ``sites.inc.php`` line 368.
PHP signature: ``sites_shell_user_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_shell_user_add", ("client_id", client_id), ("params", params))
def sites_shell_user_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_shell_user_delete``.
Source: ``sites.inc.php`` line 389.
PHP signature: ``sites_shell_user_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_shell_user_delete", ("primary_id", primary_id))
def sites_shell_user_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_shell_user_get``.
Source: ``sites.inc.php`` line 354.
PHP signature: ``sites_shell_user_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_shell_user_get", ("primary_id", primary_id))
def sites_shell_user_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_shell_user_update``.
Source: ``sites.inc.php`` line 378.
PHP signature: ``sites_shell_user_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_shell_user_update",
("client_id", client_id),
("primary_id", primary_id),
("params", params),
)
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

View file

@ -31,6 +31,409 @@ class SitesModule:
def __init__(self, client: ISPConfigClient) -> None: def __init__(self, client: ISPConfigClient) -> None:
self._c = client self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def sites_web_aliasdomain_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_aliasdomain_add``.
Source: ``sites.inc.php`` line 642.
PHP signature: ``sites_web_aliasdomain_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_aliasdomain_add", ("client_id", client_id), ("params", params))
def sites_web_aliasdomain_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_aliasdomain_delete``.
Source: ``sites.inc.php`` line 663.
PHP signature: ``sites_web_aliasdomain_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_aliasdomain_delete", ("primary_id", primary_id))
def sites_web_aliasdomain_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_aliasdomain_get``.
Source: ``sites.inc.php`` line 628.
PHP signature: ``sites_web_aliasdomain_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_aliasdomain_get", ("primary_id", primary_id))
def sites_web_aliasdomain_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_aliasdomain_update``.
Source: ``sites.inc.php`` line 652.
PHP signature: ``sites_web_aliasdomain_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_web_aliasdomain_update",
("client_id", client_id),
("primary_id", primary_id),
("params", params),
)
def sites_web_domain_add(self, client_id: Any, params: Any, readonly: Any = False) -> Any:
"""
Auto-generated wrapper for ``sites_web_domain_add``.
Source: ``sites.inc.php`` line 416.
PHP signature: ``sites_web_domain_add($session_id, $client_id, $params, $readonly = false)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_web_domain_add", ("client_id", client_id), ("params", params), ("readonly", readonly)
)
def sites_web_domain_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_domain_delete``.
Source: ``sites.inc.php`` line 471.
PHP signature: ``sites_web_domain_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_domain_delete", ("primary_id", primary_id))
def sites_web_domain_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_domain_get``.
Source: ``sites.inc.php`` line 402.
PHP signature: ``sites_web_domain_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_domain_get", ("primary_id", primary_id))
def sites_web_domain_set_status(self, primary_id: Any, status: Any) -> Any:
"""
Change domains status
Source: ``sites.inc.php`` line 870.
PHP signature: ``sites_web_domain_set_status($session_id, $primary_id, $status)``.
Params (from PHPDoc):
session (int): id
site (int): id
active (string): or inactive string
Returns: mixed - false if error
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_domain_set_status", ("primary_id", primary_id), ("status", status))
def sites_web_domain_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_domain_update``.
Source: ``sites.inc.php`` line 451.
PHP signature: ``sites_web_domain_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_web_domain_update",
("client_id", client_id),
("primary_id", primary_id),
("params", params),
)
def sites_web_folder_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_folder_add``.
Source: ``sites.inc.php`` line 738.
PHP signature: ``sites_web_folder_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_folder_add", ("client_id", client_id), ("params", params))
def sites_web_folder_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_folder_delete``.
Source: ``sites.inc.php`` line 759.
PHP signature: ``sites_web_folder_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_folder_delete", ("primary_id", primary_id))
def sites_web_folder_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_folder_get``.
Source: ``sites.inc.php`` line 724.
PHP signature: ``sites_web_folder_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_folder_get", ("primary_id", primary_id))
def sites_web_folder_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_folder_update``.
Source: ``sites.inc.php`` line 748.
PHP signature: ``sites_web_folder_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_web_folder_update",
("client_id", client_id),
("primary_id", primary_id),
("params", params),
)
def sites_web_folder_user_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_folder_user_add``.
Source: ``sites.inc.php`` line 796.
PHP signature: ``sites_web_folder_user_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_folder_user_add", ("client_id", client_id), ("params", params))
def sites_web_folder_user_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_folder_user_delete``.
Source: ``sites.inc.php`` line 817.
PHP signature: ``sites_web_folder_user_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_folder_user_delete", ("primary_id", primary_id))
def sites_web_folder_user_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_folder_user_get``.
Source: ``sites.inc.php`` line 782.
PHP signature: ``sites_web_folder_user_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_folder_user_get", ("primary_id", primary_id))
def sites_web_folder_user_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_folder_user_update``.
Source: ``sites.inc.php`` line 806.
PHP signature: ``sites_web_folder_user_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_web_folder_user_update",
("client_id", client_id),
("primary_id", primary_id),
("params", params),
)
def sites_web_subdomain_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_subdomain_add``.
Source: ``sites.inc.php`` line 690.
PHP signature: ``sites_web_subdomain_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_subdomain_add", ("client_id", client_id), ("params", params))
def sites_web_subdomain_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_subdomain_delete``.
Source: ``sites.inc.php`` line 711.
PHP signature: ``sites_web_subdomain_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_subdomain_delete", ("primary_id", primary_id))
def sites_web_subdomain_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_subdomain_get``.
Source: ``sites.inc.php`` line 676.
PHP signature: ``sites_web_subdomain_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_subdomain_get", ("primary_id", primary_id))
def sites_web_subdomain_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_subdomain_update``.
Source: ``sites.inc.php`` line 700.
PHP signature: ``sites_web_subdomain_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_web_subdomain_update",
("client_id", client_id),
("primary_id", primary_id),
("params", params),
)
def sites_web_vhost_aliasdomain_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_vhost_aliasdomain_add``.
Source: ``sites.inc.php`` line 498.
PHP signature: ``sites_web_vhost_aliasdomain_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_vhost_aliasdomain_add", ("client_id", client_id), ("params", params))
def sites_web_vhost_aliasdomain_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_vhost_aliasdomain_delete``.
Source: ``sites.inc.php`` line 543.
PHP signature: ``sites_web_vhost_aliasdomain_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_vhost_aliasdomain_delete", ("primary_id", primary_id))
def sites_web_vhost_aliasdomain_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_vhost_aliasdomain_get``.
Source: ``sites.inc.php`` line 484.
PHP signature: ``sites_web_vhost_aliasdomain_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_vhost_aliasdomain_get", ("primary_id", primary_id))
def sites_web_vhost_aliasdomain_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_vhost_aliasdomain_update``.
Source: ``sites.inc.php`` line 523.
PHP signature: ``sites_web_vhost_aliasdomain_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_web_vhost_aliasdomain_update",
("client_id", client_id),
("primary_id", primary_id),
("params", params),
)
def sites_web_vhost_subdomain_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_vhost_subdomain_add``.
Source: ``sites.inc.php`` line 570.
PHP signature: ``sites_web_vhost_subdomain_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_vhost_subdomain_add", ("client_id", client_id), ("params", params))
def sites_web_vhost_subdomain_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_vhost_subdomain_delete``.
Source: ``sites.inc.php`` line 615.
PHP signature: ``sites_web_vhost_subdomain_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_vhost_subdomain_delete", ("primary_id", primary_id))
def sites_web_vhost_subdomain_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_vhost_subdomain_get``.
Source: ``sites.inc.php`` line 556.
PHP signature: ``sites_web_vhost_subdomain_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_web_vhost_subdomain_get", ("primary_id", primary_id))
def sites_web_vhost_subdomain_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_web_vhost_subdomain_update``.
Source: ``sites.inc.php`` line 595.
PHP signature: ``sites_web_vhost_subdomain_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_web_vhost_subdomain_update",
("client_id", client_id),
("primary_id", primary_id),
("params", params),
)
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----
# ---- HAND-EDIT ONLY BELOW ----
# ---- HAND-EDIT ONLY BELOW ----
# ---- web domain --------------------------------------------------- # ---- web domain ---------------------------------------------------
def web_domain_get(self, primary_id: int) -> WebDomain: def web_domain_get(self, primary_id: int) -> WebDomain:
@ -42,12 +445,14 @@ class SitesModule:
``client_id=0`` creates an admin-owned site. ``client_id=0`` creates an admin-owned site.
""" """
return int(self._c._call( return int(
self._c._call(
"sites_web_domain_add", "sites_web_domain_add",
("client_id", int(client_id)), ("client_id", int(client_id)),
("params", dict(params)), ("params", dict(params)),
("read_only", bool(read_only)), ("read_only", bool(read_only)),
)) )
)
def web_domain_update(self, client_id: int, primary_id: int, params: Mapping[str, Any]) -> int: def web_domain_update(self, client_id: int, primary_id: int, params: Mapping[str, Any]) -> int:
"""Update a site. """Update a site.
@ -56,23 +461,27 @@ class SitesModule:
The second positional arg is ``client_id``, not ``primary_id``. The second positional arg is ``client_id``, not ``primary_id``.
Pass 0 for admin-owned. See module docstring. Pass 0 for admin-owned. See module docstring.
""" """
return int(self._c._call( return int(
self._c._call(
"sites_web_domain_update", "sites_web_domain_update",
("client_id", int(client_id)), ("client_id", int(client_id)),
("primary_id", int(primary_id)), ("primary_id", int(primary_id)),
("params", dict(params)), ("params", dict(params)),
)) )
)
def web_domain_delete(self, primary_id: int) -> int: def web_domain_delete(self, primary_id: int) -> int:
return int(self._c._call("sites_web_domain_delete", ("primary_id", int(primary_id)))) return int(self._c._call("sites_web_domain_delete", ("primary_id", int(primary_id))))
def web_domain_set_status(self, primary_id: int, status: str) -> int: def web_domain_set_status(self, primary_id: int, status: str) -> int:
"""``status`` is typically ``'active'`` or ``'inactive'``.""" """``status`` is typically ``'active'`` or ``'inactive'``."""
return int(self._c._call( return int(
self._c._call(
"sites_web_domain_set_status", "sites_web_domain_set_status",
("primary_id", int(primary_id)), ("primary_id", int(primary_id)),
("status", status), ("status", status),
)) )
)
# ---- helpers ------------------------------------------------------ # ---- helpers ------------------------------------------------------
@ -129,11 +538,13 @@ class SitesModule:
return cast(Database, self._c._call("sites_database_get", ("primary_id", int(primary_id)))) return cast(Database, self._c._call("sites_database_get", ("primary_id", int(primary_id))))
def database_add(self, client_id: int, params: Mapping[str, Any]) -> int: def database_add(self, client_id: int, params: Mapping[str, Any]) -> int:
return int(self._c._call( return int(
self._c._call(
"sites_database_add", "sites_database_add",
("client_id", int(client_id)), ("client_id", int(client_id)),
("params", dict(params)), ("params", dict(params)),
)) )
)
def database_delete(self, primary_id: int) -> int: def database_delete(self, primary_id: int) -> int:
return int(self._c._call("sites_database_delete", ("primary_id", int(primary_id)))) return int(self._c._call("sites_database_delete", ("primary_id", int(primary_id))))
@ -142,16 +553,20 @@ class SitesModule:
return cast(DatabaseUser, self._c._call("sites_database_user_get", ("primary_id", int(primary_id)))) return cast(DatabaseUser, self._c._call("sites_database_user_get", ("primary_id", int(primary_id))))
def database_user_add(self, client_id: int, params: Mapping[str, Any]) -> int: def database_user_add(self, client_id: int, params: Mapping[str, Any]) -> int:
return int(self._c._call( return int(
self._c._call(
"sites_database_user_add", "sites_database_user_add",
("client_id", int(client_id)), ("client_id", int(client_id)),
("params", dict(params)), ("params", dict(params)),
)) )
)
def database_user_update(self, client_id: int, primary_id: int, params: Mapping[str, Any]) -> int: def database_user_update(self, client_id: int, primary_id: int, params: Mapping[str, Any]) -> int:
return int(self._c._call( return int(
self._c._call(
"sites_database_user_update", "sites_database_user_update",
("client_id", int(client_id)), ("client_id", int(client_id)),
("primary_id", int(primary_id)), ("primary_id", int(primary_id)),
("params", dict(params)), ("params", dict(params)),
)) )
)

86
src/ispconfig/webdav.py Normal file
View file

@ -0,0 +1,86 @@
"""``webdav.*`` — auto-generated ISPConfig remote-API wrappers.
This module is produced by ``tools/gen_methods.py`` from the
``tools/method_inventory.json`` catalog. Hand-edits go below the
``---- HAND-EDIT ONLY BELOW ----`` marker they survive regeneration.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .client import ISPConfigClient
class WebdavModule:
"""Auto-generated module: Webdav.
All methods below the ``AUTO-GENERATED START`` marker are produced
by ``tools/gen_methods.py``. Do not hand-edit that block changes
will be overwritten on the next regeneration. Add helpers and
overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.
"""
def __init__(self, client: ISPConfigClient) -> None:
self._c = client
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
# Regenerate with: python3 tools/gen_methods.py
def sites_webdav_user_add(self, client_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_webdav_user_add``.
Source: ``sites.inc.php`` line 1043.
PHP signature: ``sites_webdav_user_add($session_id, $client_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_webdav_user_add", ("client_id", client_id), ("params", params))
def sites_webdav_user_delete(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_webdav_user_delete``.
Source: ``sites.inc.php`` line 1064.
PHP signature: ``sites_webdav_user_delete($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_webdav_user_delete", ("primary_id", primary_id))
def sites_webdav_user_get(self, primary_id: Any) -> Any:
"""
Auto-generated wrapper for ``sites_webdav_user_get``.
Source: ``sites.inc.php`` line 1029.
PHP signature: ``sites_webdav_user_get($session_id, $primary_id)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call("sites_webdav_user_get", ("primary_id", primary_id))
def sites_webdav_user_update(self, client_id: Any, primary_id: Any, params: Any) -> Any:
"""
Auto-generated wrapper for ``sites_webdav_user_update``.
Source: ``sites.inc.php`` line 1053.
PHP signature: ``sites_webdav_user_update($session_id, $client_id, $primary_id, $params)``.
AUTO-GENERATED - param shapes may need verification against your
ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.
"""
return self._c._call(
"sites_webdav_user_update",
("client_id", client_id),
("primary_id", primary_id),
("params", params),
)
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----

View file

@ -3,6 +3,9 @@
Live-smoke tests read ``ISPCONFIG_TEST_URL``, ``ISPCONFIG_TEST_USER``, and Live-smoke tests read ``ISPCONFIG_TEST_URL``, ``ISPCONFIG_TEST_USER``, and
``ISPCONFIG_TEST_PASS`` from the environment. If any is missing, those tests ``ISPCONFIG_TEST_PASS`` from the environment. If any is missing, those tests
are skipped so the default ``pytest`` run on a laptop never phones home. are skipped so the default ``pytest`` run on a laptop never phones home.
Set ``ISPCONFIG_TEST_VERIFY_SSL=0`` for panels with self-signed or
mismatched certs.
""" """
from __future__ import annotations from __future__ import annotations
@ -19,4 +22,5 @@ def live_creds() -> dict[str, str]:
password = os.environ.get("ISPCONFIG_TEST_PASS") password = os.environ.get("ISPCONFIG_TEST_PASS")
if not (url and user and password): if not (url and user and password):
pytest.skip("live smoke test: set ISPCONFIG_TEST_URL/USER/PASS to enable") pytest.skip("live smoke test: set ISPCONFIG_TEST_URL/USER/PASS to enable")
return {"url": url, "user": user, "password": password} verify = os.environ.get("ISPCONFIG_TEST_VERIFY_SSL", "1")
return {"url": url, "user": user, "password": password, "verify": verify}

View file

@ -5,23 +5,40 @@ Gated on env vars: ``ISPCONFIG_TEST_URL``, ``ISPCONFIG_TEST_USER``,
They are **read-only** no ``_add`` / ``_update`` / ``_delete`` calls. Safe They are **read-only** no ``_add`` / ``_update`` / ``_delete`` calls. Safe
to run against production (Rackham). to run against production (Rackham).
Every new auto-generated module gets at least one read-only call here so we
know the wrappers actually wire up against a live panel. Methods that the
API user lacks permission for (admin-only, etc.) are documented skips
see the README's "Known admin-only" list.
""" """
from __future__ import annotations from __future__ import annotations
from collections.abc import Iterator
import pytest import pytest
from ispconfig import ISPConfigClient from ispconfig import ISPConfigClient, NotFoundError, PermissionError
@pytest.fixture() @pytest.fixture()
def client(live_creds: dict[str, str]) -> ISPConfigClient: def client(live_creds: dict[str, str]) -> Iterator[ISPConfigClient]:
with ISPConfigClient(live_creds["url"], live_creds["user"], live_creds["password"]) as c: verify = live_creds.get("verify", "1") not in ("0", "false", "False")
yield c # type: ignore[misc] with ISPConfigClient(
live_creds["url"],
live_creds["user"],
live_creds["password"],
verify_ssl=verify,
) as c:
yield c
# ---- hand-audited modules (first pass) -----------------------------------
def test_login_returns_session(live_creds: dict[str, str]) -> None: def test_login_returns_session(live_creds: dict[str, str]) -> None:
c = ISPConfigClient(live_creds["url"], live_creds["user"], live_creds["password"]) verify = live_creds.get("verify", "1") not in ("0", "false", "False")
c = ISPConfigClient(live_creds["url"], live_creds["user"], live_creds["password"], verify_ssl=verify)
c.login() c.login()
assert c.session_id and len(c.session_id) > 10 assert c.session_id and len(c.session_id) > 10
assert c.logout() is True assert c.logout() is True
@ -45,3 +62,193 @@ def test_mail_users_under_mcbindustrial(client: ISPConfigClient) -> None:
# Don't assert count — just shape. Zero mailboxes is a valid state. # Don't assert count — just shape. Zero mailboxes is a valid state.
for u in users: for u in users:
assert "email" in u assert "email" in u
# ---- auto-generated modules: one read-only probe each -------------------
#
# These prove the wrappers encode/decode correctly against a real panel.
# Each test tolerates the method being restricted to admin (``kayos`` is a
# reseller, not an admin) — those skip with a clear reason.
def test_raw_call_list_functions(client: ISPConfigClient) -> None:
"""``list_functions`` is the introspection escape hatch — sanity check it."""
funcs = client.list_functions()
assert "sites_web_domain_get" in funcs
assert "mail_user_get" in funcs
# 300+ is typical for a modern ISPConfig.
assert len(funcs) > 200
def test_raw_call_escape_hatch(client: ISPConfigClient) -> None:
"""``raw_call`` must route through the same retry/fault-mapping pipeline."""
# ``client_get_all`` is a no-arg read.
ids = client.raw_call("client_get_all")
assert ids is None or isinstance(ids, (list, dict))
def test_clients_client_get_all(client: ISPConfigClient) -> None:
ids = client.clients.get_all()
assert isinstance(ids, list)
# Don't assert count — panel may be empty of managed clients.
def test_clients_templates_get_all(client: ISPConfigClient) -> None:
try:
tpls = client.clients.client_templates_get_all()
except PermissionError:
pytest.skip("client_templates_get_all: admin-only on this panel")
assert tpls is None or isinstance(tpls, (list, dict))
def test_server_get_all(client: ISPConfigClient) -> None:
try:
servers = client.server.server_get_all()
except PermissionError:
pytest.skip("server_get_all: admin-only on this panel")
assert servers is None or isinstance(servers, (list, dict))
def test_server_get_functions(client: ISPConfigClient) -> None:
"""Pick the first server id and ask which modules it runs."""
try:
servers = client.server.server_get_all()
except PermissionError:
pytest.skip("server_get_all: admin-only on this panel")
if not servers:
pytest.skip("no servers visible to this API user")
# ``server_get_all`` returns a list of server records. Grab the first id.
first = servers[0] if isinstance(servers, list) else next(iter(servers.values()))
server_id = int(first.get("server_id") if isinstance(first, dict) else first)
try:
fns = client.server.server_get_functions(server_id)
except PermissionError:
pytest.skip("server_get_functions: admin-only on this panel")
assert fns is None or isinstance(fns, (list, dict))
def test_monitor_jobqueue_count(client: ISPConfigClient) -> None:
try:
n = client.monitor.monitor_jobqueue_count()
except PermissionError:
pytest.skip("monitor_jobqueue_count: admin-only on this panel")
# Returns an int-ish; ISPConfig may stringify.
assert n is None or isinstance(n, (int, str, dict, list))
def test_admin_system_config_get(client: ISPConfigClient) -> None:
try:
cfg = client.admin.system_config_get("mail")
except PermissionError:
pytest.skip("system_config_get: admin-only on this panel")
assert cfg is None or isinstance(cfg, (dict, list, str))
def test_ftp_user_get_missing(client: ISPConfigClient) -> None:
"""Nonexistent primary_id → ``NotFoundError`` via the fault-map path."""
try:
result = client.ftp.sites_ftp_user_get(999_999_999)
except NotFoundError:
return # expected
except PermissionError:
pytest.skip("sites_ftp_user_get: admin-only on this panel")
# Some panels return None/empty instead of a fault.
assert result in (None, {}, "", [])
def test_shell_user_get_missing(client: ISPConfigClient) -> None:
try:
result = client.shell.sites_shell_user_get(999_999_999)
except NotFoundError:
return
except PermissionError:
pytest.skip("sites_shell_user_get: admin-only on this panel")
assert result in (None, {}, "", [])
def test_webdav_user_get_missing(client: ISPConfigClient) -> None:
try:
result = client.webdav.sites_webdav_user_get(999_999_999)
except NotFoundError:
return
except PermissionError:
pytest.skip("sites_webdav_user_get: admin-only on this panel")
assert result in (None, {}, "", [])
def test_cron_get_missing(client: ISPConfigClient) -> None:
try:
result = client.cron.sites_cron_get(999_999_999)
except NotFoundError:
return
except PermissionError:
pytest.skip("sites_cron_get: admin-only on this panel")
assert result in (None, {}, "", [])
def test_backups_list(client: ISPConfigClient) -> None:
"""``sites_web_domain_backup_list`` on a known domain."""
try:
result = client.backups.sites_web_domain_backup_list(156)
except PermissionError:
pytest.skip("sites_web_domain_backup_list: admin-only on this panel")
except NotFoundError:
pytest.skip("no backups configured for domain 156")
assert result is None or isinstance(result, (list, dict))
def test_aps_available_packages_list(client: ISPConfigClient) -> None:
try:
pkgs = client.aps.sites_aps_available_packages_list()
except PermissionError:
pytest.skip("sites_aps_available_packages_list: admin-only on this panel")
except NotFoundError:
pytest.skip("APS not initialized on this panel")
assert pkgs is None or isinstance(pkgs, (list, dict))
def test_domains_get_all_by_user(client: ISPConfigClient) -> None:
"""``domains`` is the (optional) domain-module — may not be installed."""
try:
# group 1 = admin
domains = client.domains.domains_get_all_by_user(1)
except PermissionError:
pytest.skip("domains_get_all_by_user: admin-only or module disabled")
except NotFoundError:
pytest.skip("domains module not installed")
assert domains is None or isinstance(domains, (list, dict))
def test_openvz_get_free_ip(client: ISPConfigClient) -> None:
"""OpenVZ module may not be installed — skip cleanly if so."""
try:
ip = client.openvz.openvz_get_free_ip()
except PermissionError:
pytest.skip("openvz_get_free_ip: admin-only or OpenVZ not installed")
except NotFoundError:
pytest.skip("OpenVZ not installed / no free IPs")
assert ip is None or isinstance(ip, (str, dict, list))
def test_misc_quota_get_by_user(client: ISPConfigClient) -> None:
"""``quota_get_by_user`` — look up one visible client, query its quota."""
try:
ids = client.clients.get_all()
except PermissionError:
pytest.skip("client_get_all: admin-only on this panel")
if not ids:
pytest.skip("no clients visible to this API user")
client_id = int(ids[0])
try:
group_id = client.clients.get_groupid(client_id)
except (PermissionError, NotFoundError):
pytest.skip("client_get_groupid: unavailable")
if not group_id:
pytest.skip("couldn't resolve group id for first visible client")
try:
quota = client.misc.quota_get_by_user(group_id)
except PermissionError:
pytest.skip("quota_get_by_user: admin-only on this panel")
except NotFoundError:
pytest.skip("no quota record for this user")
assert quota is None or isinstance(quota, (list, dict))

View file

@ -64,27 +64,34 @@ def test_context_manager_auto_login_logout() -> None:
def test_session_expired_retry() -> None: def test_session_expired_retry() -> None:
t = _FakeTransport([ t = _FakeTransport(
[
"sid-first", # login "sid-first", # login
SoapFault("Server", "Session not valid"), # first _call fails SoapFault("Server", "Session not valid"), # first _call fails
"sid-second", # re-login "sid-second", # re-login
{"domain": "x.com"}, # retry succeeds {"domain": "x.com"}, # retry succeeds
]) ]
)
c = _make_client(t) c = _make_client(t)
c.login() c.login()
result = c.sites.web_domain_get(1) result = c.sites.web_domain_get(1)
assert result == {"domain": "x.com"} assert result == {"domain": "x.com"}
# 4 transport calls: login, failed get, login, successful get. # 4 transport calls: login, failed get, login, successful get.
assert [call[0] for call in t.calls] == [ assert [call[0] for call in t.calls] == [
"login", "sites_web_domain_get", "login", "sites_web_domain_get", "login",
"sites_web_domain_get",
"login",
"sites_web_domain_get",
] ]
def test_session_expired_no_retry_when_disabled() -> None: def test_session_expired_no_retry_when_disabled() -> None:
t = _FakeTransport([ t = _FakeTransport(
[
"sid-first", "sid-first",
SoapFault("Server", "Session expired"), SoapFault("Server", "Session expired"),
]) ]
)
c = ISPConfigClient("http://fake/", "u", "p", max_retries=0) c = ISPConfigClient("http://fake/", "u", "p", max_retries=0)
c._transport = t # type: ignore[assignment] c._transport = t # type: ignore[assignment]
c.login() c.login()
@ -149,13 +156,13 @@ def test_envelope_encoding_map_and_scalars() -> None:
) )
assert "<session_id" in xml and ">abc<" in xml assert "<session_id" in xml and ">abc<" in xml
assert '<client_id xsi:type="xsd:int">0</client_id>' in xml assert '<client_id xsi:type="xsd:int">0</client_id>' in xml
assert 'ns2:Map' in xml assert "ns2:Map" in xml
assert '<key xsi:type="xsd:string">php</key>' in xml assert '<key xsi:type="xsd:string">php</key>' in xml
assert '<value xsi:type="xsd:string">fast-cgi</value>' in xml assert '<value xsi:type="xsd:string">fast-cgi</value>' in xml
def test_response_parsing_map() -> None: def test_response_parsing_map() -> None:
body = b'''<?xml version="1.0"?> body = b"""<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="/remote/index.php" xmlns:ns1="/remote/index.php"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@ -169,19 +176,19 @@ def test_response_parsing_map() -> None:
</return> </return>
</ns1:sites_web_domain_getResponse> </ns1:sites_web_domain_getResponse>
</SOAP-ENV:Body> </SOAP-ENV:Body>
</SOAP-ENV:Envelope>''' </SOAP-ENV:Envelope>"""
result = SoapTransport._parse_response("sites_web_domain_get", body) result = SoapTransport._parse_response("sites_web_domain_get", body)
assert result == {"domain": "mcb.com", "active": "y"} assert result == {"domain": "mcb.com", "active": "y"}
def test_response_parsing_fault() -> None: def test_response_parsing_fault() -> None:
body = b'''<?xml version="1.0"?> body = b"""<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body><SOAP-ENV:Fault> <SOAP-ENV:Body><SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode> <faultcode>SOAP-ENV:Server</faultcode>
<faultstring>Login failed.</faultstring> <faultstring>Login failed.</faultstring>
</SOAP-ENV:Fault></SOAP-ENV:Body> </SOAP-ENV:Fault></SOAP-ENV:Body>
</SOAP-ENV:Envelope>''' </SOAP-ENV:Envelope>"""
with pytest.raises(SoapFault) as excinfo: with pytest.raises(SoapFault) as excinfo:
SoapTransport._parse_response("login", body) SoapTransport._parse_response("login", body)
assert "Login failed" in excinfo.value.faultstring assert "Login failed" in excinfo.value.faultstring

195
tools/extract_inventory.py Normal file
View file

@ -0,0 +1,195 @@
"""Extract ISPConfig remote method inventory from PHP sources.
Reads every ``remote.d/*.inc.php`` plus ``remoting.inc.php`` and dumps a
structured inventory to ``tools/method_inventory.json``. Parses ``public
function foo($session_id, $bar)`` declarations; scans the preceding lines
for a PHPDoc block to pull param types and descriptions.
This is the generator's input and a diff target. Checking it in makes
ISPConfig-upgrade deltas trivially visible in git.
"""
from __future__ import annotations
import json
import os
import re
import sys
from pathlib import Path
from typing import Any
# (session_id, ...) for most methods; (username, password) for login.
# ISPConfig sigs look like ``public function foo($session_id, $params = array())``
# — the ``array()`` default means we need to match balanced parens, not
# ``[^)]*``, or we'll truncate the signature at the first close-paren. We
# also tolerate the opening ``{`` on its own line.
_METHOD_RE = re.compile(
r"^\s*public\s+function\s+(?P<name>\w+)\s*"
r"\((?P<params>(?:[^()]|\([^)]*\))*)\)\s*(?:\n\s*)?\{",
re.MULTILINE,
)
_PARAM_RE = re.compile(r"\$(\w+)(?:\s*=\s*([^,]+))?")
# Map file basename -> module grouping (for context only; generator re-groups
# by method-name prefix, not by file).
_FILE_CLASS_MAP = {
"sites.inc.php": "sites",
"dns.inc.php": "dns",
"mail.inc.php": "mail",
"client.inc.php": "client",
"server.inc.php": "server",
"monitor.inc.php": "monitor",
"admin.inc.php": "admin",
"aps.inc.php": "aps",
"domains.inc.php": "domains",
"openvz.inc.php": "openvz",
"remoting.inc.php": "core",
}
# Methods declared on the base ``remoting`` class that aren't "API methods"
# per se — they're internal helpers or lifecycle hooks. We want ``login``,
# ``logout``, and the two ``*_functions`` introspection calls; skip the rest.
_CORE_WHITELIST = {
"login",
"logout",
"get_function_list",
"get_session_token",
}
def _find_docblock(lines: list[str], line_no: int) -> str | None:
"""Walk backwards from ``line_no`` looking for the closest ``*/`` and
return the enclosing ``/** ... */`` block as raw text, if any."""
end = None
for i in range(line_no - 1, max(-1, line_no - 60), -1):
stripped = lines[i].strip()
if not stripped:
continue
if stripped.endswith("*/"):
end = i
break
# Non-comment, non-blank: no docblock for this method.
if not stripped.startswith("*") and not stripped.startswith("/*"):
return None
if end is None:
return None
for j in range(end, max(-1, end - 80), -1):
if lines[j].lstrip().startswith("/**") or lines[j].lstrip().startswith("/*"):
return "\n".join(lines[j : end + 1])
return None
def _parse_params(raw: str) -> list[dict[str, Any]]:
"""Extract ``$name`` and optional default from a PHP param list."""
out: list[dict[str, Any]] = []
for m in _PARAM_RE.finditer(raw):
name = m.group(1)
default = m.group(2).strip() if m.group(2) else None
out.append({"name": name, "default": default})
return out
def _parse_docblock(doc: str | None) -> dict[str, Any]:
if not doc:
return {"summary": None, "params": [], "return": None}
summary_lines: list[str] = []
params: list[dict[str, str]] = []
ret = None
for line in doc.splitlines():
text = line.strip()
if text.startswith("/**") or text.startswith("/*") or text == "*/":
continue
if text.startswith("*"):
text = text[1:].lstrip()
if not text:
continue
if text.startswith("@param"):
# @param int $foo description
m = re.match(r"@param\s+(\S+)\s+\$?(\w+)?\s*(.*)", text)
if m:
params.append(
{
"type": m.group(1),
"name": m.group(2) or "",
"desc": m.group(3),
}
)
elif text.startswith("@return"):
m = re.match(r"@return\s+(\S+)\s*(.*)", text)
if m:
ret = {"type": m.group(1), "desc": m.group(2)}
elif text.startswith("@"):
# Other tags (@author, @throws, etc.) ignored.
continue
else:
summary_lines.append(text)
return {
"summary": " ".join(summary_lines).strip() or None,
"params": params,
"return": ret,
}
def extract_file(path: Path) -> list[dict[str, Any]]:
src = path.read_text(encoding="utf-8", errors="replace")
lines = src.splitlines()
out: list[dict[str, Any]] = []
for m in _METHOD_RE.finditer(src):
name = m.group("name")
params_raw = m.group("params")
line_no = src[: m.start()].count("\n")
# Parse params. Skip the leading $session_id where present (that's
# the SDK's job to add) — keep it in the raw list so docstrings can
# reflect reality, but flag it.
parsed_params = _parse_params(params_raw)
doc = _find_docblock(lines, line_no)
docinfo = _parse_docblock(doc)
out.append(
{
"method": name,
"file": path.name,
"line": line_no + 1,
"raw_signature": f"{name}({params_raw.strip()})",
"params": parsed_params,
"doc": docinfo,
}
)
return out
def main(src_dir: str, out_path: str) -> None:
root = Path(src_dir)
records: list[dict[str, Any]] = []
for php in sorted(root.glob("*.inc.php")):
for rec in extract_file(php):
method = rec["method"]
# remoting.inc.php holds login/logout + internal helpers.
if php.name == "remoting.inc.php" and method not in _CORE_WHITELIST:
continue
rec["source_class"] = _FILE_CLASS_MAP.get(php.name, "unknown")
records.append(rec)
# De-dupe by method name (some methods live in multiple files via
# inheritance — take the remote.d/ version).
seen: dict[str, dict[str, Any]] = {}
for rec in records:
if rec["method"] not in seen or rec["file"] != "remoting.inc.php":
seen[rec["method"]] = rec
records = sorted(seen.values(), key=lambda r: (r["source_class"], r["method"]))
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
Path(out_path).write_text(json.dumps(records, indent=2) + "\n", encoding="utf-8")
# Quick stats to stderr so CI logs show what we got.
by_class: dict[str, int] = {}
for rec in records:
by_class[rec["source_class"]] = by_class.get(rec["source_class"], 0) + 1
print(f"extracted {len(records)} methods", file=sys.stderr)
for cls, n in sorted(by_class.items()):
print(f" {cls:<10} {n}", file=sys.stderr)
if __name__ == "__main__":
src = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("ISPCONFIG_SRC", "")
out = sys.argv[2] if len(sys.argv) > 2 else "tools/method_inventory.json"
if not src:
print("usage: extract_inventory.py <php-src-dir> [out.json]", file=sys.stderr)
sys.exit(2)
main(src, out)

446
tools/gen_methods.py Normal file
View file

@ -0,0 +1,446 @@
"""Generate Python wrappers from the ISPConfig method inventory.
Reads ``tools/method_inventory.json`` and emits/updates one module per
functional area under ``src/ispconfig/``. Each module has two clearly-marked
sections:
# ---- AUTO-GENERATED START (do not hand-edit above this line) ----
... wrappers ...
# ---- AUTO-GENERATED END ----
# ---- HAND-EDIT ONLY BELOW ----
... helpers, convenience methods, etc ...
Re-running the generator replaces only the auto block; hand-edits below
the delimiter are preserved. Hand-audited helpers already in the tree
(enable_php, a_add with fix_type_bug, user_get with filter-dict
normalization, etc.) live below the delimiter and are untouched.
If an auto-generated method name collides with a hand-audited one
(detected by scanning the hand-edit block for ``def <name>(``), the
generator skips the auto wrapper the hand version wins.
Re-run flow::
python3 tools/extract_inventory.py <php-src> tools/method_inventory.json
python3 tools/gen_methods.py
ruff format src/ tools/
git diff --stat
"""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parent.parent
INVENTORY = ROOT / "tools" / "method_inventory.json"
SRC = ROOT / "src" / "ispconfig"
AUTO_START = "# ---- AUTO-GENERATED START (do not hand-edit above this line) ----"
AUTO_END = "# ---- AUTO-GENERATED END ----"
HAND_MARKER = "# ---- HAND-EDIT ONLY BELOW ----"
# Routing: method-name prefix -> (module filename, class name, method-name
# stripping rule). The stripping rule strips a prefix from the Python method
# name when we can keep it; we don't, for auto-generated wrappers, so the
# generator emits ``method_foo_bar`` as-is. (Hand-audited helpers strip the
# prefix for ergonomics — kept below the delimiter.)
#
# Order matters: first match wins. Longer prefixes first.
_ROUTES: list[tuple[str, str, str]] = [
# sites.*
("sites_web_vhost_aliasdomain_", "sites.py", "SitesModule"),
("sites_web_vhost_subdomain_", "sites.py", "SitesModule"),
("sites_web_aliasdomain_", "sites.py", "SitesModule"),
("sites_web_subdomain_", "sites.py", "SitesModule"),
("sites_web_domain_backup", "backups.py", "BackupsModule"),
("sites_web_domain_", "sites.py", "SitesModule"),
("sites_web_folder_user_", "sites.py", "SitesModule"),
("sites_web_folder_", "sites.py", "SitesModule"),
("sites_database_user_", "databases.py", "DatabasesModule"),
("sites_database_", "databases.py", "DatabasesModule"),
("sites_ftp_user_", "ftp.py", "FtpModule"),
("sites_shell_user_", "shell.py", "ShellModule"),
("sites_cron_", "cron.py", "CronModule"),
("sites_webdav_user_", "webdav.py", "WebdavModule"),
("sites_aps_", "aps.py", "ApsModule"),
("client_", "clients.py", "ClientsModule"),
("mail_", "mail.py", "MailModule"),
("mailquota_", "mail.py", "MailModule"),
("dns_", "dns.py", "DnsModule"),
("server_", "server.py", "ServerModule"),
("monitor_", "monitor.py", "MonitorModule"),
("domains_", "domains.py", "DomainsModule"),
("openvz_", "openvz.py", "OpenvzModule"),
# admin / misc buckets
("sys_datalog_", "admin.py", "AdminModule"),
("system_config_", "admin.py", "AdminModule"),
("config_value_", "admin.py", "AdminModule"),
("update_record_permissions", "admin.py", "AdminModule"),
# quotas and misc helpers from sites.inc.php that don't match the
# `sites_*` prefix
("quota_get_by_user", "misc.py", "MiscModule"),
("databasequota_get_by_user", "misc.py", "MiscModule"),
("ftptrafficquota_data", "misc.py", "MiscModule"),
("trafficquota_get_by_user", "misc.py", "MiscModule"),
("client_get_sites_by_user", "misc.py", "MiscModule"),
]
# Modules that already exist in the tree with a hand-audited class. For
# these, we append the auto block to an existing class body rather than
# writing a fresh module.
_PREEXISTING = {
"sites.py": "SitesModule",
"dns.py": "DnsModule",
"mail.py": "MailModule",
"databases.py": "DatabasesModule",
"clients.py": "ClientsModule",
}
# Methods we skip at generation time: ``login`` and ``logout`` are already
# on the top-level client, and ``get_function_list`` is a no-arg
# introspection call we expose via ``ISPConfigClient.list_functions()``.
_SKIP_METHODS = {"login", "logout", "get_function_list"}
def route(method: str) -> tuple[str, str] | None:
for prefix, fname, cls in _ROUTES:
if method == prefix or method.startswith(prefix):
return fname, cls
return None
def _py_method_name(method: str, prefix: str) -> str:
"""Strip the routed prefix from a method name if it's safe to.
For the auto-generated wrappers we keep the full PHP method name as
the Python method name. This is verbose but unambiguous and matches
``raw_call`` semantics so you can grep for the exact PHP string.
Hand-audited helpers may provide shorter names; they live below the
delimiter and take precedence.
"""
return method # keep the full PHP name; stripping invites collisions
def _docstring(rec: dict[str, Any]) -> list[str]:
"""Return docstring lines, indented with 8 spaces (method-body level)."""
indent = " "
lines = [f'{indent}"""']
summary = rec["doc"].get("summary") or f"Auto-generated wrapper for ``{rec['method']}``."
# Escape embedded triple quotes defensively.
summary = summary.replace('"""', "'''")
lines.append(f"{indent}{summary}")
lines.append("")
lines.append(f"{indent}Source: ``{rec['file']}`` line {rec['line']}.")
sig_params = [p["name"] for p in rec["params"] if p["name"] != "session_id"]
if sig_params:
lines.append(f"{indent}PHP signature: ``{rec['raw_signature']}``.")
if rec["doc"]["params"]:
lines.append("")
lines.append(f"{indent}Params (from PHPDoc):")
for p in rec["doc"]["params"]:
pname = p.get("name") or "?"
if pname == "session_id":
continue
ptype = p.get("type", "?")
desc = (p.get("desc") or "").replace('"""', "'''")
if desc:
lines.append(f"{indent} {pname} ({ptype}): {desc}")
else:
lines.append(f"{indent} {pname} ({ptype})")
if rec["doc"].get("return"):
lines.append("")
ret_type = rec["doc"]["return"].get("type", "Any")
ret_desc = (rec["doc"]["return"].get("desc") or "").replace('"""', "'''")
suffix = f" - {ret_desc}" if ret_desc else ""
lines.append(f"{indent}Returns: {ret_type}{suffix}")
lines.append("")
lines.append(f"{indent}AUTO-GENERATED - param shapes may need verification against your")
lines.append(f"{indent}ISPConfig version. File issues at Sulkta-Coop/ispconfig-py.")
lines.append(f'{indent}"""')
return lines
_PHP_TYPE_MAP = {
"int": "int",
"integer": "int",
"string": "str",
"bool": "bool",
"boolean": "bool",
"array": "dict[str, Any] | list[Any]",
"mixed": "Any",
"float": "float",
"double": "float",
}
def _py_param_type(phpdoc_params: list[dict[str, str]], name: str) -> str:
for p in phpdoc_params:
if p.get("name") == name:
t = (p.get("type") or "").strip().lower().split("|")[0]
return _PHP_TYPE_MAP.get(t, "Any")
return "Any"
def _emit_method(rec: dict[str, Any]) -> list[str]:
method = rec["method"]
# Parameters after session_id.
params = [p for p in rec["params"] if p["name"] != "session_id"]
sig_parts = ["self"]
arg_encodes: list[str] = []
for p in params:
py_type = _py_param_type(rec["doc"]["params"], p["name"])
default = p["default"]
if default is not None:
# PHP defaults are best-effort translated.
default_py = _translate_php_default(default)
# PEP 484: a None default needs ``| None`` on the annotation.
# Any already includes None, so no change needed for Any.
if default_py == "None" and py_type not in ("Any", "dict[str, Any] | list[Any]"):
py_type = f"{py_type} | None"
sig_parts.append(f"{p['name']}: {py_type} = {default_py}")
else:
sig_parts.append(f"{p['name']}: {py_type}")
arg_encodes.append(f'("{p["name"]}", {p["name"]})')
signature = ", ".join(sig_parts)
lines: list[str] = []
lines.append(f" def {method}({signature}) -> Any:")
lines.extend(_docstring(rec))
if arg_encodes:
args_str = ", ".join(arg_encodes)
lines.append(f' return self._c._call("{method}", {args_str})')
else:
lines.append(f' return self._c._call("{method}")')
lines.append("")
return lines
def _translate_php_default(value: str) -> str:
v = value.strip()
low = v.lower()
if low in ("null",):
return "None"
if low == "true":
return "True"
if low == "false":
return "False"
if low in ("array()", "[]"):
return "None" # PHP empty-array default -> None in Python signature
# Numeric literals pass through.
if re.match(r"^-?\d+(\.\d+)?$", v):
return v
# Quoted strings.
if (v.startswith("'") and v.endswith("'")) or (v.startswith('"') and v.endswith('"')):
inner = v[1:-1]
return '"' + inner.replace("\\", "\\\\").replace('"', '\\"') + '"'
# Unknown → fall back to None (safest default for optional params).
return "None"
# -----------------------------------------------------------------------
# Module file handling
# -----------------------------------------------------------------------
def _module_header(fname: str, cls: str) -> str:
"""Stock module header for NEW module files (not preexisting)."""
stem = fname.replace(".py", "")
mod_title = stem.replace("_", " ").title()
return (
f'"""``{stem}.*`` — auto-generated ISPConfig remote-API wrappers.\n\n'
f"This module is produced by ``tools/gen_methods.py`` from the\n"
f"``tools/method_inventory.json`` catalog. Hand-edits go below the\n"
f"``{HAND_MARKER.strip('# ')}`` marker — they survive regeneration.\n"
f'"""\n\n'
f"from __future__ import annotations\n\n"
f"from typing import TYPE_CHECKING, Any\n\n"
f"if TYPE_CHECKING:\n"
f" from .client import ISPConfigClient\n\n\n"
f"class {cls}:\n"
f' """Auto-generated module: {mod_title}.\n\n'
f" All methods below the ``AUTO-GENERATED START`` marker are produced\n"
f" by ``tools/gen_methods.py``. Do not hand-edit that block — changes\n"
f" will be overwritten on the next regeneration. Add helpers and\n"
f" overrides below the ``HAND-EDIT ONLY BELOW`` marker instead.\n"
f' """\n\n'
f" def __init__(self, client: ISPConfigClient) -> None:\n"
f" self._c = client\n\n"
)
def _find_existing_auto_block(text: str) -> tuple[int, int] | None:
"""Return (start_line_idx, end_line_idx) of the existing auto block, if any."""
lines = text.splitlines()
start = end = None
for i, line in enumerate(lines):
if AUTO_START in line:
start = i
if AUTO_END in line and start is not None:
end = i
break
if start is None or end is None:
return None
return start, end
def _find_class_body_insertion(text: str, cls: str) -> int | None:
"""Find the line index of the first blank line AFTER the class's __init__
(a sensible spot to inject the auto block in a preexisting module).
"""
lines = text.splitlines()
in_class = False
saw_init = False
for i, line in enumerate(lines):
if re.match(rf"class\s+{cls}\b", line):
in_class = True
continue
if in_class and "def __init__" in line:
saw_init = True
if saw_init and line.strip() == "":
return i + 1
return None
def _extract_hand_method_names(text: str, auto_end: int | None) -> set[str]:
"""Scan everything AFTER the auto block for ``def <name>(`` so the
generator can skip methods a hand-audited helper already claims.
"""
lines = text.splitlines()
start = (auto_end + 1) if auto_end is not None else 0
names: set[str] = set()
for line in lines[start:]:
m = re.match(r"\s+def\s+(\w+)\s*\(", line)
if m:
names.add(m.group(1))
return names
def _render_auto_block(records: list[dict[str, Any]], hand_names: set[str]) -> list[str]:
lines: list[str] = []
lines.append(f" {AUTO_START}")
lines.append(" # Regenerate with: python3 tools/gen_methods.py")
lines.append("")
emitted_names: set[str] = set()
for rec in sorted(records, key=lambda r: r["method"]):
if rec["method"] in hand_names:
lines.append(f" # skipped {rec['method']}: hand-audited helper below takes precedence")
lines.append("")
continue
if rec["method"] in emitted_names:
continue
emitted_names.add(rec["method"])
lines.extend(_emit_method(rec))
lines.append(f" {AUTO_END}")
lines.append("")
lines.append(f" {HAND_MARKER}")
lines.append("")
return lines
def _ensure_any_imported(text: str) -> str:
"""Make sure ``Any`` is imported from ``typing`` — auto-generated wrappers
always need it. Leaves other imports untouched; idempotent.
"""
if re.search(r"^from\s+typing\s+import[^\n]*\bAny\b", text, re.MULTILINE):
return text
m = re.search(r"^(from\s+typing\s+import\s+)([^\n]+)$", text, re.MULTILINE)
if m:
current = m.group(2)
# Append ``Any``; keep symbols sorted lexically for stable diffs.
symbols = sorted({s.strip() for s in current.split(",")} | {"Any"})
new_line = m.group(1) + ", ".join(symbols)
return text[: m.start()] + new_line + text[m.end() :]
# No ``from typing import`` at all — add one after ``from __future__``.
fut = re.search(r"^from\s+__future__\s+import[^\n]*$", text, re.MULTILINE)
if fut:
insertion = fut.end()
return text[:insertion] + "\n\nfrom typing import Any" + text[insertion:]
# Last resort: prepend.
return "from typing import Any\n\n" + text
def _update_existing_module(path: Path, cls: str, records: list[dict[str, Any]]) -> None:
text = path.read_text(encoding="utf-8")
text = _ensure_any_imported(text)
block = _find_existing_auto_block(text)
if block is None:
# First-time injection: find where to insert.
lines = text.splitlines()
insertion = _find_class_body_insertion(text, cls)
if insertion is None:
print(f"WARN: couldn't find insertion point in {path}", file=sys.stderr)
return
hand_names = _extract_hand_method_names(text, auto_end=None)
auto_lines = _render_auto_block(records, hand_names)
new_lines = lines[:insertion] + auto_lines + lines[insertion:]
path.write_text("\n".join(new_lines).rstrip() + "\n", encoding="utf-8")
return
start, end = block
lines = text.splitlines()
hand_names = _extract_hand_method_names(text, auto_end=end)
auto_lines = _render_auto_block(records, hand_names)
# Replace start..end+possible trailing hand-marker line; we also re-emit the
# hand marker so we own its exact placement. Consume any blank lines plus
# a HAND_MARKER (with optional trailing blank) between AUTO_END and the
# first real code below — we'll re-emit the marker ourselves at the end
# of ``auto_lines``.
after = end + 1
while after < len(lines) and lines[after].strip() == "":
after += 1
if after < len(lines) and HAND_MARKER in lines[after]:
after += 1
while after < len(lines) and lines[after].strip() == "":
after += 1
new_lines = lines[:start] + auto_lines + lines[after:]
path.write_text("\n".join(new_lines).rstrip() + "\n", encoding="utf-8")
def _write_new_module(path: Path, cls: str, records: list[dict[str, Any]]) -> None:
header = _module_header(path.name, cls)
auto_lines = _render_auto_block(records, hand_names=set())
body = header + "\n".join(auto_lines).rstrip() + "\n"
path.write_text(body, encoding="utf-8")
def main() -> None:
records = json.loads(INVENTORY.read_text(encoding="utf-8"))
SRC.mkdir(parents=True, exist_ok=True)
by_module: dict[tuple[str, str], list[dict[str, Any]]] = {}
unrouted: list[str] = []
for rec in records:
if rec["method"] in _SKIP_METHODS:
continue
routed = route(rec["method"])
if routed is None:
unrouted.append(rec["method"])
# Default bucket = misc.py
routed = ("misc.py", "MiscModule")
key = routed
by_module.setdefault(key, []).append(rec)
for (fname, cls), recs in sorted(by_module.items()):
path = SRC / fname
if path.exists() and fname in _PREEXISTING:
_update_existing_module(path, cls, recs)
action = "updated"
elif path.exists():
_update_existing_module(path, cls, recs)
action = "refreshed"
else:
_write_new_module(path, cls, recs)
action = "created"
print(f"{action:<10} {fname:<16} ({len(recs)} methods)")
if unrouted:
print(f"\nUNROUTED ({len(unrouted)}) — filed under misc.py:", file=sys.stderr)
for m in unrouted:
print(f" {m}", file=sys.stderr)
if __name__ == "__main__":
main()

8284
tools/method_inventory.json Normal file

File diff suppressed because it is too large Load diff