on master help sign in
#62 WIP dbapi python
commita99255ce8944a867a9d7c65b4013666d88854d98 authorAgentM <agentm@themactionfaction.com> dateFri 17 Feb 2017, 15:56 (10 years ago) parent02b60f1
3 files changed+259−14
src/bin/ProjectM36/Server/WebSocket.hs+35−14 src/python/projectm36/__init__.py+215 src/python/test.py+9
src/bin/ProjectM36/Server/WebSocket.hs+35−14
@@ -6,6 +6,7 @@ module ProjectM36.Server.WebSocket where
66-- proxy all connections to it through ProjectM36.Client
77import Control.Monad (forever)
88import qualified Data.Text as T
9+import Data.Text.Encoding as TE
910import qualified Network.WebSockets as WS
1011import ProjectM36.Server.RemoteCallTypes.Json ()
1112import ProjectM36.Client.Json ()
@@ -42,26 +43,46 @@ websocketProxyServer port host pending = do
4243 pInfo <- promptInfo sessionId dbconn
4344 --figure out why sending three times during startup is necessary
4445 sendPromptInfo pInfo conn
45− sendPromptInfo pInfo conn
46+ --sendPromptInfo pInfo conn
47+ sendError conn (RelationVariable "spammo" ())
4648 msg <- (WS.receiveData conn) :: IO T.Text
4749 let tutdprefix = "executetutd:"
50+ evalRelExprPrefix = "evalrelexpr:"
51+ dropt t = T.drop (T.length t) msg
4852 case msg of
4953 _ | tutdprefix `T.isPrefixOf` msg -> do
50− let tutdString = T.drop (T.length tutdprefix) msg
51− case parseTutorialD tutdString of
52− Left err -> handleOpResult conn dbconn (DisplayErrorResult ("parse error: " `T.append` T.pack (show err)))
53− Right parsed -> do
54− let timeoutFilter = \exc -> if exc == RequestTimeoutException
55− then Just exc
56− else Nothing
57− responseHandler = do
58− result <- evalTutorialD sessionId dbconn SafeEvaluation parsed
59− pInfo' <- promptInfo sessionId dbconn
60− sendPromptInfo pInfo' conn
61− handleOpResult conn dbconn result
62− catchJust timeoutFilter responseHandler (\_ -> handleOpResult conn dbconn (DisplayErrorResult "Request Timed Out."))
54+ let tutdString = dropt tutdprefix
55+ handleExecuteTutorialD conn sessionId dbconn tutdString
56+ _ | evalRelExprPrefix `T.isPrefixOf` msg -> do
57+ let relExprJson = dropt evalRelExprPrefix
58+ handleEvalRelExpr conn sessionId dbconn relExprJson
6359 _ -> unexpectedMsg
6460 pure ()
61+
62+handleExecuteTutorialD :: WS.Connection -> SessionId -> Connection -> T.Text -> IO ()
63+handleExecuteTutorialD wsconn sessionId dbconn tutdString = do
64+ case parseTutorialD tutdString of
65+ Left err -> handleOpResult wsconn dbconn (DisplayErrorResult ("parse error: " `T.append` T.pack (show err)))
66+ Right parsed -> do
67+ let timeoutFilter = \exc -> if exc == RequestTimeoutException
68+ then Just exc
69+ else Nothing
70+ responseHandler = do
71+ result <- evalTutorialD sessionId dbconn SafeEvaluation parsed
72+ pInfo' <- promptInfo sessionId dbconn
73+ sendPromptInfo pInfo' wsconn
74+ handleOpResult wsconn dbconn result
75+ catchJust timeoutFilter responseHandler (\_ -> handleOpResult wsconn dbconn (DisplayErrorResult "Request Timed Out."))
76+
77+handleEvalRelExpr :: WS.Connection -> SessionId -> Connection -> T.Text -> IO ()
78+handleEvalRelExpr wsconn sessionId dbconn relExprText = do
79+ case (eitherDecodeStrict' (TE.encodeUtf8 relExprText)) :: Either String RelationalExpr of
80+ Left err -> sendError wsconn err --perhaps send an error data type instead (?)
81+ Right relExpr -> do
82+ result <- executeRelationalExpr sessionId dbconn relExpr
83+ case result of
84+ Left err -> sendError wsconn err
85+ Right rel -> handleOpResult wsconn dbconn (DisplayRelationResult rel)
6586
6687notificationCallback :: WS.Connection -> NotificationCallback
6788notificationCallback conn notifName evaldNotif = WS.sendTextData conn (encode (object ["notificationname" .= notifName,
src/python/projectm36/__init__.pynew file+215
@@ -0,0 +1,215 @@
1+import websocket as ws
2+#this websocket-client module supports both python 2.7 and 3.x- the "websockets" module supports python 3.4+ but relies on asyncio. It might be possible to mix the modules in this driver or it make more sense to create a separate python 3.4+ database driver.
3+import exceptions
4+import json
5+
6+apilevel = '2.0'
7+threadsafety = 2
8+paramstyle = None #not applicable
9+
10+class Error(exceptions.StandardError):
11+ pass
12+
13+class Warning(exceptions.StandardError):
14+ pass
15+
16+class InterfaceError(Error):
17+ pass
18+
19+class DatabaseError(Error):
20+ pass
21+
22+class InternalError(DatabaseError):
23+ pass
24+
25+class OperationalError(DatabaseError):
26+ pass
27+
28+class ProgrammingError(DatabaseError):
29+ pass
30+
31+class IntegrityError(DatabaseError):
32+ pass
33+
34+class DataError(DatabaseError):
35+ pass
36+
37+class NotSupportedError(DatabaseError):
38+ pass
39+
40+class ProjectM36Connection(object):
41+ def __init__(self, proto, host, port, path, dbname):
42+ assert proto in ('ws', 'wss'), "proto must be one of 'ws' or 'wss'"
43+ self.proto = proto
44+ self.host = host
45+ self.port = port
46+ self.path = path
47+ self.dbname = dbname
48+ self.conn = None
49+
50+ def connect(self):
51+ port = ''
52+ if self.port:
53+ port = ':{}'.format(self.port)
54+
55+ path = ''
56+ if self.path:
57+ path = '/{}'.format(self.path)
58+
59+
60+ self.conn = ws.create_connection('{}://{}{}{}'.format(self.proto,
61+ self.host,
62+ port,
63+ path))
64+ self.conn.send('connectdb:{}'.format(self.dbname))
65+ result = self.conn.recv()
66+ print result
67+
68+ result = self.conn.recv()
69+ print result
70+
71+ return True
72+
73+ def close(self):
74+ if self.conn is None: #already closed
75+ raise ProgrammingError('attempt to close connection which was not open')
76+ self.conn.close()
77+ self.conn = None
78+
79+ def commit(self):
80+ self.executeTutorialD(':commit')
81+
82+ def rollback(self):
83+ self.executeTutorialD(':rollback')
84+
85+ def cursor(self):
86+ return Cursor(self)
87+
88+ def _send(self, t):
89+ print 'SEND:', t
90+ return self.conn.send(t)
91+
92+ def _recv(self):
93+ return self.conn.recv()
94+
95+class ProjectM36JSONEncoder(json.JSONEncoder):
96+ def default(self, obj):
97+ if isinstance(obj, RelationalExpr):
98+ return obj.as_json()
99+ else:
100+ super(ProjectM36JSONEncoder, self).default(obj)
101+
102+class ProjectM36JSONDecoder(json.JSONDecoder):
103+ def decode(self, s):
104+ if
105+ super(ProjectM36JSONDecoder, self).decode(s)
106+
107+class Cursor(object):
108+ def __init__(self, conn):
109+ self._arraysize = 1
110+ self._conn = conn
111+
112+ @property
113+ def description(self):
114+ return []
115+
116+ @property
117+ def rowcount(self):
118+ return 0
119+
120+ #def callproc(self): #not applicable
121+
122+ def close(self):
123+ return
124+
125+ def executeTutorialD(self, tutoriald):
126+ self.conn.send()
127+
128+ def execute(self, relationalExpression):
129+ relExprJson = json.dumps(relationalExpression, cls=ProjectM36JSONEncoder)
130+ self._conn._send('evalrelexpr:{}'.format(relExprJson))
131+ result = self._conn._recv()
132+ rel = json.loads(result, cls=ProjectM36JSONDecoder)
133+ return rel
134+
135+ def executemany(self, op, params):
136+ raise NotImplementedError()
137+
138+ def fetchone(self):
139+ return 0
140+
141+ def fetchmany(self):
142+ return 0
143+
144+ def fetchall(self):
145+ return 0
146+
147+ #def nextset(self): #not applicable
148+
149+ @property
150+ def arraysize(self):
151+ return self._arraysize
152+
153+ @arraysize.setter
154+ def set_arraysize(self, size):
155+ self._arraysize = size
156+
157+ def setinputsizes(self, sizes):
158+ return
159+
160+ def setoutputsize(self, size, column):
161+ return
162+
163+def connect(proto, host, port, path, dbname):
164+ conn = ProjectM36Connection(proto, host, port, path, dbname)
165+ conn.connect()
166+ return conn
167+
168+class RelationalExpr(object):
169+ def as_json(self):
170+ return {'tag':self.__class__.__name__,
171+ 'contents':self.json_contents()}
172+
173+class RelationVariable(RelationalExpr):
174+ def __init__(self, rvname):
175+ self.name = rvname
176+
177+ def json_contents(self):
178+ return [self.name, []]
179+
180+class Equals(RelationalExpr):
181+ def __init__(self, exprA, exprB):
182+ self.a = exprA
183+ self.b = exprB
184+'''
185+(Maybe [AttributeExprBase a]) [TupleExprBase a] |
186+ --- | Create and reference a relation from attributes and a tuple set.
187+ MakeStaticRelation Attributes RelationTupleSet |
188+ --- | Reference an existing relation in Haskell-space.
189+ ExistingRelation Relation |
190+ --MakeFunctionalRelation (creates a relation from a tuple-generating function, potentially infinite)
191+ --in Tutorial D, relational variables pick up the type of the first relation assigned to them
192+ --relational variables should also be able to be explicitly-typed like in Haskell
193+ --- | Reference a relation variable by its name.
194+ RelationVariable RelVarName a |
195+ --- | Create a projection over attribute names. (Note that the 'AttributeNames' structure allows for the names to be inverted.)
196+ Project AttributeNames (RelationalExprBase a) |
197+ --- | Create a union of two relational expressions. The expressions should have identical attributes.
198+ Union (RelationalExprBase a) (RelationalExprBase a) |
199+ --- | Create a join of two relational expressions. The join occurs on attributes which are identical. If the expressions have no overlapping attributes, the join becomes a cross-product of both tuple sets.
200+ Join (RelationalExprBase a) (RelationalExprBase a) |
201+ --- | Rename an attribute (first argument) to another (second argument).
202+ Rename AttributeName AttributeName (RelationalExprBase a) |
203+ --- | Return a relation containing all tuples of the first argument which do not appear in the second argument (minus).
204+ Difference (RelationalExprBase a) (RelationalExprBase a) |
205+ --- | Create a sub-relation composed of the first argument's attributes which will become an attribute of the result expression. The unreferenced attributes are not altered in the result but duplicate tuples in the projection of the expression minus the attribute names are compressed into one. For more information, <https://github.com/agentm/project-m36/blob/master/docs/introduction_to_the_relational_algebra.markdown#group read the relational algebra tutorial.>
206+ Group AttributeNames AttributeName (RelationalExprBase a) |
207+ --- | Create an expression to unwrap a sub-relation contained within at an attribute's name. Note that this is not always an inverse of a group operation.
208+ Ungroup AttributeName (RelationalExprBase a) |
209+ --- | Filter the tuples of the relational expression to only retain the tuples which evaluate against the restriction predicate to true.
210+ Restrict (RestrictionPredicateExprBase a) (RelationalExprBase a) |
211+ --- | Returns the true relation iff
212+ Equals (RelationalExprBase a) (RelationalExprBase a) |
213+ NotEquals (RelationalExprBase a) (RelationalExprBase a) |
214+ Extend (ExtendTupleExprBase a) (RelationalExprBase a)
215+'''
src/python/test.pynew file+9
@@ -0,0 +1,9 @@
1+import projectm36 as db
2+
3+conn = db.connect('ws', 'localhost', 8000, None, 'spam')
4+cursor = conn.cursor()
5+res = cursor.execute(db.RelationVariable('true'))
6+print res
7+#for row in res
8+# print row
9+