Skip to content

Commit 4303dfa

Browse files
committed
Initial commit using urllib.
1 parent e2b3f24 commit 4303dfa

File tree

6 files changed

+188
-0
lines changed

6 files changed

+188
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__
2+
.venv

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
requests

test.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/sh -e
2+
usage() {
3+
echo "Usage: $0 [-h]" 1>&2;
4+
echo " -h Display this help message." 1>&2;
5+
echo " -i Initialize virtual environment." 1>&2;
6+
exit 1;
7+
}
8+
9+
while getopts "hi" opt; do
10+
case $opt in
11+
i)
12+
i=1
13+
;;
14+
*)
15+
usage
16+
;;
17+
esac
18+
done
19+
20+
PYTHON=python3.9
21+
22+
if [ -n "$i" ]; then
23+
$PYTHON -m venv .venv
24+
fi
25+
source .venv/bin/activate
26+
if [ -n "$i" ]; then
27+
$PYTHON -m pip install -r requirements.txt
28+
fi
29+
$PYTHON -m unittest

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import test_vtwsclib

tests/test_vtwsclib.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import os
2+
import unittest
3+
4+
from vtwsclib import Vtapi
5+
6+
7+
class TestVtapi(unittest.TestCase):
8+
@classmethod
9+
def setUpClass(cls):
10+
if not all(key in os.environ for key in ['VTIGER_HOST', 'VTIGER_USER', 'VTIGER_PASS']):
11+
cls.skipTest("environment variables not configured")
12+
13+
def test_login(self):
14+
with Vtapi(os.environ['VTIGER_HOST']) as api:
15+
api.login(os.environ['VTIGER_USER'], os.environ['VTIGER_PASS'])
16+
17+
def test_count(self):
18+
with Vtapi(os.environ['VTIGER_HOST']) as api:
19+
api.login(os.environ['VTIGER_USER'], os.environ['VTIGER_PASS'])
20+
self.assertGreater(api.count('Quotes'), 0)
21+
22+
def test_retrieve(self):
23+
with Vtapi(os.environ['VTIGER_HOST']) as api:
24+
api.login(os.environ['VTIGER_USER'], os.environ['VTIGER_PASS'])
25+
self.assertIn('website', api.retrieve('CompanyDetails')[0])

vtwsclib.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import hashlib
2+
import json
3+
import logging
4+
import urllib.parse
5+
import urllib.request
6+
7+
_logger = logging.getLogger(__name__)
8+
9+
10+
class Vtapi:
11+
def __init__(self, url):
12+
self.session_name = None
13+
self.url = url + '/webservice.php'
14+
15+
def __enter__(self):
16+
return self
17+
18+
def __exit__(self, type, value, traceback):
19+
try:
20+
self.logout()
21+
finally:
22+
pass
23+
24+
def count(self, module):
25+
query = f"select count(*) from {module};"
26+
result = self.query(query)
27+
return int(result[0]['count'])
28+
29+
def create(self, module, values):
30+
data = {
31+
'operation': 'create',
32+
'sessionName': self.session_name,
33+
'elementType': module,
34+
'element': json.dumps(values),
35+
}
36+
response = self._urlopen(self.url, data=data)
37+
return self._result(response)
38+
39+
def download(self, id):
40+
params = {
41+
'operation': 'download',
42+
'sessionName': self.session_name,
43+
'id': id,
44+
}
45+
response = self._urlopen(self.url, params=params)
46+
return self._result(response)
47+
48+
def listtypes(self):
49+
params = {
50+
'operation': 'listtypes',
51+
'sessionName': self.session_name,
52+
}
53+
response = self._urlopen(self.url, params=params)
54+
return self._result(response)
55+
56+
def login(self, username, accesskey):
57+
token = self._getchallenge(username)
58+
self.session_name = self._login(username, token, accesskey)
59+
60+
def logout(self):
61+
if self.session_name:
62+
data = {
63+
'operation': 'logout',
64+
'sessionName': self.session_name,
65+
}
66+
self._urlopen(self.url, data=data)
67+
self.session_name = None
68+
69+
def query(self, query):
70+
params = {
71+
'operation': 'query',
72+
'sessionName': self.session_name,
73+
'query': query,
74+
}
75+
try:
76+
response = self._urlopen(self.url, params=params)
77+
return self._result(response)
78+
except:
79+
_logger.error("failed to query '%s'", query)
80+
raise
81+
82+
def retrieve(self, module, limit=0, offset=0):
83+
query = f"select * from {module};"
84+
if limit or offset:
85+
query = query[:-1] + f" limit {offset}, {limit};"
86+
return self.query(query)
87+
88+
def _getchallenge(self, username):
89+
params = {
90+
'operation': 'getchallenge',
91+
'username': username,
92+
}
93+
response = self._urlopen(self.url, params=params)
94+
result = self._result(response)
95+
token = result['token']
96+
return token
97+
98+
def _login(self, username, token, accesskey):
99+
hasher = hashlib.md5()
100+
hasher.update(token.encode('utf-8'))
101+
hasher.update(accesskey.encode('utf-8'))
102+
data = {
103+
'operation': 'login',
104+
'username': username,
105+
'accessKey': hasher.hexdigest(),
106+
}
107+
response = self._urlopen(self.url, data=data)
108+
body = self._result(response)
109+
return body['sessionName']
110+
111+
def _result(self, response):
112+
body = json.loads(response)
113+
if not body['success']:
114+
raise VtapiError(**body['error'], status=response.status_code)
115+
return body['result']
116+
117+
def _urlopen(self, url, data=None, params=None):
118+
if params:
119+
url = url + '?' + urllib.parse.urlencode(params)
120+
if data:
121+
data = urllib.parse.urlencode(data).encode('utf-8')
122+
with urllib.request.urlopen(url, data=data) as f:
123+
return f.read().decode('utf-8')
124+
125+
class VtapiError(Exception):
126+
def __init__(self, code, message, status=None):
127+
self.code = code
128+
self.message = message
129+
self.status = status
130+
super().__init__(f"{self.code}: {self.message}")

0 commit comments

Comments
 (0)