epptools 1.0.0__tar.gz → 1.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {epptools-1.0.0 → epptools-1.0.2}/CHANGELOG.md +44 -0
- {epptools-1.0.0/epptools.egg-info → epptools-1.0.2}/PKG-INFO +1 -1
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/builders.md +22 -11
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/contacts.md +29 -17
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/builders.md +22 -13
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/contacts.md +29 -17
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/builders.md +22 -11
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/contacts.md +28 -17
- {epptools-1.0.0 → epptools-1.0.2}/epptools/_version.py +1 -1
- {epptools-1.0.0 → epptools-1.0.2}/epptools/builders.py +16 -7
- {epptools-1.0.0 → epptools-1.0.2}/epptools/commands.py +51 -8
- {epptools-1.0.0 → epptools-1.0.2/epptools.egg-info}/PKG-INFO +1 -1
- {epptools-1.0.0 → epptools-1.0.2}/pyproject.toml +1 -1
- {epptools-1.0.0 → epptools-1.0.2}/LICENSE +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/MANIFEST.in +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/README.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/README.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/balance.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/commands.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/domains.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/errors.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/hosts.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/poll.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/quickstart.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/responses.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/en/session.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/README.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/balance.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/commands.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/domains.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/errors.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/hosts.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/poll.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/quickstart.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/responses.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/ru/session.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/README.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/balance.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/commands.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/domains.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/errors.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/hosts.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/poll.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/quickstart.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/responses.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/docs/uk/session.md +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools/__init__.py +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools/client.py +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools/config.py +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools/exceptions.py +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools/frame.py +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools/namespaces.py +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools/py.typed +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools/response.py +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools/result_code.py +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools/transport.py +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools.egg-info/SOURCES.txt +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools.egg-info/dependency_links.txt +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/epptools.egg-info/top_level.txt +0 -0
- {epptools-1.0.0 → epptools-1.0.2}/setup.cfg +0 -0
|
@@ -4,6 +4,50 @@ All notable changes to this library are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.0.2]
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **A postal change that carried only part of the block could delete the rest of the address.** A
|
|
12
|
+
`<contact:postalInfo>` inside `<contact:chg>` is not merged field by field by every registry: one
|
|
13
|
+
that *replaces* the block stores exactly what you sent and drops everything else — while answering
|
|
14
|
+
**1000**. Clearing an organisation on its own therefore left a contact with no name, no street, no
|
|
15
|
+
city, no postal code and no country, and nothing in the response said so. `name`, `city` and `cc`
|
|
16
|
+
are now required in every postal change, through `contact.update()` and the update builder alike.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- A postal change missing `name`, `city` or `cc` now raises `ValidationException` instead of being
|
|
21
|
+
sent. This rejects calls that previously appeared to succeed — those were the calls losing data.
|
|
22
|
+
Read the current block with `client.contact.info(id).postal_info()` and send it back with your
|
|
23
|
+
change applied.
|
|
24
|
+
- The documentation for partial postal updates was wrong in the same way and has been rewritten. A
|
|
25
|
+
block is replaced, not merged: a field you leave out is deleted, not preserved.
|
|
26
|
+
|
|
27
|
+
## [1.0.1]
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **Clearing one optional part of an address built a frame the server rejects.** RFC 5733 makes
|
|
32
|
+
`<contact:addr>` a sequence with a required `city` and `cc`, so a change to any part of it sends
|
|
33
|
+
the whole block. What the library did was substitute an EMPTY STRING for whatever the caller had
|
|
34
|
+
not supplied — so `{"sp": ""}`, the documented way to remove a state, went out with `<city/>` and
|
|
35
|
+
`<cc/>` beside it. Both are schema-invalid (`postalLineType` has minLength 1, `ccType` is exactly
|
|
36
|
+
two characters), and an invalid frame comes back as a bare `2001` naming no element. A caller
|
|
37
|
+
following the manual got the least useful error in EPP.
|
|
38
|
+
|
|
39
|
+
A partial address change now requires `city` and `cc` and says so before anything is sent. Read
|
|
40
|
+
them back from `info()` and pass them through unchanged alongside what you are changing.
|
|
41
|
+
|
|
42
|
+
- **An empty value is now refused for the fields the schema forbids it on.** Which parts of a
|
|
43
|
+
contact can be CLEARED is fixed by `contact-1.0.xsd` and not by convention: `org`, `street`, `sp`
|
|
44
|
+
and `pc` have types with no minimum length, so sending them empty is what removes them; `name`,
|
|
45
|
+
`city` and `cc` do not, so an empty one cannot be sent at all. Passing one is answered here, with
|
|
46
|
+
the reason, instead of costing a round trip.
|
|
47
|
+
|
|
48
|
+
Neither changes a call that worked. Both turn a call the server was already rejecting into one that
|
|
49
|
+
fails immediately and says why.
|
|
50
|
+
|
|
7
51
|
## [1.0.0]
|
|
8
52
|
|
|
9
53
|
First public release.
|
|
@@ -398,24 +398,35 @@ Statuses go in their own blocks; every field change lands in `chg`.
|
|
|
398
398
|
.send())
|
|
399
399
|
```
|
|
400
400
|
|
|
401
|
-
###
|
|
401
|
+
### An address is REPLACED, not merged
|
|
402
402
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
403
|
+
The block you pass **replaces** the one the registry holds. It is not merged field by field, so an
|
|
404
|
+
argument you do not pass is not sent — and the registry deletes what it held. An argument passed as
|
|
405
|
+
`""` is sent empty, which is what clears a field.
|
|
406
|
+
|
|
407
|
+
RFC 5733 can be read as "leave it out and the registry keeps what it holds", since every child of
|
|
408
|
+
`chgPostalInfoType` is optional, but that reading is not safe. Against a registry that replaces —
|
|
409
|
+
**every command answering 1000** — a block sent without its `org` comes back with the organisation
|
|
410
|
+
gone, and a block carrying only an `org` leaves the contact with no postal address at all: name,
|
|
411
|
+
street, city, postal code and country.
|
|
412
|
+
|
|
413
|
+
`name`, `city` and `country_code` are required in every address change for that reason, and the
|
|
414
|
+
builder refuses the call without them. They keep the frame valid; they cannot restore an argument you
|
|
415
|
+
did not pass. **Read the block first and pass it back with your change applied:**
|
|
406
416
|
|
|
407
417
|
```python
|
|
408
|
-
|
|
418
|
+
current = client.contact.info("C1").postal_info()["int"]
|
|
419
|
+
|
|
420
|
+
# Remove the organisation, keeping the rest of the address exactly as it was.
|
|
409
421
|
(client.contact.update_builder("C1")
|
|
410
|
-
.change_international_address(
|
|
422
|
+
.change_international_address(name=current["name"], city=current["city"],
|
|
423
|
+
country_code=current["cc"], street=current.get("street"),
|
|
424
|
+
org="")
|
|
411
425
|
.send())
|
|
412
426
|
```
|
|
413
427
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
— and when it is sent, city and country go with it. Leaving them out of a call that changes a street
|
|
417
|
-
line sends them empty, and empty is a deliberate clear. The command answers 1000 and the contact
|
|
418
|
-
loses its city and country.
|
|
428
|
+
The form you do not mention — local or international — is untouched: the two are addressed
|
|
429
|
+
separately.
|
|
419
430
|
|
|
420
431
|
Changing one postal form leaves the other exactly as it was.
|
|
421
432
|
|
|
@@ -325,31 +325,43 @@ There is no way to *remove* a contact's transfer secret. RFC 5731 gives a domain
|
|
|
325
325
|
RFC 5733 defines no equivalent for a contact, so a contact's code can be replaced but not cleared.
|
|
326
326
|
An empty password is not a substitute: an empty value is still a value the holder can present.
|
|
327
327
|
|
|
328
|
-
### The partial-update rule:
|
|
328
|
+
### The partial-update rule: a postal block is REPLACED, not merged
|
|
329
329
|
|
|
330
|
-
|
|
330
|
+
Send a postal block in `chg` and the registry **replaces** the block it holds with the one you sent.
|
|
331
|
+
The two are not merged field by field, so whatever you leave out is gone.
|
|
331
332
|
|
|
332
|
-
|
|
333
|
+
RFC 5733 can be read the other way: in `chgPostalInfoType` name, org and addr are each optional,
|
|
334
|
+
which looks like "leave it out and the registry keeps what it holds". That reading is not safe.
|
|
335
|
+
Against a registry that replaces, **every one of these commands answers 1000**:
|
|
336
|
+
|
|
337
|
+
| What the `chg` carried | What the contact had afterwards |
|
|
333
338
|
|---|---|
|
|
334
|
-
| the
|
|
335
|
-
| the
|
|
336
|
-
|
|
|
339
|
+
| the complete block, `"org": ""` | organisation removed, address untouched |
|
|
340
|
+
| the complete block, no `org` key | organisation **also removed** |
|
|
341
|
+
| `{"org": ""}` and nothing else | **no postal block at all** — name, street, city, postal code and country gone |
|
|
342
|
+
|
|
343
|
+
So there is no such thing as changing one field of an address, and the failure is silent: the command
|
|
344
|
+
succeeds and the data is gone. `name`, `city` and `cc` are required in every postal change and the
|
|
345
|
+
SDK refuses the call without them, but that guard only keeps the frame schema-valid — it cannot put
|
|
346
|
+
back an `org`, an `sp` or a `pc` you did not send.
|
|
337
347
|
|
|
338
|
-
|
|
339
|
-
touched: changing the `int` block leaves the `loc` block exactly as it was, and the other way round.
|
|
348
|
+
**Read the block, apply your change, send it back whole:**
|
|
340
349
|
|
|
341
350
|
```python
|
|
342
|
-
|
|
343
|
-
client.contact.update("C1",
|
|
344
|
-
chg={"postal_info": {"type": "int", "org": "",
|
|
345
|
-
"city": "Kyiv", "cc": "UA"}})
|
|
351
|
+
current = client.contact.info("C1").postal_info()["int"]
|
|
352
|
+
client.contact.update("C1", chg={"postal_info": {**current, "type": "int", "org": ""}})
|
|
346
353
|
```
|
|
347
354
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
355
|
+
One thing the replacement does *not* reach is the other postal form: `int` and `loc` are addressed
|
|
356
|
+
separately, so replacing one leaves the other exactly as it was.
|
|
357
|
+
|
|
358
|
+
Inside the block you send, an empty string is still what clears an optional field:
|
|
359
|
+
|
|
360
|
+
| You write | What happens |
|
|
361
|
+
|---|---|
|
|
362
|
+
| the key holds a value | the field is set to it |
|
|
363
|
+
| the key holds `""` | the field is sent empty, which **clears** it |
|
|
364
|
+
| the key is absent | the field is not sent — and the registry deletes what it held |
|
|
353
365
|
|
|
354
366
|
Street lines behave differently and in your favour: an address block sent without any `street`
|
|
355
367
|
leaves the existing lines untouched.
|
|
@@ -401,26 +401,35 @@ handle = (client.contact.create_builder(Contact.AUTO_ID, "contact@example.com")
|
|
|
401
401
|
.send())
|
|
402
402
|
```
|
|
403
403
|
|
|
404
|
-
###
|
|
404
|
+
### Адрес ЗАМЕЩАЕТСЯ целиком, а не сливается по полям
|
|
405
405
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
406
|
+
Блок, который вы передаёте, **замещает** тот, что хранит реестр. Их не сливают поле за полем, поэтому
|
|
407
|
+
аргумент, которого вы не передали, не отправляется — и реестр удаляет то, что хранил. Аргумент,
|
|
408
|
+
переданный как `""`, отправляется пустым, и именно это очищает поле.
|
|
409
|
+
|
|
410
|
+
RFC 5733 можно прочитать как «не передавайте — и реестр сохранит то, что хранит», ведь каждая
|
|
411
|
+
составляющая `chgPostalInfoType` необязательна, но это чтение небезопасно. Против реестра, который
|
|
412
|
+
замещает блок, — причём **каждая команда отвечает 1000** — блок, отправленный без `org`,
|
|
413
|
+
возвращается уже без организации, а блок, в котором был один лишь `org`, оставляет контакт вообще
|
|
414
|
+
без почтового адреса: без имени, улицы, города, индекса и страны.
|
|
415
|
+
|
|
416
|
+
Именно поэтому `name`, `city` и `country_code` обязательны в любом изменении адреса, и билдер без
|
|
417
|
+
них отказывает. Они удерживают кадр валидным, но вернуть аргумент, которого вы не передали, не
|
|
418
|
+
могут. **Сначала прочитайте блок и верните его вместе со своим изменением:**
|
|
409
419
|
|
|
410
420
|
```python
|
|
411
|
-
|
|
421
|
+
current = client.contact.info("C1").postal_info()["int"]
|
|
422
|
+
|
|
423
|
+
# Убрать организацию, оставив остальной адрес ровно таким, каким он был.
|
|
412
424
|
(client.contact.update_builder("C1")
|
|
413
|
-
.change_international_address(
|
|
425
|
+
.change_international_address(name=current["name"], city=current["city"],
|
|
426
|
+
country_code=current["cc"], street=current.get("street"),
|
|
427
|
+
org="")
|
|
414
428
|
.send())
|
|
415
429
|
```
|
|
416
430
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
отправляется целиком или не отправляется вовсе, — а раз отправляется, то вместе с городом и страной.
|
|
420
|
-
Не указать их в вызове, который меняет строку улицы, значит отправить их пустыми, а пустое — это
|
|
421
|
-
осознанная очистка. Команда ответит 1000, и контакт лишится города и страны.
|
|
422
|
-
|
|
423
|
-
Изменение одной почтовой формы оставляет другую ровно такой, какой она была.
|
|
431
|
+
Изменение одной почтовой формы оставляет другую ровно такой, какой она была: они адресуются
|
|
432
|
+
раздельно.
|
|
424
433
|
|
|
425
434
|
### Здесь нет clear_auth_info()
|
|
426
435
|
|
|
@@ -331,31 +331,43 @@ RFC 5733 ничего равнозначного для контакта не о
|
|
|
331
331
|
очистить. Пустой пароль заменой не является: пустое значение — это по-прежнему значение, которое
|
|
332
332
|
держатель может предъявить.
|
|
333
333
|
|
|
334
|
-
### Правило
|
|
334
|
+
### Правило изменения: почтовый блок ЗАМЕЩАЕТСЯ, а не сливается
|
|
335
335
|
|
|
336
|
-
|
|
336
|
+
Отправьте почтовый блок в `chg` — и реестр **заместит** им тот, что хранит. Их не сливают поле за
|
|
337
|
+
полем, поэтому всё, чего вы не передали, исчезает.
|
|
337
338
|
|
|
338
|
-
|
|
339
|
+
RFC 5733 можно прочитать и иначе: в `chgPostalInfoType` каждая из составляющих — name, org, addr —
|
|
340
|
+
необязательна, и это выглядит как «не передавайте её, и реестр сохранит то, что хранит». Это чтение
|
|
341
|
+
небезопасно: против реестра, который замещает блок, **каждая из этих команд отвечает 1000**:
|
|
342
|
+
|
|
343
|
+
| Что нёс `chg` | Что осталось у контакта |
|
|
339
344
|
|---|---|
|
|
340
|
-
|
|
|
341
|
-
|
|
|
342
|
-
|
|
|
345
|
+
| полный блок с `"org": ""` | организация убрана, адрес нетронут |
|
|
346
|
+
| полный блок без ключа `org` | организация **тоже убрана** |
|
|
347
|
+
| один лишь `{"org": ""}` | **почтового блока нет вообще** — ни имени, ни улицы, города, индекса и страны |
|
|
348
|
+
|
|
349
|
+
То есть изменить одно поле адреса невозможно, и сбой молчаливый: команда успешна, а данных нет.
|
|
350
|
+
`name`, `city` и `cc` обязательны в любом почтовом изменении, и этот SDK без них отказывает, — но
|
|
351
|
+
эта проверка лишь удерживает кадр валидным и вернуть `org`, `sp` или `pc`, которого вы не передали,
|
|
352
|
+
не может.
|
|
343
353
|
|
|
344
|
-
|
|
345
|
-
идёт речь: изменение блока `int` оставляет блок `loc` ровно таким, каким он был, и наоборот.
|
|
354
|
+
**Прочитайте блок, примените своё изменение и отправьте его целиком:**
|
|
346
355
|
|
|
347
356
|
```python
|
|
348
|
-
|
|
349
|
-
client.contact.update("C1",
|
|
350
|
-
chg={"postal_info": {"type": "int", "org": "",
|
|
351
|
-
"city": "Kyiv", "cc": "UA"}})
|
|
357
|
+
current = client.contact.info("C1").postal_info()["int"]
|
|
358
|
+
client.contact.update("C1", chg={"postal_info": {**current, "type": "int", "org": ""}})
|
|
352
359
|
```
|
|
353
360
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
361
|
+
Чего замещение не достаёт — так это второй почтовой формы: `int` и `loc` адресуются раздельно,
|
|
362
|
+
поэтому замена одной оставляет другую ровно такой, какой она была.
|
|
363
|
+
|
|
364
|
+
Внутри блока, который вы отправляете, пустая строка по-прежнему очищает необязательное поле:
|
|
365
|
+
|
|
366
|
+
| Что вы пишете | Что происходит |
|
|
367
|
+
|---|---|
|
|
368
|
+
| ключ несёт значение | поле принимает это значение |
|
|
369
|
+
| ключ несёт `""` | поле отправляется пустым, что его **очищает** |
|
|
370
|
+
| ключа нет | поле не отправляется — и реестр удаляет то, что хранил |
|
|
359
371
|
|
|
360
372
|
Со строками улицы всё иначе, и в вашу пользу: блок адреса, отправленный без `street`, оставляет
|
|
361
373
|
существующие строки нетронутыми.
|
|
@@ -399,24 +399,35 @@ handle = (client.contact.create_builder(Contact.AUTO_ID, "contact@example.com")
|
|
|
399
399
|
.send())
|
|
400
400
|
```
|
|
401
401
|
|
|
402
|
-
###
|
|
402
|
+
### Адресу ЗАМІЩУЮТЬ цілком, а не зливають по полях
|
|
403
403
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
404
|
+
Блок, який ви передаєте, **заміщає** той, що тримає реєстр. Їх не зливають поле за полем, тож
|
|
405
|
+
аргумент, якого ви не передали, не надсилається — і реєстр видаляє те, що тримав. Аргумент,
|
|
406
|
+
переданий як `""`, надсилається порожнім, і саме це очищає поле.
|
|
407
|
+
|
|
408
|
+
RFC 5733 можна прочитати як «не подавайте — і реєстр збереже те, що має», бо кожна складова
|
|
409
|
+
`chgPostalInfoType` необов'язкова, але це читання небезпечне. Проти реєстру, який заміщає блок, —
|
|
410
|
+
причому **кожна команда відповідає 1000** — блок, надісланий без `org`, повертається вже без
|
|
411
|
+
організації, а блок, у якому був самий лише `org`, лишає контакт узагалі без поштової адреси: без
|
|
412
|
+
імені, вулиці, міста, індексу та країни.
|
|
413
|
+
|
|
414
|
+
Саме тому `name`, `city` і `country_code` обов'язкові в кожній зміні адреси, і білдер без них
|
|
415
|
+
відмовляє. Вони тримають кадр валідним, але не здатні повернути аргумент, якого ви не передали.
|
|
416
|
+
**Спершу прочитайте блок і поверніть його разом зі своєю зміною:**
|
|
407
417
|
|
|
408
418
|
```python
|
|
409
|
-
|
|
419
|
+
current = client.contact.info("C1").postal_info()["int"]
|
|
420
|
+
|
|
421
|
+
# Прибрати організацію, лишивши решту адреси точно такою, як була.
|
|
410
422
|
(client.contact.update_builder("C1")
|
|
411
|
-
.change_international_address(
|
|
423
|
+
.change_international_address(name=current["name"], city=current["city"],
|
|
424
|
+
country_code=current["cc"], street=current.get("street"),
|
|
425
|
+
org="")
|
|
412
426
|
.send())
|
|
413
427
|
```
|
|
414
428
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
зовсім, — а коли надсилається, місто і країна їдуть разом із ним. Якщо не вказати їх у виклику, який
|
|
418
|
-
змінює рядок вулиці, вони підуть порожніми, а порожнє — це свідоме очищення. Команда відповість 1000,
|
|
419
|
-
і контакт втратить своє місто та країну.
|
|
429
|
+
Форма, якої ви не згадали — локальна чи міжнародна, — лишається недоторканою: вони адресуються
|
|
430
|
+
окремо.
|
|
420
431
|
|
|
421
432
|
Зміна однієї поштової форми лишає другу рівно такою, як була.
|
|
422
433
|
|
|
@@ -328,31 +328,42 @@ RFC 5733 не визначає для контакту жодного відпо
|
|
|
328
328
|
очистити. Порожній пароль замінником не є: порожнє значення — це все одно значення, яке той, хто
|
|
329
329
|
його має, може пред'явити.
|
|
330
330
|
|
|
331
|
-
### Правило
|
|
331
|
+
### Правило оновлення: поштовий блок ЗАМІЩУЄТЬСЯ, а не зливається
|
|
332
332
|
|
|
333
|
-
|
|
333
|
+
Надішліть поштовий блок у `chg` — і реєстр **замістить** ним той, що тримає. Їх не зливають поле за
|
|
334
|
+
полем, тож усе, чого ви не подали, зникає.
|
|
334
335
|
|
|
335
|
-
|
|
336
|
+
RFC 5733 можна прочитати й інакше: у `chgPostalInfoType` кожна зі складових — name, org, addr —
|
|
337
|
+
необов'язкова, і це схоже на «не подавайте її, і реєстр збереже те, що має». Це читання небезпечне:
|
|
338
|
+
проти реєстру, який заміщає блок, **кожна з цих команд відповідає 1000**:
|
|
339
|
+
|
|
340
|
+
| Що ніс `chg` | Що лишилося в контакті |
|
|
336
341
|
|---|---|
|
|
337
|
-
|
|
|
338
|
-
|
|
|
339
|
-
|
|
|
342
|
+
| повний блок із `"org": ""` | організацію прибрано, адреса недоторкана |
|
|
343
|
+
| повний блок без ключа `org` | організацію **також прибрано** |
|
|
344
|
+
| самий лише `{"org": ""}` | **поштового блоку немає взагалі** — ані імені, ані вулиці, міста, індексу чи країни |
|
|
345
|
+
|
|
346
|
+
Тобто змінити одне поле адреси неможливо, і збій мовчазний: команда успішна, а даних немає. `name`,
|
|
347
|
+
`city` і `cc` обов'язкові в кожній поштовій зміні, і цей SDK без них відмовляє, — але ця перевірка
|
|
348
|
+
лише тримає кадр валідним і не здатна повернути `org`, `sp` чи `pc`, якого ви не подали.
|
|
340
349
|
|
|
341
|
-
|
|
342
|
-
зміна блоку `int` лишає блок `loc` точно таким, як був, і навпаки.
|
|
350
|
+
**Прочитайте блок, застосуйте свою зміну і надішліть його цілим:**
|
|
343
351
|
|
|
344
352
|
```python
|
|
345
|
-
|
|
346
|
-
client.contact.update("C1",
|
|
347
|
-
chg={"postal_info": {"type": "int", "org": "",
|
|
348
|
-
"city": "Kyiv", "cc": "UA"}})
|
|
353
|
+
current = client.contact.info("C1").postal_info()["int"]
|
|
354
|
+
client.contact.update("C1", chg={"postal_info": {**current, "type": "int", "org": ""}})
|
|
349
355
|
```
|
|
350
356
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
357
|
+
Чого заміщення не досягає — то це другої поштової форми: `int` і `loc` адресуються окремо, тож
|
|
358
|
+
заміна однієї лишає другу точно такою, як була.
|
|
359
|
+
|
|
360
|
+
Усередині блоку, який ви надсилаєте, порожній рядок так само очищає необов'язкове поле:
|
|
361
|
+
|
|
362
|
+
| Ви пишете | Що відбувається |
|
|
363
|
+
|---|---|
|
|
364
|
+
| ключ має значення | поле встановлюється в це значення |
|
|
365
|
+
| ключ містить `""` | поле надсилається порожнім, що **очищає** його |
|
|
366
|
+
| ключа немає | поле не надсилається — і реєстр видаляє те, що тримав |
|
|
356
367
|
|
|
357
368
|
Рядки вулиці поводяться інакше і на вашу користь: блок адреси, надісланий без жодного `street`,
|
|
358
369
|
лишає наявні рядки недоторканими.
|
|
@@ -508,18 +508,27 @@ class ContactUpdateBuilder(Builder):
|
|
|
508
508
|
org: Optional[str] = None,
|
|
509
509
|
state_province: Optional[str] = None,
|
|
510
510
|
postal_code: Optional[str] = None) -> "ContactUpdateBuilder":
|
|
511
|
-
"""Change the ASCII address.
|
|
511
|
+
"""Change the ASCII address.
|
|
512
|
+
|
|
513
|
+
THE BLOCK YOU PASS REPLACES THE ONE THE REGISTRY HOLDS. It is not merged field by field,
|
|
514
|
+
so anything you leave out is deleted:
|
|
512
515
|
|
|
513
516
|
================== ===========================================================
|
|
514
|
-
leave it out/None the field is not sent, and the registry keeps its value
|
|
515
517
|
give a value the field is set to it
|
|
516
|
-
give ``""`` the field is CLEARED — the
|
|
517
|
-
|
|
518
|
+
give ``""`` the field is CLEARED — the way to remove org, state province
|
|
519
|
+
or postal code
|
|
520
|
+
leave it out/None the field is not sent, and the registry DELETES what it held
|
|
518
521
|
================== ===========================================================
|
|
519
522
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
+
RFC 5733 can be read as "leave it out and the registry keeps its value", since every child
|
|
524
|
+
of chgPostalInfoType is optional, but that reading is not safe: against a registry that
|
|
525
|
+
replaces, a block sent without its org comes back 1000 with the org gone, and a block
|
|
526
|
+
carrying only an org leaves the contact with no postal address at all. ``name``, ``city``
|
|
527
|
+
and ``country_code`` are required here for that reason — but they only keep the frame valid,
|
|
528
|
+
they cannot restore a field you did not send. Read the current block with ``contact.info()``
|
|
529
|
+
and pass it back with your change applied.
|
|
530
|
+
|
|
531
|
+
The other form (local or international) IS untouched — the two are addressed separately.
|
|
523
532
|
"""
|
|
524
533
|
return self._address("int", name, city, country_code, street, org, state_province, postal_code)
|
|
525
534
|
|
|
@@ -706,24 +706,67 @@ def _append_postal(frame: Frame, parent: ET.Element, pi: Dict[str, Any],
|
|
|
706
706
|
"""
|
|
707
707
|
block = frame.ns(parent, _ADMIN, "contact:postalInfo", None, {"type": pi.get("type") or "int"})
|
|
708
708
|
|
|
709
|
-
|
|
710
|
-
|
|
709
|
+
# A postalInfo inside <contact:chg> REPLACES the stored one. It is not merged field by field.
|
|
710
|
+
#
|
|
711
|
+
# RFC 5733 can be read the other way: in chgPostalInfoType each of name/org/addr is optional,
|
|
712
|
+
# which looks like "omit it and the registry keeps what it holds". That reading is not safe.
|
|
713
|
+
# Against a registry that replaces, a chg carrying only <contact:org/> answers **1000** and leaves
|
|
714
|
+
# the contact with NO postalInfo at all — name, street, city, postal code and country gone, in
|
|
715
|
+
# both the int and loc blocks — and a complete block sent without an <org> removes the
|
|
716
|
+
# organisation just as surely.
|
|
717
|
+
#
|
|
718
|
+
# So the short form does not fail, it DESTROYS, and it reports success while doing it. A client
|
|
719
|
+
# cannot tell a replacing registry from a merging one, and the cost of guessing wrong is a
|
|
720
|
+
# registrant's postal address. Every change therefore carries the whole block.
|
|
721
|
+
for required in ("name", "city", "cc"):
|
|
722
|
+
if not str(pi.get(required) or "").strip():
|
|
723
|
+
raise ValidationException(
|
|
724
|
+
"postalInfo: a <contact:postalInfo> is REPLACED as a whole, not merged, so every "
|
|
725
|
+
f'change must carry the complete block — "{required}" is missing. (A registry that '
|
|
726
|
+
"replaces answers 1000 and silently drops everything you left out.) Read the current "
|
|
727
|
+
"block with contact.info() and send it back with your change applied."
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
# name is postalLineType, minLength 1: there is NO WAY to clear a name. An empty element is
|
|
731
|
+
# schema-invalid and the server answers a bare 2001 naming no field — refused here, where the
|
|
732
|
+
# message can say so.
|
|
733
|
+
_require_not_empty(pi.get("name"), "name")
|
|
734
|
+
frame.ns(block, _ADMIN, "contact:name", pi["name"])
|
|
735
|
+
|
|
736
|
+
# org is optPostalLineType, which HAS no minLength — an empty one is legal and is exactly how an
|
|
737
|
+
# organisation is removed.
|
|
711
738
|
if ("org" in pi) if partial else bool(pi.get("org")):
|
|
712
739
|
frame.ns(block, _ADMIN, "contact:org", pi.get("org") or "")
|
|
713
740
|
|
|
714
|
-
# <addr> is a sequence with a required city and cc,
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
741
|
+
# <addr> is a sequence with a required city and cc, and it is always emitted: the block replaces
|
|
742
|
+
# what the registry holds, so leaving the address out is what deletes it. The required parts were
|
|
743
|
+
# already asserted above, before anything was written.
|
|
744
|
+
|
|
718
745
|
addr = frame.ns(block, _ADMIN, "contact:addr")
|
|
719
746
|
for line in (pi.get("street") or []):
|
|
720
747
|
frame.ns(addr, _ADMIN, "contact:street", line)
|
|
721
|
-
frame.ns(addr, _ADMIN, "contact:city", pi
|
|
748
|
+
frame.ns(addr, _ADMIN, "contact:city", pi["city"])
|
|
722
749
|
if ("sp" in pi) if partial else bool(pi.get("sp")):
|
|
723
750
|
frame.ns(addr, _ADMIN, "contact:sp", pi.get("sp") or "")
|
|
724
751
|
if ("pc" in pi) if partial else bool(pi.get("pc")):
|
|
725
752
|
frame.ns(addr, _ADMIN, "contact:pc", pi.get("pc") or "")
|
|
726
|
-
frame.ns(addr, _ADMIN, "contact:cc", pi
|
|
753
|
+
frame.ns(addr, _ADMIN, "contact:cc", pi["cc"])
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def _require_not_empty(value: Any, field: str) -> None:
|
|
757
|
+
"""Refuse an empty value for an element whose schema type forbids one.
|
|
758
|
+
|
|
759
|
+
The distinction is not a house rule, it is contact-1.0.xsd: ``optPostalLineType`` (org, street,
|
|
760
|
+
sp) has no minLength and ``pcType`` has none either, so those four clear by being sent empty.
|
|
761
|
+
``postalLineType`` (name, city) has minLength 1 and ``ccType`` is exactly two characters, so an
|
|
762
|
+
empty one of those cannot be sent at all. Getting it wrong costs a round trip and returns a bare
|
|
763
|
+
2001 with no field named — the least useful error in EPP.
|
|
764
|
+
"""
|
|
765
|
+
if not str(value or "").strip():
|
|
766
|
+
raise ValidationException(
|
|
767
|
+
'postalInfo: "%s" cannot be empty — RFC 5733 gives it a schema type with a minimum '
|
|
768
|
+
"length, so there is no way to clear it. Omit the key to leave it unchanged." % field
|
|
769
|
+
)
|
|
727
770
|
|
|
728
771
|
|
|
729
772
|
def _append_disclose(frame: Frame, parent: ET.Element, disclose: Dict[str, Any]) -> None:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "epptools"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.2"
|
|
8
8
|
description = "Dependency-free Python client for EPP domain registries (RFC 5730-5734 over TLS), with a registry's own extensions discovered from its greeting."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|