1
1
__all__ = ['get_search' ]
2
2
3
3
import flask
4
- import simplejson as json
5
-
4
+ try :
5
+ import sqlalchemy
6
+ import sqlalchemy .exc
7
+ import sqlalchemy .ext .declarative
8
+ import sqlalchemy .orm
9
+ import sqlalchemy .sql .functions
10
+ except ImportError as e :
11
+ _sqlalchemy_import_error = e
12
+ sqlalchemy = None
13
+
14
+ import config
6
15
import signals
7
16
import storage
8
17
import toolkit
9
18
10
19
from .app import app
11
20
12
21
22
+ cfg = config .load ()
23
+ index = None
13
24
store = storage .load ()
14
- #index = Index()
15
25
16
26
17
- class Index (dict ):
27
+ if sqlalchemy :
28
+ Base = sqlalchemy .ext .declarative .declarative_base ()
29
+
30
+ class Version (Base ):
31
+ "Schema version for the search-index database"
32
+ __tablename__ = 'version'
33
+
34
+ id = sqlalchemy .Column (sqlalchemy .Integer , primary_key = True )
35
+
36
+ def __repr__ (self ):
37
+ return '<{0}(id={1})>' .format (type (self ).__name__ , self .id )
38
+
39
+ class Repository (Base ):
40
+ "Repository description"
41
+ __tablename__ = 'repository'
42
+
43
+ id = sqlalchemy .Column (sqlalchemy .Integer , primary_key = True )
44
+ name = sqlalchemy .Column (
45
+ sqlalchemy .String , nullable = False , unique = True )
46
+ description = sqlalchemy .Column (sqlalchemy .String )
47
+
48
+ def __repr__ (self ):
49
+ return "<{0}(name='{1}', description='{2}')>" .format (
50
+ type (self ).__name__ , self .name , self .description )
51
+
52
+
53
+ class Index (object ):
18
54
"""Maintain an index of repository data
19
55
20
56
The index is a dictionary. The keys are
@@ -23,43 +59,33 @@ class Index (dict):
23
59
24
60
index['library/ubuntu'] = 'An ubuntu image...'
25
61
"""
26
- def __init__ (self ):
27
- super (Index , self ).__init__ ()
62
+ def __init__ (self , database ):
63
+ self ._engine = sqlalchemy .create_engine (database )
64
+ self ._session = sqlalchemy .orm .sessionmaker (bind = self ._engine )
28
65
self .version = 1
29
- self .load ()
66
+ self ._setup_database ()
30
67
signals .repository_created .connect (self ._handler_repository_created )
31
- signals .repository_updated .connect (self ._handler_repository_created )
68
+ signals .repository_updated .connect (self ._handler_repository_updated )
32
69
signals .repository_deleted .connect (self ._handler_repository_deleted )
33
70
34
- def load (self ):
35
- regenerated = False
71
+ def _setup_database (self ):
72
+ session = self . _session ()
36
73
try :
37
- index_content = store .get_content (store .index_path ())
38
- except (OSError , IOError ):
39
- index_data = self ._regenerate_index ()
40
- regenerated = True
41
- else :
42
- data = json .loads (index_content )
43
- if data ['version' ] != self .version :
74
+ version = session .query (
75
+ sqlalchemy .sql .functions .max (Version .id )).first ()[0 ]
76
+ except sqlalchemy .exc .OperationalError :
77
+ version = None
78
+ if version :
79
+ if version != self .version :
44
80
raise NotImplementedError (
45
- 'unrecognized search index version {0}' .format (
46
- data ['version' ]))
47
- index_data = data ['index' ]
48
- self .clear ()
49
- self .update (index_data )
50
- if regenerated :
51
- self .save ()
52
-
53
- def save (self ):
54
- index_data = {
55
- 'version' : self .version ,
56
- 'index' : dict (self ),
57
- }
58
- store .put_content (store .index_path (), json .dumps (index_data ))
59
-
60
- def _regenerate_index (self ):
61
- index_data = {}
62
- description = '' # TODO(wking): store descriptions
81
+ 'unrecognized search index version {0}' .format (version ))
82
+ else :
83
+ self ._generate_index (session = session )
84
+ session .close ()
85
+
86
+ def _generate_index (self , session ):
87
+ Base .metadata .create_all (self ._engine )
88
+ session .add (Version (id = self .version ))
63
89
try :
64
90
namespace_paths = list (
65
91
store .list_directory (path = store .repositories ))
@@ -74,41 +100,68 @@ def _regenerate_index(self):
74
100
repository_paths = []
75
101
for path in repository_paths :
76
102
repository = path .rsplit ('/' , 1 )[- 1 ]
77
- key = '{0}/{1}' .format (namespace , repository )
78
- index_data [key ] = description
79
- return index_data
103
+ name = '{0}/{1}' .format (namespace , repository )
104
+ description = None # TODO(wking): store descriptions
105
+ session .add (Repository (name = name , description = description ))
106
+ session .commit ()
80
107
81
108
def _handler_repository_created (
82
109
self , sender , namespace , repository , value ):
83
- key = '{0}/{1}' .format (namespace , repository )
110
+ name = '{0}/{1}' .format (namespace , repository )
84
111
description = '' # TODO(wking): store descriptions
85
- self [key ] = description
86
- self .save ()
87
-
88
- def _handler_repository_deleted (self , sender , namespace , repository ):
89
- key = '{0}/{1}' .format (namespace , repository )
90
- try :
91
- self .pop (key )
92
- except KeyError :
93
- pass
94
- else :
95
- self .save ()
112
+ session = self ._session ()
113
+ session .add (Repository (name = name , description = description ))
114
+ session .commit ()
115
+ session .close ()
96
116
117
+ def _handler_repository_updated (
118
+ self , sender , namespace , repository , value ):
119
+ name = '{0}/{1}' .format (namespace , repository )
120
+ description = '' # TODO(wking): store descriptions
121
+ session = self ._session ()
122
+ session .query (Repository ).filter (
123
+ Repository .name == name ).update (
124
+ values = {'description' : description },
125
+ synchronize_session = False )
126
+ session .commit ()
127
+ session .close ()
97
128
98
- index = Index ()
129
+ def _handler_repository_deleted (self , sender , namespace , repository ):
130
+ name = '{0}/{1}' .format (namespace , repository )
131
+ session = self ._session ()
132
+ session .query (Repository ).filter (Repository .name == name ).delete ()
133
+ session .commit ()
134
+ session .close ()
135
+
136
+ def results (self , search_term ):
137
+ session = self ._session ()
138
+ like_term = '%{}%' .format (search_term )
139
+ repositories = session .query (Repository ).filter (
140
+ sqlalchemy .sql .or_ (
141
+ Repository .name .like (like_term ),
142
+ Repository .description .like (like_term )))
143
+ return [
144
+ {
145
+ 'name' : repo .name ,
146
+ 'description' : repo .description ,
147
+ }
148
+ for repo in repositories ]
149
+
150
+
151
+ # Enable the search index
152
+ if cfg .search_index :
153
+ if not sqlalchemy :
154
+ raise _sqlalchemy_import_error
155
+ index = Index (database = cfg .search_index )
99
156
100
157
101
158
@app .route ('/v1/search' , methods = ['GET' ])
102
159
def get_search ():
103
160
search_term = flask .request .args .get ('q' , '' )
104
- results = [
105
- {
106
- 'name' : name ,
107
- 'description' : description ,
108
- }
109
- for name , description in index .items ()
110
- if search_term in name
111
- or search_term in description ]
161
+ if index is None :
162
+ results = []
163
+ else :
164
+ results = index .results (search_term = search_term )
112
165
return toolkit .response ({
113
166
'query' : search_term ,
114
167
'num_results' : len (results ),
0 commit comments