Skip to content

Commit 9dc8dae

Browse files
authored
Merge pull request #7 from vesellov/master
added README.md and bumped version
2 parents ccbc99b + 099524b commit 9dc8dae

File tree

5 files changed

+171
-63
lines changed

5 files changed

+171
-63
lines changed

README.md

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# epp-python-client
2+
3+
[EPP](https://en.wikipedia.org/wiki/Extensible_Provisioning_Protocol) is Extensible Provisioning Protocol used for registrar-registry communication to register and manage domains.
4+
5+
The library provides an interface to the Extensible Provisioning Protocol:
6+
7+
- Python client library for sending/receiving/parsing XML-formatted EPP requests and responses
8+
- RPC server running as intermediate gateway between consumer application and EPP registry
9+
- RPC client library written for Python applications to be able to interract with the RPC server
10+
11+
12+
13+
## Install
14+
15+
pip install --upgrade https://github.com/datahaven-net/epp-python-client/archive/master.zip
16+
17+
18+
19+
## Usage
20+
21+
### Python client
22+
23+
from epp import epp_client
24+
conn = epp_client.EPPConnection(
25+
host='localhost',
26+
port=700,
27+
user='epp_user_01',
28+
password='some_secret',
29+
verbose=True,
30+
)
31+
conn.open()
32+
print(conn.domain_check(domain_name='domain-may-not-exist.com'))
33+
conn.close()
34+
35+
36+
The client is using `beautifulsoup4` to parse XML responses into Python objects. It is possible to access each element of the EPP response directly:
37+
38+
from epp import epp_client
39+
conn = epp_client.EPPConnection(
40+
host='localhost',
41+
port=700,
42+
user='epp_user_01',
43+
password='some_secret',
44+
verbose=True,
45+
return_soup=True,
46+
)
47+
conn.open()
48+
print(conn.domain_check(domain_name='domain-possibly-exist.com').find('response').resdata.find('domain:name').get('avail') == '0')
49+
conn.close()
50+
51+
52+
53+
### RPC server & client
54+
55+
It is also possible to use the library in another way using intermediate RabbitMQ queue server.
56+
If your application requires stable and reliable connection to the EPP registry system and be able to run many EPP requests per minute it is not possible to establish new EPP connection for each request using the only Python client.
57+
58+
There is an RPC server included in that library:
59+
60+
1. when RPC-server starts it first connects to the EPP back-end system via Python client library and holds the connection open after "greeting" and "login" command are processed
61+
2. then it connects to the RabbitMQ server to be able to receive and process RPC calls from external applications
62+
3. it also automatically reconnects to the EPP system when connection is closed on server side
63+
64+
To be able to run the server first you must create two text files holding credentials to your EPP registry system and RabbitMQ server:
65+
66+
###### epp_params.txt
67+
68+
epp.your-registry.com 700 epp_login epp_password
69+
70+
71+
###### rabbitmq_params.txt
72+
73+
localhost 5672 rabbitmq_rpc_server_login rabbitmq_rpc_server_password
74+
75+
76+
To start RPC server use the command above:
77+
78+
epp-gate -v --reconnect --epp=epp_params.txt --rabbitmq=rabbitmq_params.txt --queue=epp_messages
79+
80+
81+
Connection to the RPC server is carried out through RabbitMQ RPC-client also included in that library:
82+
83+
from epp import rpc_client
84+
rpc_client.make_client(
85+
rabbitmq_credentials=('localhost', 5672, 'rabbitmq_rpc_client_login', 'rabbitmq_rpc_client_password', ),
86+
rabbitmq_queue_name='epp_messages',
87+
)
88+
print(rpc_client.cmd_domain_check(domains=["domain-possibly-exist.com", "another-domain.com", ]))

setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup_params = dict(
44
name='epp-python-client',
5-
version='0.0.1',
5+
version='0.0.2',
66
author='Veselin Penev',
77
author_email='[email protected]',
88
packages=find_packages(where='src'),
@@ -18,6 +18,7 @@
1818
install_requires=[
1919
"beautifulsoup4",
2020
"lxml",
21+
"pika",
2122
],
2223
)
2324

src/epp/epp_client.py

+46-43
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class EPPResponseEmptyError(Exception):
6161

6262
class EPPConnection:
6363

64-
def __init__(self, host, port, user, password, verbose=False, raise_errors=False):
64+
def __init__(self, host, port, user, password, verbose=False, raise_errors=False, return_soup=None):
6565
self.host = host
6666
self.port = int(port)
6767
self.user = user
@@ -72,6 +72,7 @@ def __init__(self, host, port, user, password, verbose=False, raise_errors=False
7272
self.format_32 = get_format_32()
7373
self.verbose = verbose
7474
self.raise_errors = raise_errors
75+
self.return_soup = return_soup
7576

7677
def open(self, timeout=10):
7778
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -175,15 +176,15 @@ def write(self, xml):
175176
return None
176177
return ret
177178

178-
def call(self, cmd, soup=False):
179+
def call(self, cmd, soup=None):
179180
if self.write(cmd):
180181
if self.verbose:
181182
logger.debug('sent %d bytes:\n%s\n', len(cmd), cmd)
182183
raw = self.read()
183184
if raw:
184185
if self.verbose:
185186
logger.debug('received %d bytes:\n%s', len(raw), raw.decode())
186-
if soup:
187+
if soup is True or (self.return_soup is True and soup is not False):
187188
try:
188189
soup = BeautifulSoup(raw, "lxml")
189190
result = soup.find('result')
@@ -218,79 +219,81 @@ def greeting(self):
218219
logger.debug('connected to %s (v%s)', svid, self.version)
219220
return greeting_response
220221

221-
def login(self):
222+
def login(self, **kwargs):
222223
if self.verbose:
223224
logger.debug('attempt to login with: %r', self.user)
225+
kwargs['soup'] = False
224226
login_response = self.call(cmd=commands.login % dict(
225227
user=self.user,
226228
password=self.password,
227-
))
229+
), **kwargs)
228230
soup = BeautifulSoup(login_response, "lxml")
229231
result = soup.find('result')
230232
code = int(result.get('code'))
231233
if code != 1000:
232234
raise Exception('login response code: %r' % code)
233235
return True
234236

235-
def logout(self):
236-
logout_response = self.call(cmd=commands.logout)
237+
def logout(self, **kwargs):
238+
kwargs['soup'] = False
239+
logout_response = self.call(cmd=commands.logout, **kwargs)
237240
soup = BeautifulSoup(logout_response, "lxml")
238241
result = soup.find('result')
239242
code = int(result.get('code'))
240243
if code not in [1000, 1300, 1500, ]:
241244
raise Exception('logout response code: %r' % code)
242245
return True
243246

244-
def poll_req(self):
247+
def poll_req(self, **kwargs):
245248
return self.call(cmd=commands.poll % dict(
246249
cltrid=make_cltrid(),
247-
))
250+
), **kwargs)
248251

249-
def poll_ack(self, msg_id):
252+
def poll_ack(self, msg_id, **kwargs):
250253
return self.call(cmd=commands.poll_ack % dict(
251254
cltrid=make_cltrid(),
252255
msgid=msg_id,
253-
))
256+
), **kwargs)
254257

255-
def host_check(self, nameservers_list):
258+
def host_check(self, nameservers_list, **kwargs):
256259
return self.call(cmd=commands.nameserver.check % dict(
257260
cltrid=make_cltrid(),
258261
nameservers='\n'.join([commands.nameserver.single % ns for ns in nameservers_list]),
259-
))
262+
), **kwargs)
260263

261-
def host_info(self, nameserver):
264+
def host_info(self, nameserver, **kwargs):
262265
return self.call(cmd=commands.nameserver.info % dict(
263266
cltrid=make_cltrid(),
264267
nameserver=nameserver,
265-
))
268+
), **kwargs)
266269

267-
def host_create(self, nameserver, ip_addresses_list):
270+
def host_create(self, nameserver, ip_addresses_list, **kwargs):
268271
return self.call(cmd=commands.nameserver.create % dict(
269272
cltrid=make_cltrid(),
270273
nameserver=nameserver,
271274
ip_addresses='\n'.join([commands.nameserver.ip_address % ipaddr for ipaddr in ip_addresses_list])
272-
))
275+
), **kwargs)
273276

274-
def contact_check(self, contact):
277+
def contact_check(self, contact, **kwargs):
275278
return self.call(cmd=commands.contact.check % dict(
276279
cltrid=make_cltrid(),
277280
contacts=commands.contact.single % contact,
278-
))
281+
), **kwargs)
279282

280-
def contact_check_multiple(self, contacts_list):
283+
def contact_check_multiple(self, contacts_list, **kwargs):
281284
return self.call(cmd=commands.contact.check % dict(
282285
cltrid=make_cltrid(),
283286
contacts='\n'.join([commands.contact.single % c for c in contacts_list]),
284-
))
287+
), **kwargs)
285288

286-
def contact_info(self, contact_id, auth_info=None):
289+
def contact_info(self, contact_id, auth_info=None, **kwargs):
287290
return self.call(cmd=commands.contact.info % dict(
288291
cltrid=make_cltrid(),
289292
contact_id=contact_id,
290293
auth_info='' if not auth_info else commands.contact.auth_info % auth_info,
291-
))
294+
), **kwargs)
292295

293-
def contact_create(self, contact_id, voice=None, fax=None, email=None, contacts=[], auth_info=None):
296+
def contact_create(self, contact_id, voice=None, fax=None, email=None, contacts=[], auth_info=None, **kwargs):
294297
return self.call(cmd=commands.contact.create % dict(
295298
cltrid=make_cltrid(),
296299
contact_id=contact_id,
@@ -317,9 +320,9 @@ def contact_create(self, contact_id, voice=None, fax=None, email=None, contacts=
317320
) for cont in contacts
318321
]),
319322
auth_info='' if not auth_info else commands.contact.auth_info % auth_info,
320-
))
323+
), **kwargs)
321324

322-
def contact_update(self, contact_id, voice=None, fax=None, email=None, contacts=[], auth_info=None):
325+
def contact_update(self, contact_id, voice=None, fax=None, email=None, contacts=[], auth_info=None, **kwargs):
323326
return self.call(cmd=commands.contact.update % dict(
324327
cltrid=make_cltrid(),
325328
contact_id=contact_id,
@@ -346,37 +349,37 @@ def contact_update(self, contact_id, voice=None, fax=None, email=None, contacts=
346349
) for cont in contacts
347350
]),
348351
auth_info='' if not auth_info else commands.contact.auth_info % auth_info,
349-
))
352+
), **kwargs)
350353

351-
def contact_delete(self, contact_id):
354+
def contact_delete(self, contact_id, **kwargs):
352355
return self.call(cmd=commands.contact.delete % dict(
353356
cltrid=make_cltrid(),
354357
contact_id=contact_id,
355-
))
358+
), **kwargs)
356359

357-
def domain_check(self, domain_name):
360+
def domain_check(self, domain_name, **kwargs):
358361
return self.call(cmd=commands.domain.check % dict(
359362
cltrid=make_cltrid(),
360363
domain_names=commands.domain.single % domain_name,
361-
))
364+
), **kwargs)
362365

363-
def domain_check_multiple(self, domains_list):
366+
def domain_check_multiple(self, domains_list, **kwargs):
364367
return self.call(cmd=commands.domain.check % dict(
365368
cltrid=make_cltrid(),
366369
domain_names='\n'.join([
367370
commands.domain.single % d for d in domains_list
368371
]),
369-
))
372+
), **kwargs)
370373

371-
def domain_info(self, domain_name, auth_info=None):
374+
def domain_info(self, domain_name, auth_info=None, **kwargs):
372375
return self.call(cmd=commands.domain.info % dict(
373376
cltrid=make_cltrid(),
374377
domain_name=domain_name,
375378
auth_info='' if not auth_info else commands.domain.auth_info % auth_info,
376-
))
379+
), **kwargs)
377380

378381
def domain_create(self, domain_name, registrant, nameservers=[], period=1, period_units='y',
379-
contact_admin=None, contact_billing=None, contact_tech=None, auth_info=None):
382+
contact_admin=None, contact_billing=None, contact_tech=None, auth_info=None, **kwargs):
380383
return self.call(cmd=commands.domain.create % dict(
381384
cltrid=make_cltrid(),
382385
domain_name=domain_name,
@@ -389,20 +392,20 @@ def domain_create(self, domain_name, registrant, nameservers=[], period=1, perio
389392
contact_billing='' if not contact_billing else commands.domain.single_contact1 % dict(type='billing', id=contact_billing),
390393
contact_tech='' if not contact_tech else commands.domain.single_contact1 % dict(type='tech', id=contact_tech),
391394
auth_info='' if not auth_info else commands.domain.auth_info % auth_info,
392-
))
395+
), **kwargs)
393396

394-
def domain_renew(self, domain_name, cur_exp_date, period=1, period_units='y'):
397+
def domain_renew(self, domain_name, cur_exp_date, period=1, period_units='y', **kwargs):
395398
return self.call(cmd=commands.domain.renew % dict(
396399
cltrid=make_cltrid(),
397400
domain_name=domain_name,
398401
cur_exp_date=cur_exp_date,
399402
period='' if period is None else commands.domain.period % dict(value=period, units=period_units or 'y'),
400-
))
403+
), **kwargs)
401404

402405
def domain_update(self, domain_name, auth_info=None,
403406
add_nameservers=[], remove_nameservers=[],
404407
add_contacts=[], remove_contacts=[], change_registrant=None,
405-
rgp_restore=None, rgp_restore_report=None):
408+
rgp_restore=None, rgp_restore_report=None, **kwargs):
406409
restore_extension = ''
407410
if rgp_restore:
408411
restore_extension = commands.domain.restore_request_extension
@@ -428,12 +431,12 @@ def domain_update(self, domain_name, auth_info=None,
428431
commands.domain.single_registrant % change_registrant
429432
),
430433
restore_extension=restore_extension,
431-
))
434+
), **kwargs)
432435

433-
def domain_transfer(self, domain_name, auth_info=None, period=None, period_units=None):
436+
def domain_transfer(self, domain_name, auth_info=None, period=None, period_units=None, **kwargs):
434437
return self.call(cmd=commands.domain.transfer % dict(
435438
cltrid=make_cltrid(),
436439
domain_name=domain_name,
437440
auth_info='' if not auth_info else commands.domain.auth_info % auth_info,
438441
period='' if period is None else commands.domain.period % dict(value=period, units=period_units or 'y'),
439-
))
442+
), **kwargs)

0 commit comments

Comments
 (0)