optimisations et meilleure gestion des erreurs
[restcoda.git] / rest.wsgi
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 import sys, traceback
5
6 # configuration (codes d'accès à la base MS-SQL)
7 sys.path.append('/home/thomas/public_html/')
8 import rest_config
9
10 # pour savoir quel objet interroger : Routes
11 from routes import Mapper
12 from routes.middleware import RoutesMiddleware
13 # et analyse des paramètres (POST et query_string) pour filtrage
14 from paste.request import parse_formvars
15
16 # pour chercher les données sur MS-SQL
17 from pymssql import connect
18
19 # pour afficher le résultat : jinja
20 from jinja import Environment, FileSystemLoader
21 from jinja.filters import stringfilter
22 import jinja.exceptions
23
24 # TODO systeme de cache : beaker
25 # from beaker.middleware import CacheMiddleware
26
27 # formats de sortie autorisés, et content-type correspondant
28 # formats = { 'xml': 'application/xml', 'html': 'text/html', 'txt': 'text/plain', 'json': 'application/json', 'rss': 'application/rss+xml' }
29 formats = { 'xml': 'application/xml', 'html': 'text/html', 'txt': 'text/plain', 'json': 'text/plain', 'rss': 'application/rss+xml' }
30
31 # les routes RESTful (cf http://routes.groovie.org/manual.html#restful-services)
32 mapper = Mapper()
33 mapper.resource('demlog','demlog')
34 mapper.resource('comlog','comlog')
35 mapper.resource('demdep','demdep')
36 mapper.resource('comdep','comdep')
37 mapper.resource('dempub','dempub')
38 mapper.resource('compub','compub')
39 mapper.resource('comare','comare')
40 # pour les comarexxx où xxx est un code d'implantation
41 mapper.resource('comarei','comare:(impl)',controller='comare')
42 mapper.resource('comsra','comsre')
43 mapper.resource('comsrai','comsre:(impl)',controller='comsre')
44 mapper.resource('dem','dem')
45 mapper.resource('com','com')
46
47 class objetsql(object):
48     """objet de base : dispose d'un accès à MS-SQL (lire les données) et d'un accès à jinja (rendu des données)"""
49     def __init__(self, environ):
50         self.environ = environ
51     def template(self,template):
52         self.jinja = Environment(loader=FileSystemLoader('/home/thomas/public_html/'))
53         self.outputformat = self.environ['wsgiorg.routing_args'][1].get('format','xml')
54         return self.jinja.get_template('%s.%s' % (template,self.outputformat))
55     def cursor(self):
56         if not hasattr(self,'bd'):
57             self.bd = connect(host=rest_config.host,user=rest_config.user,password=rest_config.password,database=rest_config.database)
58         return self.bd.cursor()
59
60 class document(objetsql):
61     """objet document CODA (demlog, comlog, demdep... ils ont tous le même format)"""
62     def __init__(self, environ, code_document='%', basename_template='document'):
63         super(document, self).__init__(environ)
64         self.code_document = code_document
65         self.basename_template = basename_template
66
67     def _get_index(self, code):
68         """renvoie une liste de documents"""
69         # connexion a la base de données
70         cursor = self.cursor()
71         # extraction des données
72         cursor.execute("select top 15 * from auf_v_acces_demcom where code like '%s' order by date_modif desc" % code)
73         index = {}
74         index['code'] = code
75         items = []
76         while 1:
77             item = dict_fetchone( cursor )
78             if item == None: break
79             item['code_rest'] = coda2rest(item['code'])
80             items.append(item)
81         index['documents'] = items
82         return index
83
84     def _get_details(self,code,id):
85          """renvoie la liste des détails pour un document"""
86         details = []
87         cursor = self.cursor()
88         cursor.execute("select * from auf_v_acces_dtls_demcom where code like '%s' and numero = %d" % (code, id))
89         while 1:
90             detail = dict_fetchone(cursor)
91             if detail == None: break
92             details.append(detail)
93         return details
94
95     def _get_document(self,code,id):
96          """renvoie un document"""
97         cursor = self.cursor()
98         cursor.execute("select top 1 * from auf_v_acces_demcom where code like '%s' and numero = %d" % (code, id))
99         document = dict_fetchone(cursor)
100         if document == None:
101             raise "document inexistant"
102         document['code_rest'] = coda2rest( document['code'] )
103         document['details'] = self._get_details( code,id )
104         return document
105
106     def index(self):
107         """renvoie une liste de documents formatée"""
108         # preparation du modèle
109         template = self.template('%s-index' % self.basename_template)
110         # recherche des documents concernés
111         index = self._get_index( self.code_document )
112         # envoi des données au template
113         output = template.render( index )
114         return self.outputformat, output
115
116     def show(self):
117         """renvoie un document formaté"""
118         # return 'txt', '%s' % self.environ # pour debug
119         id = int(self.environ['wsgiorg.routing_args'][1]['id'])
120         template = self.template( self.basename_template )
121         document = self._get_document( self.code_document, id )
122         output = template.render( document )
123         return self.outputformat, output
124
125 class dem(document):
126     def __init__(self, environ):
127         super(dem, self).__init__(environ, code_document = 'DEM-%')
128
129 class com(document):
130     def __init__(self, environ):
131         super(com, self).__init__(environ, code_document = 'COM-%')
132
133 class demlog(document):
134     def __init__(self, environ):
135         super(demlog, self).__init__(environ, code_document = 'DEM-LOG-AUF')
136
137 class comlog(document):
138     def __init__(self, environ):
139         super(comlog, self).__init__(environ, code_document = 'COM-LOG-AUF')
140
141 class demdep(document):
142     def __init__(self, environ):
143         super(demdep, self).__init__(environ, code_document = 'DEM-DEP-AUF')
144
145 class comdep(document):
146     def __init__(self, environ):
147         super(comdep, self).__init__(environ, code_document = 'COM-DEP-AUF')
148
149 class dempub(document):
150     def __init__(self, environ):
151         super(dempub, self).__init__(environ, code_document = 'DEM-PUB-AUF')
152
153 class compub(document):
154     def __init__(self, environ):
155         super(compub, self).__init__(environ, code_document = 'COM-PUB-AUF')
156
157 class comsre(document):
158     def __init__(self, environ):
159         # est-ce un appel de comsre ou comsrexxx (avec xxx = implantation)
160         impl = environ['wsgiorg.routing_args'][1].get('impl','%')
161         super(comsre, self).__init__(environ, code_document = 'COM-SRE-%s' % impl)
162
163 class comare(document):
164     def __init__(self, environ):
165         impl = environ['wsgiorg.routing_args'][1].get('impl','%')
166         super(comare, self).__init__(environ, code_document = 'COM-ARE-%s' % impl)
167
168 #
169 # fin des objets accessibles
170 #
171
172 def dispatcher(environ, start_response):
173     """dispatch vers la bonne methode du bon objet, et retour WSGI"""
174     parse_formvars(environ)
175     results = environ['wsgiorg.routing_args'][1]
176     try:
177         target_class = globals()[results['controller']]
178         method = getattr(target_class,results['action'])
179     except:
180         start_response("404 Not Found", [('Content-type', 'text/html')])
181         return '<html><body><h2>404 objet ou action invalide</h2><pre>%s: %s\n%s</pre></body></html>' % ( sys.exc_info()[0] , sys.exc_info()[1] , traceback.format_exc())
182     try:
183         type, output = method(target_class(environ))
184         start_response("200 OK", [('Content-type', formats[type])])
185         return output.encode('utf-8')
186     except jinja.exceptions.TemplateNotFound, template:
187         start_response("415 Unsupported Media Type", [('Content-type', 'text/html')])
188         return '<html><body><h2>415 format non supporté (%s inexistant)</h2></body></html>' % template
189     except:
190         start_response("500 INTERNAL ERROR", [('Content-type', 'text/html')])
191         return '<html><body><h2>500 erreur lors du traitement</h2><pre>%s: %s\n%s</pre></body></html>' % ( sys.exc_info()[0] , sys.exc_info()[1] , traceback.format_exc())
192
193 # application qui sera lancée par mod_wsgi : on route et on dispatche
194 application = RoutesMiddleware( dispatcher, mapper)
195 # TODO : ajouter un middleware de cache (beaker, basé sur REQUEST_URI quand la methode est GET)
196
197
198 #
199 # petits utilitaires
200 #
201 def dict_fetchone(cursor):
202     """Renvoie le resultat d'un fetchone dans un dictionnaire"""
203     result = cursor.fetchone()
204     if result == None: return None
205     result_dict = {}
206     for i in range(len(result)):
207         if isinstance( result[i], str ):
208             result_dict[cursor.description[i][0]] = result[i].decode('iso-8859-1')
209         else:
210             result_dict[cursor.description[i][0]] = result[i]
211     return result_dict
212
213 import re 
214 p = re.compile('(dem|com)-(...)-(...)',re.IGNORECASE)
215 def coda2rest(value):
216     """Traduit un nom CODA vers la base REST correspodante, 
217     par exemple DEM-LOG-AUF en demlog ou COM-ARE-VN3 en comarevn3"""
218     m = p.search(value)
219     if m == None: return value
220     if m.group(3).lower() == 'auf':
221         return m.group(1).lower() + m.group(2).lower()
222     else:
223         return m.group(1).lower() + m.group(2).lower() + m.group(3).lower()
224