sonic.client
module
21 | Source code
25 |from enum import Enum
26 | import socket
27 | import re
28 | from queue import Queue
29 | import itertools
30 |
31 |
32 | class SonicServerError(Exception):
33 | """Generic Sonic Server exception"""
34 | pass
35 |
36 |
37 | class ChannelError(Exception):
38 | """Sonic Channel specific exception"""
39 | pass
40 |
41 |
42 | # Commands available on all channels + START that's available on the uninitialized channel
43 | COMMON_CMDS = [
44 | 'START',
45 | 'PING',
46 | 'HELP',
47 | 'QUIT'
48 | ]
49 |
50 | # Channels commands
51 | ALL_CMDS = {
52 | # FIXME: unintialized entry isn't needed anymore.
53 | 'UNINITIALIZED': [
54 | *COMMON_CMDS,
55 | ],
56 | 'ingest': [
57 | *COMMON_CMDS,
58 | # PUSH <collection> <bucket> <object> "<text>" [LANG(<locale>)]?
59 | 'PUSH',
60 | 'POP', # POP <collection> <bucket> <object> "<text>"
61 | 'COUNT', # COUNT <collection> [<bucket> [<object>]?]?
62 | 'FLUSHC', # FLUSHC <collection>
63 | 'FLUSHB', # FLUSHB <collection> <bucket>
64 | 'FLUSHO', # FLUSHO <collection> <bucket> <object>
65 | ],
66 | 'search': [
67 | *COMMON_CMDS,
68 | # QUERY <collection> <bucket> "<terms>" [LIMIT(<count>)]? [OFFSET(<count>)]? [LANG(<locale>)]?
69 | 'QUERY',
70 | 'SUGGEST', # SUGGEST <collection> <bucket> "<word>" [LIMIT(<count>)]?
71 | ],
72 | 'control': [
73 | *COMMON_CMDS,
74 | 'TRIGGER', # TRIGGER [<action>]?
75 | ]
76 | }
77 |
78 | # snippet from asonic code.
79 |
80 |
81 | def quote_text(text):
82 | """Quote text and normalize it in sonic protocol context.
83 |
84 | Arguments:
85 | text str -- text to quote/escape
86 |
87 | Returns:
88 | str -- quoted text
89 | """
90 | if text is None:
91 | return ""
92 | return '"' + text.replace('"', '\\"').replace('\r\n', ' ') + '"'
93 |
94 |
95 | def is_error(response):
96 | """Check if the response is Error or not in sonic context.
97 |
98 | Errors start with `ERR`
99 | Arguments:
100 | response {str} -- response string
101 |
102 | Returns:
103 | [bool] -- true if response is an error.
104 | """
105 | if response.startswith('ERR '):
106 | return True
107 | return False
108 |
109 |
110 | def raise_for_error(response):
111 | """Raise SonicServerError in case of error response.
112 |
113 | Arguments:
114 | response {str} -- message to check if it's error or not.
115 |
116 | Raises:
117 | SonicServerError --
118 |
119 | Returns:
120 | str -- the response message
121 | """
122 | if is_error(response):
123 | raise SonicServerError(response)
124 | return response
125 |
126 |
127 | def _parse_protocol_version(text):
128 | """Extracts protocol version from response message
129 |
130 | Arguments:
131 | text {str} -- text that may contain protocol version info (e.g STARTED search protocol(1) buffer(20000) )
132 |
133 | Raises:
134 | ValueError -- Raised when s doesn't have protocol information
135 |
136 | Returns:
137 | str -- protocol version.
138 | """
139 | matches = re.findall("protocol\((\w+)\)", text)
140 | if not matches:
141 | raise ValueError("{} doesn't contain protocol(NUMBER)".format(text))
142 | return matches[0]
143 |
144 |
145 | def _parse_buffer_size(text):
146 | """Extracts buffering from response message
147 |
148 | Arguments:
149 | text {str} -- text that may contain buffering info (e.g STARTED search protocol(1) buffer(20000) )
150 |
151 | Raises:
152 | ValueError -- Raised when s doesn't have buffering information
153 |
154 | Returns:
155 | str -- buffering.
156 | """
157 |
158 | matches = re.findall("buffer\((\w+)\)", text)
159 | if not matches:
160 | raise ValueError("{} doesn't contain buffer(NUMBER)".format(text))
161 | return matches[0]
162 |
163 |
164 | def _get_async_response_id(text):
165 | """Extract async response message id.
166 |
167 | Arguments:
168 | text {str} -- text that may contain async response id (e.g PENDING gn4RLF8M )
169 |
170 | Raises:
171 | ValueError -- [description]
172 |
173 | Returns:
174 | str -- async response id
175 | """
176 | text = text.strip()
177 | matches = re.findall("PENDING (\w+)", text)
178 | if not matches:
179 | raise ValueError("{} doesn't contain async response id".format(text))
180 | return matches[0]
181 |
182 |
183 | def pythonify_result(resp):
184 | if resp in ["OK", "PONG"]:
185 | return True
186 |
187 | if resp.startswith("EVENT QUERY") or resp.startswith("EVENT SUGGEST"):
188 | return resp.split()[3:]
189 |
190 | if resp.startswith("RESULT"):
191 | return int(resp.split()[-1])
192 | return resp
193 |
194 | # Channels names
195 | INGEST = 'ingest'
196 | SEARCH = 'search'
197 | CONTROL = 'control'
198 |
199 | class SonicConnection:
200 | def __init__(self, host: str, port: int, password: str, channel: str, keepalive: bool=True, timeout: int=60):
201 | """Base for sonic connections
202 |
203 | bufsize: indicates the buffer size to be used while communicating with the server.
204 | protocol: sonic protocol version
205 |
206 | Arguments:
207 | host {str} -- sonic server host
208 | port {int} -- sonic server port
209 | password {str} -- user password defined in `config.cfg` file on the server side.
210 | channel {str} -- channel name one of (ingest, search, control)
211 |
212 | Keyword Arguments:
213 | keepalive {bool} -- sets keepalive socket option (default: {True})
214 | timeout {int} -- sets socket timeout (default: {60})
215 | """
216 |
217 | self.host = host
218 | self.port = port
219 | self._password = password
220 | self.channel = channel
221 | self.raw = False
222 | self.address = self.host, self.port
223 | self.keepalive = keepalive
224 | self.timeout = timeout
225 | self.socket_connect_timeout = 10
226 | self.__socket = None
227 | self.__reader = None
228 | self.__writer = None
229 | self.bufize = None
230 | self.protocol = None
231 |
232 | def connect(self):
233 | """Connects to sonic server endpoint
234 |
235 | Returns:
236 | bool: True when connection happens and successfully switched to a channel.
237 | """
238 | resp = self._reader.readline()
239 | if 'CONNECTED' in resp:
240 | self.connected = True
241 |
242 | resp = self._execute_command("START", self.channel, self._password)
243 | self.protocol = _parse_protocol_version(resp)
244 | self.bufsize = _parse_buffer_size(resp)
245 |
246 | return self.ping()
247 |
248 | def ping(self):
249 | return self._execute_command("PING") == "PONG"
250 |
251 | def __create_connection(self, address):
252 | "Create a TCP socket connection"
253 | # we want to mimic what socket.create_connection does to support
254 | # ipv4/ipv6, but we want to set options prior to calling
255 | # socket.connect()
256 | # snippet taken from redis client code.
257 | err = None
258 | for res in socket.getaddrinfo(self.host, self.port, 0,
259 | socket.SOCK_STREAM):
260 | family, socktype, proto, canonname, socket_address = res
261 | sock = None
262 | try:
263 | sock = socket.socket(family, socktype, proto)
264 | # TCP_NODELAY
265 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
266 |
267 | # TCP_KEEPALIVE
268 | if self.keepalive:
269 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
270 |
271 | # set the socket_connect_timeout before we connect
272 | if self.socket_connect_timeout:
273 | sock.settimeout(self.timeout)
274 |
275 | # connect
276 | sock.connect(socket_address)
277 |
278 | # set the socket_timeout now that we're connected
279 | if self.timeout:
280 | sock.settimeout(self.timeout)
281 | return sock
282 |
283 | except socket.error as _:
284 | err = _
285 | if sock is not None:
286 | sock.close()
287 |
288 | if err is not None:
289 | raise err
290 | raise socket.error("socket.getaddrinfo returned an empty list")
291 |
292 | @property
293 | def _socket(self):
294 | if not self.__socket:
295 | # socket.create_connection(self.address)
296 | self.__socket = self.__create_connection(self.address)
297 |
298 | return self.__socket
299 |
300 | @property
301 | def _reader(self):
302 | if not self.__reader:
303 | self.__reader = self._socket.makefile('r')
304 | return self.__reader
305 |
306 | @property
307 | def _writer(self):
308 | if not self.__writer:
309 | self.__writer = self._socket.makefile('w')
310 | return self.__writer
311 |
312 | def close(self):
313 | """
314 | Closes the connection and its resources.
315 | """
316 | resources = (self.__reader, self.__writer, self.__socket)
317 | for rc in resources:
318 | if rc is not None:
319 | rc.close()
320 | self.__reader = None
321 | self.__writer = None
322 | self.__socket = None
323 |
324 | def _format_command(self, cmd, *args):
325 | """Format command according to sonic protocol
326 |
327 | Arguments:
328 | cmd {str} -- a valid sonic command
329 |
330 | Returns:
331 | str -- formatted command string to be sent on the wire.
332 | """
333 | cmd_str = cmd + " "
334 | cmd_str += " ".join(args)
335 | cmd_str += "\n" # specs says \n, asonic does \r\n
336 | return cmd_str
337 |
338 | def _execute_command(self, cmd, *args):
339 | """Formats and sends command with suitable arguments on the wire to sonic server
340 |
341 | Arguments:
342 | cmd {str} -- valid command
343 |
344 | Raises:
345 | ChannelError -- Raised for unsupported channel commands
346 |
347 | Returns:
348 | object|str -- depends on the `self.raw` mode
349 | if mode is raw: result is always a string
350 | else the result is converted to suitable python response (e.g boolean, int, list)
351 | """
352 | if cmd not in ALL_CMDS[self.channel]:
353 | raise ChannelError(
354 | "command {} isn't allowed in channel {}".format(cmd, self.channel))
355 |
356 | cmd_str = self._format_command(cmd, *args)
357 | self._writer.write(cmd_str)
358 | self._writer.flush()
359 | resp = self._get_response()
360 | return resp
361 |
362 | def _get_response(self):
363 | """Gets a response string from sonic server.
364 |
365 | Returns:
366 | object|str -- depends on the `self.raw` mode
367 | if mode is raw: result is always a string
368 | else the result is converted to suitable python response (e.g boolean, int, list)
369 | """
370 | resp = raise_for_error(self._reader.readline()).strip()
371 | if not self.raw:
372 | return pythonify_result(resp)
373 | return resp
374 |
375 |
376 | class ConnectionPool:
377 |
378 | def __init__(self, **create_kwargs):
379 | """ConnectionPool for Sonic connections.
380 |
381 | create_kwargs: SonicConnection create kwargs (passed to the connection constructor.)
382 | """
383 | self._inuse_connections = set()
384 | self._available_connections = Queue()
385 | self._create_kwargs = create_kwargs
386 |
387 | def get_connection(self) -> SonicConnection:
388 | """Gets a connection from the pool or creates one.
389 |
390 | Returns:
391 | SonicConnection -- Sonic connection.
392 | """
393 | conn = None
394 |
395 | if not self._available_connections.empty():
396 | conn = self._available_connections.get()
397 | else:
398 | # make connection and add to active connections
399 | conn = self._make_connection()
400 |
401 | self._inuse_connections.add(conn)
402 | return conn
403 |
404 | def release(self, conn:SonicConnection) -> None:
405 | """Releases connection `conn` to the pool
406 |
407 | Arguments:
408 | conn {SonicConnection} -- Connection to release back to the pool.
409 | """
410 | self._inuse_connections.remove(conn)
411 | if conn.ping():
412 | self._available_connections.put_nowait(conn)
413 |
414 | def _make_connection(self) -> SonicConnection:
415 | """Creates SonicConnection object and returns it.
416 |
417 | Returns:
418 | SonicConnection -- newly created sonic connection.
419 | """
420 | con = SonicConnection(**self._create_kwargs)
421 | con.connect()
422 | return con
423 |
424 | def close(self) -> None:
425 | """Closes the pool and all of the connections.
426 | """
427 | for con in itertools.chain(self._inuse_connections, self._available_connections):
428 | con.close()
429 |
430 | class SonicClient:
431 |
432 | def __init__(self, host: str, port: int, password: str, channel: str, pool: ConnectionPool=None):
433 | """Base for sonic clients
434 |
435 | bufsize: indicates the buffer size to be used while communicating with the server.
436 | protocol: sonic protocol version
437 |
438 | Arguments:
439 | host {str} -- sonic server host
440 | port {int} -- sonic server port
441 | password {str} -- user password defined in `config.cfg` file on the server side.
442 | channel {str} -- channel name one of (ingest, search, control)
443 |
444 | """
445 |
446 | self.host = host
447 | self.port = port
448 | self._password = password
449 | self.channel = channel
450 | self.bufsize = 0
451 | self.protocol = 1
452 | self.raw = False
453 | self.address = self.host, self.port
454 |
455 | if not pool:
456 | self.pool = ConnectionPool(
457 | host=host, port=port, password=password, channel=channel)
458 |
459 | def close(self):
460 | """close the connection and clean up open resources.
461 | """
462 | pass
463 |
464 | def __enter__(self):
465 | return self
466 |
467 | def __exit__(self, exc_type, exc_val, exc_tb):
468 | self.close()
469 |
470 | def get_active_connection(self) -> SonicConnection:
471 | """Gets a connection from the pool
472 |
473 | Returns:
474 | SonicConnection -- connection from the pool
475 | """
476 | active = self.pool.get_connection()
477 | active.raw = self.raw
478 | return active
479 |
480 | def _execute_command(self, cmd, *args):
481 | """Executes command `cmd` with arguments `args`
482 |
483 | Arguments:
484 | cmd {str} -- command to execute
485 | *args -- `cmd`'s arguments
486 | Returns:
487 | str|object -- result of execution
488 | """
489 | active = self.get_active_connection()
490 | try:
491 | res = active._execute_command(cmd, *args)
492 | finally:
493 | self.pool.release(active)
494 | return res
495 |
496 | def _execute_command_async(self, cmd, *args):
497 | """Executes async command `cmd` with arguments `args` and awaits its result.
498 |
499 | Arguments:
500 | cmd {str} -- command to execute
501 | *args -- `cmd`'s arguments
502 | Returns:
503 | str|object -- result of execution
504 | """
505 |
506 | active = self.get_active_connection()
507 | try:
508 | active._execute_command(cmd, *args)
509 | resp = active._get_response()
510 | finally:
511 | self.pool.release(active)
512 | return resp
513 |
514 | class CommonCommandsMixin:
515 | """Mixin of the commands used by all sonic channels."""
516 |
517 | def ping(self):
518 | """Send ping command to the server
519 |
520 | Returns:
521 | bool -- True if successfully reaching the server.
522 | """
523 | return self._execute_command("PING")
524 |
525 | def quit(self):
526 | """Quit the channel and closes the connection.
527 |
528 | """
529 | self._execute_command("QUIT")
530 | self.close()
531 |
532 | # TODO: check help.
533 | def help(self, *args):
534 | """Sends Help query."""
535 | return self._execute_command("HELP", *args)
536 |
537 |
538 | class IngestClient(SonicClient, CommonCommandsMixin):
539 | def __init__(self, host: str, port: str, password: str):
540 | super().__init__(host, port, password, INGEST)
541 |
542 | def push(self, collection: str, bucket: str, object: str, text: str, lang: str=None):
543 | """Push search data in the index
544 |
545 | Arguments:
546 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.)
547 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..)
548 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact)
549 | text {str} -- search text to be indexed can be a single word, or a longer text; within maximum length safety limits
550 |
551 | Keyword Arguments:
552 | lang {str} -- [description] (default: {None})
553 |
554 | Returns:
555 | bool -- True if search data are pushed in the index.
556 | """
557 |
558 | lang = "LANG({})".format(lang) if lang else ''
559 | text = quote_text(text)
560 | return self._execute_command("PUSH", collection, bucket, object, text, lang)
561 |
562 | def pop(self, collection: str, bucket: str, object: str, text: str):
563 | """Pop search data from the index
564 |
565 | Arguments:
566 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.)
567 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..)
568 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact)
569 | text {str} -- search text to be indexed can be a single word, or a longer text; within maximum length safety limits
570 |
571 | Returns:
572 | int
573 | """
574 | text = quote_text(text)
575 | return self._execute_command("POP", collection, bucket, object, text)
576 |
577 | def count(self, collection: str, bucket: str=None, object: str=None):
578 | """Count indexed search data
579 |
580 | Arguments:
581 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.)
582 |
583 | Keyword Arguments:
584 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..)
585 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact)
586 |
587 | Returns:
588 | int -- count of index search data.
589 | """
590 | bucket = bucket or ''
591 | object = object or ''
592 | return self._execute_command('COUNT', collection, bucket, object)
593 |
594 | def flush_collection(self, collection: str):
595 | """Flush all indexed data from a collection
596 |
597 | Arguments:
598 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.)
599 |
600 | Returns:
601 | int -- number of flushed data
602 | """
603 | return self._execute_command('FLUSHC', collection)
604 |
605 | def flush_bucket(self, collection: str, bucket: str):
606 | """Flush all indexed data from a bucket in a collection
607 |
608 | Arguments:
609 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.)
610 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..)
611 |
612 | Returns:
613 | int -- number of flushed data
614 | """
615 | return self._execute_command('FLUSHB', collection, bucket)
616 |
617 | def flush_object(self, collection: str, bucket: str, object: str):
618 | """Flush all indexed data from an object in a bucket in collection
619 |
620 | Arguments:
621 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.)
622 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..)
623 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact)
624 |
625 | Returns:
626 | int -- number of flushed data
627 | """
628 | return self._execute_command('FLUSHO', collection, bucket, object)
629 |
630 | def flush(self, collection: str, bucket: str=None, object: str=None):
631 | """Flush indexed data in a collection, bucket, or in an object.
632 |
633 | Arguments:
634 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.)
635 |
636 | Keyword Arguments:
637 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..)
638 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact)
639 |
640 | Returns:
641 | int -- number of flushed data
642 | """
643 | if not bucket and not object:
644 | return self.flush_collection(collection)
645 | elif bucket and not object:
646 | return self.flush_bucket(collection, bucket)
647 | elif object and bucket:
648 | return self.flush_object(collection, bucket, object)
649 |
650 |
651 | class SearchClient(SonicClient, CommonCommandsMixin):
652 | def __init__(self, host: str, port: int, password: str):
653 | """Create Sonic client that operates on the Search Channel
654 |
655 | Arguments:
656 | host {str} -- valid reachable host address
657 | port {int} -- port number
658 | password {str} -- password (defined in config.cfg file on the server side)
659 |
660 | """
661 | super().__init__(host, port, password, SEARCH)
662 |
663 | def query(self, collection: str, bucket: str, terms: str, limit: int=None, offset: int=None, lang: str=None):
664 | """Query the database
665 |
666 | Arguments:
667 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.)
668 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..)
669 | terms {str} -- text for search terms
670 |
671 | Keyword Arguments:
672 | limit {int} -- a positive integer number; set within allowed maximum & minimum limits
673 | offset {int} -- a positive integer number; set within allowed maximum & minimum limits
674 | lang {str} -- an ISO 639-3 locale code eg. eng for English (if set, the locale must be a valid ISO 639-3 code; if not set, the locale will be guessed from text).
675 |
676 | Returns:
677 | list -- list of objects ids.
678 | """
679 | limit = "LIMIT({})".format(limit) if limit else ''
680 | lang = "LANG({})".format(lang) if lang else ''
681 | offset = "OFFSET({})".format(offset) if offset else ''
682 |
683 | terms = quote_text(terms)
684 | return self._execute_command_async(
685 | 'QUERY', collection, bucket, terms, limit, offset, lang)
686 |
687 | def suggest(self, collection: str, bucket: str, word: str, limit: int=None):
688 | """auto-completes word.
689 |
690 | Arguments:
691 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.)
692 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..)
693 | word {str} -- word to autocomplete
694 |
695 |
696 | Keyword Arguments:
697 | limit {int} -- a positive integer number; set within allowed maximum & minimum limits (default: {None})
698 |
699 | Returns:
700 | list -- list of suggested words.
701 | """
702 | limit = "LIMIT({})".format(limit) if limit else ''
703 | word = quote_text(word)
704 | return self._execute_command(
705 | 'SUGGEST', collection, bucket, word, limit)
706 |
707 |
708 | class ControlClient(SonicClient, CommonCommandsMixin):
709 | def __init__(self, host: str, port: int, password: str):
710 | """Create Sonic client that operates on the Control Channel
711 |
712 | Arguments:
713 | host {str} -- valid reachable host address
714 | port {int} -- port number
715 | password {str} -- password (defined in config.cfg file on the server side)
716 |
717 | """
718 | super().__init__(host, port, password, CONTROL)
719 |
720 | def trigger(self, action: str=''):
721 | """Trigger an action
722 |
723 | Keyword Arguments:
724 | action {str} -- text for action
725 | """
726 | self._execute_command('TRIGGER', action)
727 |
728 |
729 | def test_ingest():
730 | with IngestClient("127.0.0.1", 1491, 'password') as ingestcl:
731 | print(ingestcl.ping())
732 | print(ingestcl.protocol)
733 | print(ingestcl.bufsize)
734 | ingestcl.push("wiki", "articles", "article-1",
735 | "for the love of god hell")
736 | ingestcl.push("wiki", "articles", "article-2",
737 | "for the love of satan heaven")
738 | ingestcl.push("wiki", "articles", "article-3",
739 | "for the love of lorde hello")
740 | ingestcl.push("wiki", "articles", "article-4",
741 | "for the god of loaf helmet")
742 |
743 |
744 | def test_search():
745 | with SearchClient("127.0.0.1", 1491, 'password') as querycl:
746 | print(querycl.ping())
747 | print(querycl.query("wiki", "articles", "for"))
748 | print(querycl.query("wiki", "articles", "love"))
749 |
750 |
751 | def test_control():
752 | with ControlClient("127.0.0.1", 1491, 'password') as controlcl:
753 | print(controlcl.ping())
754 | controlcl.trigger("consolidate")
755 |
756 |
757 | if __name__ == "__main__":
758 | test_ingest()
759 | test_search()
760 | test_control()
761 | Functions
769 |-
770 |
771 | def is_error(response) 772 |
773 | -
774 |
780 |Check if the response is Error or not in sonic context.
775 |Errors start with
776 |ERR
Arguments
777 |response {str} – response string
778 |Returns
779 |[bool] – true if response is an error.
781 |796 |Source code
782 |
795 |def is_error(response): 783 | """Check if the response is Error or not in sonic context. 784 | 785 | Errors start with `ERR` 786 | Arguments: 787 | response {str} -- response string 788 | 789 | Returns: 790 | [bool] -- true if response is an error. 791 | """ 792 | if response.startswith('ERR '): 793 | return True 794 | return False
797 | 798 | def pythonify_result(resp) 799 |
800 | -
801 |
802 | 803 |815 |
Source code
804 |
814 |def pythonify_result(resp): 805 | if resp in ["OK", "PONG"]: 806 | return True 807 | 808 | if resp.startswith("EVENT QUERY") or resp.startswith("EVENT SUGGEST"): 809 | return resp.split()[3:] 810 | 811 | if resp.startswith("RESULT"): 812 | return int(resp.split()[-1]) 813 | return resp
816 | 817 | def quote_text(text) 818 |
819 | -
820 |
825 |Quote text and normalize it in sonic protocol context.
821 |Arguments
822 |text str – text to quote/escape
823 |Returns
824 |str – quoted text
826 |840 |Source code
827 |
839 |def quote_text(text): 828 | """Quote text and normalize it in sonic protocol context. 829 | 830 | Arguments: 831 | text str -- text to quote/escape 832 | 833 | Returns: 834 | str -- quoted text 835 | """ 836 | if text is None: 837 | return "" 838 | return '"' + text.replace('"', '\\"').replace('\r\n', ' ') + '"'
841 | 842 | def raise_for_error(response) 843 |
844 | -
845 |
852 |Raise SonicServerError in case of error response.
846 |Arguments
847 |response {str} – message to check if it's error or not.
848 |Raises
849 |SonicServerError –
850 |Returns
851 |str – the response message
853 |870 |Source code
854 |
869 |def raise_for_error(response): 855 | """Raise SonicServerError in case of error response. 856 | 857 | Arguments: 858 | response {str} -- message to check if it's error or not. 859 | 860 | Raises: 861 | SonicServerError -- 862 | 863 | Returns: 864 | str -- the response message 865 | """ 866 | if is_error(response): 867 | raise SonicServerError(response) 868 | return response
871 | 872 | def test_control() 873 |
874 | -
875 |
876 | 877 |883 |
Source code
878 |
882 |def test_control(): 879 | with ControlClient("127.0.0.1", 1491, 'password') as controlcl: 880 | print(controlcl.ping()) 881 | controlcl.trigger("consolidate")
884 | 885 | def test_ingest() 886 |
887 | -
888 |
889 | 890 |905 |
Source code
891 |
904 |def test_ingest(): 892 | with IngestClient("127.0.0.1", 1491, 'password') as ingestcl: 893 | print(ingestcl.ping()) 894 | print(ingestcl.protocol) 895 | print(ingestcl.bufsize) 896 | ingestcl.push("wiki", "articles", "article-1", 897 | "for the love of god hell") 898 | ingestcl.push("wiki", "articles", "article-2", 899 | "for the love of satan heaven") 900 | ingestcl.push("wiki", "articles", "article-3", 901 | "for the love of lorde hello") 902 | ingestcl.push("wiki", "articles", "article-4", 903 | "for the god of loaf helmet")
906 | 907 | def test_search() 908 |
909 | -
910 |
911 | 912 |919 |
Source code
913 |
918 |def test_search(): 914 | with SearchClient("127.0.0.1", 1491, 'password') as querycl: 915 | print(querycl.ping()) 916 | print(querycl.query("wiki", "articles", "for")) 917 | print(querycl.query("wiki", "articles", "love"))
920 |
Classes
924 |-
925 |
926 | class ChannelError 927 | (ancestors: builtins.Exception, builtins.BaseException) 928 |
929 | -
930 |
931 |Sonic Channel specific exception
932 |937 |Source code
933 |
936 |class ChannelError(Exception): 934 | """Sonic Channel specific exception""" 935 | pass
938 | 939 | class CommonCommandsMixin 940 |
941 | -
942 |
943 |Mixin of the commands used by all sonic channels.
944 |968 |Source code
945 |
967 |class CommonCommandsMixin: 946 | """Mixin of the commands used by all sonic channels.""" 947 | 948 | def ping(self): 949 | """Send ping command to the server 950 | 951 | Returns: 952 | bool -- True if successfully reaching the server. 953 | """ 954 | return self._execute_command("PING") 955 | 956 | def quit(self): 957 | """Quit the channel and closes the connection. 958 | 959 | """ 960 | self._execute_command("QUIT") 961 | self.close() 962 | 963 | # TODO: check help. 964 | def help(self, *args): 965 | """Sends Help query.""" 966 | return self._execute_command("HELP", *args)
Subclasses
969 |-
970 |
- IngestClient 971 |
- SearchClient 972 |
- ControlClient 973 |
Methods
975 |-
976 |
977 | def help(self, *args) 978 |
979 | -
980 |
981 |Sends Help query.
982 |987 |Source code
983 |
986 |def help(self, *args): 984 | """Sends Help query.""" 985 | return self._execute_command("HELP", *args)
988 | 989 | def ping(self) 990 |
991 | -
992 |
995 |Send ping command to the server
993 |Returns
994 |bool – True if successfully reaching the server.
996 |1005 |Source code
997 |
1004 |def ping(self): 998 | """Send ping command to the server 999 | 1000 | Returns: 1001 | bool -- True if successfully reaching the server. 1002 | """ 1003 | return self._execute_command("PING")
1006 | 1007 | def quit(self) 1008 |
1009 | -
1010 |
1011 |Quit the channel and closes the connection.
1012 |1020 |Source code
1013 |
1019 |def quit(self): 1014 | """Quit the channel and closes the connection. 1015 | 1016 | """ 1017 | self._execute_command("QUIT") 1018 | self.close()
1021 |
1023 | 1024 | class ConnectionPool 1025 |
1026 | -
1027 |
1028 | 1029 |1084 |
Source code
1030 |
1083 |class ConnectionPool: 1031 | 1032 | def __init__(self, **create_kwargs): 1033 | """ConnectionPool for Sonic connections. 1034 | 1035 | create_kwargs: SonicConnection create kwargs (passed to the connection constructor.) 1036 | """ 1037 | self._inuse_connections = set() 1038 | self._available_connections = Queue() 1039 | self._create_kwargs = create_kwargs 1040 | 1041 | def get_connection(self) -> SonicConnection: 1042 | """Gets a connection from the pool or creates one. 1043 | 1044 | Returns: 1045 | SonicConnection -- Sonic connection. 1046 | """ 1047 | conn = None 1048 | 1049 | if not self._available_connections.empty(): 1050 | conn = self._available_connections.get() 1051 | else: 1052 | # make connection and add to active connections 1053 | conn = self._make_connection() 1054 | 1055 | self._inuse_connections.add(conn) 1056 | return conn 1057 | 1058 | def release(self, conn:SonicConnection) -> None: 1059 | """Releases connection `conn` to the pool 1060 | 1061 | Arguments: 1062 | conn {SonicConnection} -- Connection to release back to the pool. 1063 | """ 1064 | self._inuse_connections.remove(conn) 1065 | if conn.ping(): 1066 | self._available_connections.put_nowait(conn) 1067 | 1068 | def _make_connection(self) -> SonicConnection: 1069 | """Creates SonicConnection object and returns it. 1070 | 1071 | Returns: 1072 | SonicConnection -- newly created sonic connection. 1073 | """ 1074 | con = SonicConnection(**self._create_kwargs) 1075 | con.connect() 1076 | return con 1077 | 1078 | def close(self) -> None: 1079 | """Closes the pool and all of the connections. 1080 | """ 1081 | for con in itertools.chain(self._inuse_connections, self._available_connections): 1082 | con.close()
Methods
1085 |-
1086 |
1087 | def __init__(self, **create_kwargs) 1088 |
1089 | -
1090 |
1095 |ConnectionPool for Sonic connections.
1091 |-
1092 |
create_kwargs
:SonicConnection
create
kwargs
(passed
to
the
connection
constructor.
)
1093 | - 1094 |
1096 |1106 |Source code
1097 |
1105 |def __init__(self, **create_kwargs): 1098 | """ConnectionPool for Sonic connections. 1099 | 1100 | create_kwargs: SonicConnection create kwargs (passed to the connection constructor.) 1101 | """ 1102 | self._inuse_connections = set() 1103 | self._available_connections = Queue() 1104 | self._create_kwargs = create_kwargs
1107 | 1108 | def close(self) 1109 |
1110 | -
1111 |
1112 |Closes the pool and all of the connections.
1113 |1120 |Source code
1114 |
1119 |def close(self) -> None: 1115 | """Closes the pool and all of the connections. 1116 | """ 1117 | for con in itertools.chain(self._inuse_connections, self._available_connections): 1118 | con.close()
1121 | 1122 | def get_connection(self) 1123 |
1124 | -
1125 |
1128 |Gets a connection from the pool or creates one.
1126 |Returns
1127 |SonicConnection – Sonic connection.
1129 |1147 |Source code
1130 |
1146 |def get_connection(self) -> SonicConnection: 1131 | """Gets a connection from the pool or creates one. 1132 | 1133 | Returns: 1134 | SonicConnection -- Sonic connection. 1135 | """ 1136 | conn = None 1137 | 1138 | if not self._available_connections.empty(): 1139 | conn = self._available_connections.get() 1140 | else: 1141 | # make connection and add to active connections 1142 | conn = self._make_connection() 1143 | 1144 | self._inuse_connections.add(conn) 1145 | return conn
1148 | 1149 | def release(self, conn) 1150 |
1151 | -
1152 |
1155 |Releases connection
1153 |conn
to the poolArguments
1154 |conn {SonicConnection} – Connection to release back to the pool.
1156 |1167 |Source code
1157 |
1166 |def release(self, conn:SonicConnection) -> None: 1158 | """Releases connection `conn` to the pool 1159 | 1160 | Arguments: 1161 | conn {SonicConnection} -- Connection to release back to the pool. 1162 | """ 1163 | self._inuse_connections.remove(conn) 1164 | if conn.ping(): 1165 | self._available_connections.put_nowait(conn)
1168 |
1170 | 1171 | class ControlClient 1172 | (ancestors: SonicClient, CommonCommandsMixin) 1173 |
1174 | -
1175 |
1176 |Mixin of the commands used by all sonic channels.
1177 |1198 |Source code
1178 |
1197 |class ControlClient(SonicClient, CommonCommandsMixin): 1179 | def __init__(self, host: str, port: int, password: str): 1180 | """Create Sonic client that operates on the Control Channel 1181 | 1182 | Arguments: 1183 | host {str} -- valid reachable host address 1184 | port {int} -- port number 1185 | password {str} -- password (defined in config.cfg file on the server side) 1186 | 1187 | """ 1188 | super().__init__(host, port, password, CONTROL) 1189 | 1190 | def trigger(self, action: str=''): 1191 | """Trigger an action 1192 | 1193 | Keyword Arguments: 1194 | action {str} -- text for action 1195 | """ 1196 | self._execute_command('TRIGGER', action)
Methods
1199 |-
1200 |
1201 | def __init__(self, host, port, password) 1202 |
1203 | -
1204 |
1209 |Create Sonic client that operates on the Control Channel
1205 |Arguments
1206 |host {str} – valid reachable host address 1207 | port {int} – port number 1208 | password {str} – password (defined in config.cfg file on the server side)
1210 |1222 |Source code
1211 |
1221 |def __init__(self, host: str, port: int, password: str): 1212 | """Create Sonic client that operates on the Control Channel 1213 | 1214 | Arguments: 1215 | host {str} -- valid reachable host address 1216 | port {int} -- port number 1217 | password {str} -- password (defined in config.cfg file on the server side) 1218 | 1219 | """ 1220 | super().__init__(host, port, password, CONTROL)
1223 | 1224 | def trigger(self, action='') 1225 |
1226 | -
1227 |
1231 |Trigger an action
1228 |Keyword Arguments: 1229 | action {str} – 1230 | text for action
1232 |1241 |Source code
1233 |
1240 |def trigger(self, action: str=''): 1234 | """Trigger an action 1235 | 1236 | Keyword Arguments: 1237 | action {str} -- text for action 1238 | """ 1239 | self._execute_command('TRIGGER', action)
1242 |
Inherited members
1244 |-
1245 |
SonicClient
: 1246 |-
1247 |
close
1248 | get_active_connection
1249 |
1251 | CommonCommandsMixin
: 1252 | 1257 |
1258 |
1260 | 1261 | class IngestClient 1262 | (ancestors: SonicClient, CommonCommandsMixin) 1263 |
1264 | -
1265 |
1266 |Mixin of the commands used by all sonic channels.
1267 |1380 |Source code
1268 |
1379 |class IngestClient(SonicClient, CommonCommandsMixin): 1269 | def __init__(self, host: str, port: str, password: str): 1270 | super().__init__(host, port, password, INGEST) 1271 | 1272 | def push(self, collection: str, bucket: str, object: str, text: str, lang: str=None): 1273 | """Push search data in the index 1274 | 1275 | Arguments: 1276 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1277 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1278 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1279 | text {str} -- search text to be indexed can be a single word, or a longer text; within maximum length safety limits 1280 | 1281 | Keyword Arguments: 1282 | lang {str} -- [description] (default: {None}) 1283 | 1284 | Returns: 1285 | bool -- True if search data are pushed in the index. 1286 | """ 1287 | 1288 | lang = "LANG({})".format(lang) if lang else '' 1289 | text = quote_text(text) 1290 | return self._execute_command("PUSH", collection, bucket, object, text, lang) 1291 | 1292 | def pop(self, collection: str, bucket: str, object: str, text: str): 1293 | """Pop search data from the index 1294 | 1295 | Arguments: 1296 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1297 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1298 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1299 | text {str} -- search text to be indexed can be a single word, or a longer text; within maximum length safety limits 1300 | 1301 | Returns: 1302 | int 1303 | """ 1304 | text = quote_text(text) 1305 | return self._execute_command("POP", collection, bucket, object, text) 1306 | 1307 | def count(self, collection: str, bucket: str=None, object: str=None): 1308 | """Count indexed search data 1309 | 1310 | Arguments: 1311 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1312 | 1313 | Keyword Arguments: 1314 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1315 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1316 | 1317 | Returns: 1318 | int -- count of index search data. 1319 | """ 1320 | bucket = bucket or '' 1321 | object = object or '' 1322 | return self._execute_command('COUNT', collection, bucket, object) 1323 | 1324 | def flush_collection(self, collection: str): 1325 | """Flush all indexed data from a collection 1326 | 1327 | Arguments: 1328 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1329 | 1330 | Returns: 1331 | int -- number of flushed data 1332 | """ 1333 | return self._execute_command('FLUSHC', collection) 1334 | 1335 | def flush_bucket(self, collection: str, bucket: str): 1336 | """Flush all indexed data from a bucket in a collection 1337 | 1338 | Arguments: 1339 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1340 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1341 | 1342 | Returns: 1343 | int -- number of flushed data 1344 | """ 1345 | return self._execute_command('FLUSHB', collection, bucket) 1346 | 1347 | def flush_object(self, collection: str, bucket: str, object: str): 1348 | """Flush all indexed data from an object in a bucket in collection 1349 | 1350 | Arguments: 1351 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1352 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1353 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1354 | 1355 | Returns: 1356 | int -- number of flushed data 1357 | """ 1358 | return self._execute_command('FLUSHO', collection, bucket, object) 1359 | 1360 | def flush(self, collection: str, bucket: str=None, object: str=None): 1361 | """Flush indexed data in a collection, bucket, or in an object. 1362 | 1363 | Arguments: 1364 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1365 | 1366 | Keyword Arguments: 1367 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1368 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1369 | 1370 | Returns: 1371 | int -- number of flushed data 1372 | """ 1373 | if not bucket and not object: 1374 | return self.flush_collection(collection) 1375 | elif bucket and not object: 1376 | return self.flush_bucket(collection, bucket) 1377 | elif object and bucket: 1378 | return self.flush_object(collection, bucket, object)
Methods
1381 |-
1382 |
1383 | def count(self, collection, bucket=None, object=None) 1384 |
1385 | -
1386 |
1396 |Count indexed search data
1387 |Arguments
1388 |collection {str} – 1389 | index collection (ie. what you search in, eg. messages, products, etc.) 1390 | Keyword Arguments: 1391 | bucket {str} – index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1392 | object {str} – 1393 | object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact)
1394 |Returns
1395 |int – count of index search data.
1397 |1415 |Source code
1398 |
1414 |def count(self, collection: str, bucket: str=None, object: str=None): 1399 | """Count indexed search data 1400 | 1401 | Arguments: 1402 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1403 | 1404 | Keyword Arguments: 1405 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1406 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1407 | 1408 | Returns: 1409 | int -- count of index search data. 1410 | """ 1411 | bucket = bucket or '' 1412 | object = object or '' 1413 | return self._execute_command('COUNT', collection, bucket, object)
1416 | 1417 | def flush(self, collection, bucket=None, object=None) 1418 |
1419 | -
1420 |
1430 |Flush indexed data in a collection, bucket, or in an object.
1421 |Arguments
1422 |collection {str} – 1423 | index collection (ie. what you search in, eg. messages, products, etc.) 1424 | Keyword Arguments: 1425 | bucket {str} – index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1426 | object {str} – 1427 | object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact)
1428 |Returns
1429 |int – number of flushed data
1431 |1452 |Source code
1432 |
1451 |def flush(self, collection: str, bucket: str=None, object: str=None): 1433 | """Flush indexed data in a collection, bucket, or in an object. 1434 | 1435 | Arguments: 1436 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1437 | 1438 | Keyword Arguments: 1439 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1440 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1441 | 1442 | Returns: 1443 | int -- number of flushed data 1444 | """ 1445 | if not bucket and not object: 1446 | return self.flush_collection(collection) 1447 | elif bucket and not object: 1448 | return self.flush_bucket(collection, bucket) 1449 | elif object and bucket: 1450 | return self.flush_object(collection, bucket, object)
1453 | 1454 | def flush_bucket(self, collection, bucket) 1455 |
1456 | -
1457 |
1464 |Flush all indexed data from a bucket in a collection
1458 |Arguments
1459 |collection {str} – 1460 | index collection (ie. what you search in, eg. messages, products, etc.) 1461 | bucket {str} – index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..)
1462 |Returns
1463 |int – number of flushed data
1465 |1478 |Source code
1466 |
1477 |def flush_bucket(self, collection: str, bucket: str): 1467 | """Flush all indexed data from a bucket in a collection 1468 | 1469 | Arguments: 1470 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1471 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1472 | 1473 | Returns: 1474 | int -- number of flushed data 1475 | """ 1476 | return self._execute_command('FLUSHB', collection, bucket)
1479 | 1480 | def flush_collection(self, collection) 1481 |
1482 | -
1483 |
1489 |Flush all indexed data from a collection
1484 |Arguments
1485 |collection {str} – 1486 | index collection (ie. what you search in, eg. messages, products, etc.)
1487 |Returns
1488 |int – number of flushed data
1490 |1502 |Source code
1491 |
1501 |def flush_collection(self, collection: str): 1492 | """Flush all indexed data from a collection 1493 | 1494 | Arguments: 1495 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1496 | 1497 | Returns: 1498 | int -- number of flushed data 1499 | """ 1500 | return self._execute_command('FLUSHC', collection)
1503 | 1504 | def flush_object(self, collection, bucket, object) 1505 |
1506 | -
1507 |
1516 |Flush all indexed data from an object in a bucket in collection
1508 |Arguments
1509 |collection {str} – 1510 | index collection (ie. what you search in, eg. messages, products, etc.) 1511 | bucket {str} – index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1512 | object {str} – 1513 | object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact)
1514 |Returns
1515 |int – number of flushed data
1517 |1531 |Source code
1518 |
1530 |def flush_object(self, collection: str, bucket: str, object: str): 1519 | """Flush all indexed data from an object in a bucket in collection 1520 | 1521 | Arguments: 1522 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1523 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1524 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1525 | 1526 | Returns: 1527 | int -- number of flushed data 1528 | """ 1529 | return self._execute_command('FLUSHO', collection, bucket, object)
1532 | 1533 | def pop(self, collection, bucket, object, text) 1534 |
1535 | -
1536 |
1546 |Pop search data from the index
1537 |Arguments
1538 |collection {str} – 1539 | index collection (ie. what you search in, eg. messages, products, etc.) 1540 | bucket {str} – index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1541 | object {str} – 1542 | object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1543 | text {str} – search text to be indexed can be a single word, or a longer text; within maximum length safety limits
1544 |Returns
1545 |int
1547 |1563 |Source code
1548 |
1562 |def pop(self, collection: str, bucket: str, object: str, text: str): 1549 | """Pop search data from the index 1550 | 1551 | Arguments: 1552 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1553 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1554 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1555 | text {str} -- search text to be indexed can be a single word, or a longer text; within maximum length safety limits 1556 | 1557 | Returns: 1558 | int 1559 | """ 1560 | text = quote_text(text) 1561 | return self._execute_command("POP", collection, bucket, object, text)
1564 | 1565 | def push(self, collection, bucket, object, text, lang=None) 1566 |
1567 | -
1568 |
1580 |Push search data in the index
1569 |Arguments
1570 |collection {str} – 1571 | index collection (ie. what you search in, eg. messages, products, etc.) 1572 | bucket {str} – index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1573 | object {str} – 1574 | object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1575 | text {str} – search text to be indexed can be a single word, or a longer text; within maximum length safety limits 1576 | Keyword Arguments: 1577 | lang {str} – [description] (default: {None})
1578 |Returns
1579 |bool – True if search data are pushed in the index.
1581 |1602 |Source code
1582 |
1601 |def push(self, collection: str, bucket: str, object: str, text: str, lang: str=None): 1583 | """Push search data in the index 1584 | 1585 | Arguments: 1586 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1587 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1588 | object {str} -- object identifier that refers to an entity in an external database, where the searched object is stored (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact) 1589 | text {str} -- search text to be indexed can be a single word, or a longer text; within maximum length safety limits 1590 | 1591 | Keyword Arguments: 1592 | lang {str} -- [description] (default: {None}) 1593 | 1594 | Returns: 1595 | bool -- True if search data are pushed in the index. 1596 | """ 1597 | 1598 | lang = "LANG({})".format(lang) if lang else '' 1599 | text = quote_text(text) 1600 | return self._execute_command("PUSH", collection, bucket, object, text, lang)
1603 |
Inherited members
1605 |-
1606 |
SonicClient
: 1607 |-
1608 |
__init__
1609 | close
1610 | get_active_connection
1611 |
1613 | CommonCommandsMixin
: 1614 | 1619 |
1620 |
1622 | 1623 | class SearchClient 1624 | (ancestors: SonicClient, CommonCommandsMixin) 1625 |
1626 | -
1627 |
1628 |Mixin of the commands used by all sonic channels.
1629 |1686 |Source code
1630 |
1685 |class SearchClient(SonicClient, CommonCommandsMixin): 1631 | def __init__(self, host: str, port: int, password: str): 1632 | """Create Sonic client that operates on the Search Channel 1633 | 1634 | Arguments: 1635 | host {str} -- valid reachable host address 1636 | port {int} -- port number 1637 | password {str} -- password (defined in config.cfg file on the server side) 1638 | 1639 | """ 1640 | super().__init__(host, port, password, SEARCH) 1641 | 1642 | def query(self, collection: str, bucket: str, terms: str, limit: int=None, offset: int=None, lang: str=None): 1643 | """Query the database 1644 | 1645 | Arguments: 1646 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1647 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1648 | terms {str} -- text for search terms 1649 | 1650 | Keyword Arguments: 1651 | limit {int} -- a positive integer number; set within allowed maximum & minimum limits 1652 | offset {int} -- a positive integer number; set within allowed maximum & minimum limits 1653 | lang {str} -- an ISO 639-3 locale code eg. eng for English (if set, the locale must be a valid ISO 639-3 code; if not set, the locale will be guessed from text). 1654 | 1655 | Returns: 1656 | list -- list of objects ids. 1657 | """ 1658 | limit = "LIMIT({})".format(limit) if limit else '' 1659 | lang = "LANG({})".format(lang) if lang else '' 1660 | offset = "OFFSET({})".format(offset) if offset else '' 1661 | 1662 | terms = quote_text(terms) 1663 | return self._execute_command_async( 1664 | 'QUERY', collection, bucket, terms, limit, offset, lang) 1665 | 1666 | def suggest(self, collection: str, bucket: str, word: str, limit: int=None): 1667 | """auto-completes word. 1668 | 1669 | Arguments: 1670 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1671 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1672 | word {str} -- word to autocomplete 1673 | 1674 | 1675 | Keyword Arguments: 1676 | limit {int} -- a positive integer number; set within allowed maximum & minimum limits (default: {None}) 1677 | 1678 | Returns: 1679 | list -- list of suggested words. 1680 | """ 1681 | limit = "LIMIT({})".format(limit) if limit else '' 1682 | word = quote_text(word) 1683 | return self._execute_command( 1684 | 'SUGGEST', collection, bucket, word, limit)
Methods
1687 |-
1688 |
1689 | def __init__(self, host, port, password) 1690 |
1691 | -
1692 |
1697 |Create Sonic client that operates on the Search Channel
1693 |Arguments
1694 |host {str} – valid reachable host address 1695 | port {int} – port number 1696 | password {str} – password (defined in config.cfg file on the server side)
1698 |1710 |Source code
1699 |
1709 |def __init__(self, host: str, port: int, password: str): 1700 | """Create Sonic client that operates on the Search Channel 1701 | 1702 | Arguments: 1703 | host {str} -- valid reachable host address 1704 | port {int} -- port number 1705 | password {str} -- password (defined in config.cfg file on the server side) 1706 | 1707 | """ 1708 | super().__init__(host, port, password, SEARCH)
1711 | 1712 | def query(self, collection, bucket, terms, limit=None, offset=None, lang=None) 1713 |
1714 | -
1715 |
1727 |Query the database
1716 |Arguments
1717 |collection {str} – index collection (ie. what you search in, eg. messages, products, etc.) 1718 | bucket {str} – index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1719 | terms {str} – 1720 | text for search terms 1721 | Keyword Arguments: 1722 | limit {int} – a positive integer number; set within allowed maximum & minimum limits 1723 | offset {int} – a positive integer number; set within allowed maximum & minimum limits 1724 | lang {str} – an ISO 639-3 locale code eg. eng for English (if set, the locale must be a valid ISO 639-3 code; if not set, the locale will be guessed from text).
1725 |Returns
1726 |list – list of objects ids.
1728 |1753 |Source code
1729 |
1752 |def query(self, collection: str, bucket: str, terms: str, limit: int=None, offset: int=None, lang: str=None): 1730 | """Query the database 1731 | 1732 | Arguments: 1733 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1734 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1735 | terms {str} -- text for search terms 1736 | 1737 | Keyword Arguments: 1738 | limit {int} -- a positive integer number; set within allowed maximum & minimum limits 1739 | offset {int} -- a positive integer number; set within allowed maximum & minimum limits 1740 | lang {str} -- an ISO 639-3 locale code eg. eng for English (if set, the locale must be a valid ISO 639-3 code; if not set, the locale will be guessed from text). 1741 | 1742 | Returns: 1743 | list -- list of objects ids. 1744 | """ 1745 | limit = "LIMIT({})".format(limit) if limit else '' 1746 | lang = "LANG({})".format(lang) if lang else '' 1747 | offset = "OFFSET({})".format(offset) if offset else '' 1748 | 1749 | terms = quote_text(terms) 1750 | return self._execute_command_async( 1751 | 'QUERY', collection, bucket, terms, limit, offset, lang)
1754 | 1755 | def suggest(self, collection, bucket, word, limit=None) 1756 |
1757 | -
1758 |
1768 |auto-completes word.
1759 |Arguments
1760 |collection {str} – index collection (ie. what you search in, eg. messages, products, etc.) 1761 | bucket {str} – index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1762 | word {str} – 1763 | word to autocomplete 1764 | Keyword Arguments: 1765 | limit {int} – a positive integer number; set within allowed maximum & minimum limits (default: {None})
1766 |Returns
1767 |list – list of suggested words.
1769 |1790 |Source code
1770 |
1789 |def suggest(self, collection: str, bucket: str, word: str, limit: int=None): 1771 | """auto-completes word. 1772 | 1773 | Arguments: 1774 | collection {str} -- index collection (ie. what you search in, eg. messages, products, etc.) 1775 | bucket {str} -- index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, user-2, .., otherwise use a common bucket name eg. generic, default, common, ..) 1776 | word {str} -- word to autocomplete 1777 | 1778 | 1779 | Keyword Arguments: 1780 | limit {int} -- a positive integer number; set within allowed maximum & minimum limits (default: {None}) 1781 | 1782 | Returns: 1783 | list -- list of suggested words. 1784 | """ 1785 | limit = "LIMIT({})".format(limit) if limit else '' 1786 | word = quote_text(word) 1787 | return self._execute_command( 1788 | 'SUGGEST', collection, bucket, word, limit)
1791 |
Inherited members
1793 |-
1794 |
SonicClient
: 1795 |-
1796 |
close
1797 | get_active_connection
1798 |
1800 | CommonCommandsMixin
: 1801 | 1806 |
1807 |
1809 | 1810 | class SonicClient 1811 |
1812 | -
1813 |
1814 | 1815 |1900 |
Source code
1816 |
1899 |class SonicClient: 1817 | 1818 | def __init__(self, host: str, port: int, password: str, channel: str, pool: ConnectionPool=None): 1819 | """Base for sonic clients 1820 | 1821 | bufsize: indicates the buffer size to be used while communicating with the server. 1822 | protocol: sonic protocol version 1823 | 1824 | Arguments: 1825 | host {str} -- sonic server host 1826 | port {int} -- sonic server port 1827 | password {str} -- user password defined in `config.cfg` file on the server side. 1828 | channel {str} -- channel name one of (ingest, search, control) 1829 | 1830 | """ 1831 | 1832 | self.host = host 1833 | self.port = port 1834 | self._password = password 1835 | self.channel = channel 1836 | self.bufsize = 0 1837 | self.protocol = 1 1838 | self.raw = False 1839 | self.address = self.host, self.port 1840 | 1841 | if not pool: 1842 | self.pool = ConnectionPool( 1843 | host=host, port=port, password=password, channel=channel) 1844 | 1845 | def close(self): 1846 | """close the connection and clean up open resources. 1847 | """ 1848 | pass 1849 | 1850 | def __enter__(self): 1851 | return self 1852 | 1853 | def __exit__(self, exc_type, exc_val, exc_tb): 1854 | self.close() 1855 | 1856 | def get_active_connection(self) -> SonicConnection: 1857 | """Gets a connection from the pool 1858 | 1859 | Returns: 1860 | SonicConnection -- connection from the pool 1861 | """ 1862 | active = self.pool.get_connection() 1863 | active.raw = self.raw 1864 | return active 1865 | 1866 | def _execute_command(self, cmd, *args): 1867 | """Executes command `cmd` with arguments `args` 1868 | 1869 | Arguments: 1870 | cmd {str} -- command to execute 1871 | *args -- `cmd`'s arguments 1872 | Returns: 1873 | str|object -- result of execution 1874 | """ 1875 | active = self.get_active_connection() 1876 | try: 1877 | res = active._execute_command(cmd, *args) 1878 | finally: 1879 | self.pool.release(active) 1880 | return res 1881 | 1882 | def _execute_command_async(self, cmd, *args): 1883 | """Executes async command `cmd` with arguments `args` and awaits its result. 1884 | 1885 | Arguments: 1886 | cmd {str} -- command to execute 1887 | *args -- `cmd`'s arguments 1888 | Returns: 1889 | str|object -- result of execution 1890 | """ 1891 | 1892 | active = self.get_active_connection() 1893 | try: 1894 | active._execute_command(cmd, *args) 1895 | resp = active._get_response() 1896 | finally: 1897 | self.pool.release(active) 1898 | return resp
Subclasses
1901 |-
1902 |
- IngestClient 1903 |
- SearchClient 1904 |
- ControlClient 1905 |
Methods
1907 |-
1908 |
1909 | def __init__(self, host, port, password, channel, pool=None) 1910 |
1911 | -
1912 |
1923 |Base for sonic clients
1913 |-
1914 |
- bufsize: indicates the buffer size to be used while communicating with the server. 1915 |
protocol
:sonic
protocol
version
1916 | - 1917 |
Arguments
1919 |host {str} – sonic server host 1920 | port {int} – sonic server port 1921 | password {str} – user password defined in
config.cfg
file on the server side. 1922 | channel {str} – channel name one of (ingest, search, control)1924 |1952 |Source code
1925 |
1951 |def __init__(self, host: str, port: int, password: str, channel: str, pool: ConnectionPool=None): 1926 | """Base for sonic clients 1927 | 1928 | bufsize: indicates the buffer size to be used while communicating with the server. 1929 | protocol: sonic protocol version 1930 | 1931 | Arguments: 1932 | host {str} -- sonic server host 1933 | port {int} -- sonic server port 1934 | password {str} -- user password defined in `config.cfg` file on the server side. 1935 | channel {str} -- channel name one of (ingest, search, control) 1936 | 1937 | """ 1938 | 1939 | self.host = host 1940 | self.port = port 1941 | self._password = password 1942 | self.channel = channel 1943 | self.bufsize = 0 1944 | self.protocol = 1 1945 | self.raw = False 1946 | self.address = self.host, self.port 1947 | 1948 | if not pool: 1949 | self.pool = ConnectionPool( 1950 | host=host, port=port, password=password, channel=channel)
1953 | 1954 | def close(self) 1955 |
1956 | -
1957 |
1958 |close the connection and clean up open resources.
1959 |1965 |Source code
1960 |
1964 |def close(self): 1961 | """close the connection and clean up open resources. 1962 | """ 1963 | pass
1966 | 1967 | def get_active_connection(self) 1968 |
1969 | -
1970 |
1973 |Gets a connection from the pool
1971 |Returns
1972 |SonicConnection – connection from the pool
1974 |1985 |Source code
1975 |
1984 |def get_active_connection(self) -> SonicConnection: 1976 | """Gets a connection from the pool 1977 | 1978 | Returns: 1979 | SonicConnection -- connection from the pool 1980 | """ 1981 | active = self.pool.get_connection() 1982 | active.raw = self.raw 1983 | return active
1986 |
1988 | 1989 | class SonicConnection 1990 |
1991 | -
1992 |
1993 | 1994 |2171 |
Source code
1995 |
2170 |class SonicConnection: 1996 | def __init__(self, host: str, port: int, password: str, channel: str, keepalive: bool=True, timeout: int=60): 1997 | """Base for sonic connections 1998 | 1999 | bufsize: indicates the buffer size to be used while communicating with the server. 2000 | protocol: sonic protocol version 2001 | 2002 | Arguments: 2003 | host {str} -- sonic server host 2004 | port {int} -- sonic server port 2005 | password {str} -- user password defined in `config.cfg` file on the server side. 2006 | channel {str} -- channel name one of (ingest, search, control) 2007 | 2008 | Keyword Arguments: 2009 | keepalive {bool} -- sets keepalive socket option (default: {True}) 2010 | timeout {int} -- sets socket timeout (default: {60}) 2011 | """ 2012 | 2013 | self.host = host 2014 | self.port = port 2015 | self._password = password 2016 | self.channel = channel 2017 | self.raw = False 2018 | self.address = self.host, self.port 2019 | self.keepalive = keepalive 2020 | self.timeout = timeout 2021 | self.socket_connect_timeout = 10 2022 | self.__socket = None 2023 | self.__reader = None 2024 | self.__writer = None 2025 | self.bufize = None 2026 | self.protocol = None 2027 | 2028 | def connect(self): 2029 | """Connects to sonic server endpoint 2030 | 2031 | Returns: 2032 | bool: True when connection happens and successfully switched to a channel. 2033 | """ 2034 | resp = self._reader.readline() 2035 | if 'CONNECTED' in resp: 2036 | self.connected = True 2037 | 2038 | resp = self._execute_command("START", self.channel, self._password) 2039 | self.protocol = _parse_protocol_version(resp) 2040 | self.bufsize = _parse_buffer_size(resp) 2041 | 2042 | return self.ping() 2043 | 2044 | def ping(self): 2045 | return self._execute_command("PING") == "PONG" 2046 | 2047 | def __create_connection(self, address): 2048 | "Create a TCP socket connection" 2049 | # we want to mimic what socket.create_connection does to support 2050 | # ipv4/ipv6, but we want to set options prior to calling 2051 | # socket.connect() 2052 | # snippet taken from redis client code. 2053 | err = None 2054 | for res in socket.getaddrinfo(self.host, self.port, 0, 2055 | socket.SOCK_STREAM): 2056 | family, socktype, proto, canonname, socket_address = res 2057 | sock = None 2058 | try: 2059 | sock = socket.socket(family, socktype, proto) 2060 | # TCP_NODELAY 2061 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 2062 | 2063 | # TCP_KEEPALIVE 2064 | if self.keepalive: 2065 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) 2066 | 2067 | # set the socket_connect_timeout before we connect 2068 | if self.socket_connect_timeout: 2069 | sock.settimeout(self.timeout) 2070 | 2071 | # connect 2072 | sock.connect(socket_address) 2073 | 2074 | # set the socket_timeout now that we're connected 2075 | if self.timeout: 2076 | sock.settimeout(self.timeout) 2077 | return sock 2078 | 2079 | except socket.error as _: 2080 | err = _ 2081 | if sock is not None: 2082 | sock.close() 2083 | 2084 | if err is not None: 2085 | raise err 2086 | raise socket.error("socket.getaddrinfo returned an empty list") 2087 | 2088 | @property 2089 | def _socket(self): 2090 | if not self.__socket: 2091 | # socket.create_connection(self.address) 2092 | self.__socket = self.__create_connection(self.address) 2093 | 2094 | return self.__socket 2095 | 2096 | @property 2097 | def _reader(self): 2098 | if not self.__reader: 2099 | self.__reader = self._socket.makefile('r') 2100 | return self.__reader 2101 | 2102 | @property 2103 | def _writer(self): 2104 | if not self.__writer: 2105 | self.__writer = self._socket.makefile('w') 2106 | return self.__writer 2107 | 2108 | def close(self): 2109 | """ 2110 | Closes the connection and its resources. 2111 | """ 2112 | resources = (self.__reader, self.__writer, self.__socket) 2113 | for rc in resources: 2114 | if rc is not None: 2115 | rc.close() 2116 | self.__reader = None 2117 | self.__writer = None 2118 | self.__socket = None 2119 | 2120 | def _format_command(self, cmd, *args): 2121 | """Format command according to sonic protocol 2122 | 2123 | Arguments: 2124 | cmd {str} -- a valid sonic command 2125 | 2126 | Returns: 2127 | str -- formatted command string to be sent on the wire. 2128 | """ 2129 | cmd_str = cmd + " " 2130 | cmd_str += " ".join(args) 2131 | cmd_str += "\n" # specs says \n, asonic does \r\n 2132 | return cmd_str 2133 | 2134 | def _execute_command(self, cmd, *args): 2135 | """Formats and sends command with suitable arguments on the wire to sonic server 2136 | 2137 | Arguments: 2138 | cmd {str} -- valid command 2139 | 2140 | Raises: 2141 | ChannelError -- Raised for unsupported channel commands 2142 | 2143 | Returns: 2144 | object|str -- depends on the `self.raw` mode 2145 | if mode is raw: result is always a string 2146 | else the result is converted to suitable python response (e.g boolean, int, list) 2147 | """ 2148 | if cmd not in ALL_CMDS[self.channel]: 2149 | raise ChannelError( 2150 | "command {} isn't allowed in channel {}".format(cmd, self.channel)) 2151 | 2152 | cmd_str = self._format_command(cmd, *args) 2153 | self._writer.write(cmd_str) 2154 | self._writer.flush() 2155 | resp = self._get_response() 2156 | return resp 2157 | 2158 | def _get_response(self): 2159 | """Gets a response string from sonic server. 2160 | 2161 | Returns: 2162 | object|str -- depends on the `self.raw` mode 2163 | if mode is raw: result is always a string 2164 | else the result is converted to suitable python response (e.g boolean, int, list) 2165 | """ 2166 | resp = raise_for_error(self._reader.readline()).strip() 2167 | if not self.raw: 2168 | return pythonify_result(resp) 2169 | return resp
Methods
2172 |-
2173 |
2174 | def __init__(self, host, port, password, channel, keepalive=True, timeout=60) 2175 |
2176 | -
2177 |
2192 |Base for sonic connections
2178 |-
2179 |
- bufsize: indicates the buffer size to be used while communicating with the server. 2180 |
protocol
:sonic
protocol
version
2181 | - 2182 |
Arguments
2184 |host {str} – sonic server host 2185 | port {int} – sonic server port 2186 | password {str} – user password defined in
config.cfg
file on the server side. 2187 | channel {str} – channel name one of (ingest, search, control) 2188 | Keyword Arguments: 2189 | keepalive {bool} – sets keepalive socket option (default: {True}) 2190 | timeout {int} – sets socket timeout 2191 | (default: {60})2193 |2226 |Source code
2194 |
2225 |def __init__(self, host: str, port: int, password: str, channel: str, keepalive: bool=True, timeout: int=60): 2195 | """Base for sonic connections 2196 | 2197 | bufsize: indicates the buffer size to be used while communicating with the server. 2198 | protocol: sonic protocol version 2199 | 2200 | Arguments: 2201 | host {str} -- sonic server host 2202 | port {int} -- sonic server port 2203 | password {str} -- user password defined in `config.cfg` file on the server side. 2204 | channel {str} -- channel name one of (ingest, search, control) 2205 | 2206 | Keyword Arguments: 2207 | keepalive {bool} -- sets keepalive socket option (default: {True}) 2208 | timeout {int} -- sets socket timeout (default: {60}) 2209 | """ 2210 | 2211 | self.host = host 2212 | self.port = port 2213 | self._password = password 2214 | self.channel = channel 2215 | self.raw = False 2216 | self.address = self.host, self.port 2217 | self.keepalive = keepalive 2218 | self.timeout = timeout 2219 | self.socket_connect_timeout = 10 2220 | self.__socket = None 2221 | self.__reader = None 2222 | self.__writer = None 2223 | self.bufize = None 2224 | self.protocol = None
2227 | 2228 | def close(self) 2229 |
2230 | -
2231 |
2232 |Closes the connection and its resources.
2233 |2246 |Source code
2234 |
2245 |def close(self): 2235 | """ 2236 | Closes the connection and its resources. 2237 | """ 2238 | resources = (self.__reader, self.__writer, self.__socket) 2239 | for rc in resources: 2240 | if rc is not None: 2241 | rc.close() 2242 | self.__reader = None 2243 | self.__writer = None 2244 | self.__socket = None
2247 | 2248 | def connect(self) 2249 |
2250 | -
2251 |
2257 |Connects to sonic server endpoint
2252 |Returns
2253 |-
2254 |
bool
2255 | - True when connection happens and successfully switched to a channel. 2256 |
2258 |2275 |Source code
2259 |
2274 |def connect(self): 2260 | """Connects to sonic server endpoint 2261 | 2262 | Returns: 2263 | bool: True when connection happens and successfully switched to a channel. 2264 | """ 2265 | resp = self._reader.readline() 2266 | if 'CONNECTED' in resp: 2267 | self.connected = True 2268 | 2269 | resp = self._execute_command("START", self.channel, self._password) 2270 | self.protocol = _parse_protocol_version(resp) 2271 | self.bufsize = _parse_buffer_size(resp) 2272 | 2273 | return self.ping()
2276 | 2277 | def ping(self) 2278 |
2279 | -
2280 |
2281 | 2282 |2286 |
Source code
2283 |
2285 |def ping(self): 2284 | return self._execute_command("PING") == "PONG"
2287 |
2289 | 2290 | class SonicServerError 2291 | (ancestors: builtins.Exception, builtins.BaseException) 2292 |
2293 | -
2294 |
2295 |Generic Sonic Server exception
2296 |2301 |Source code
2297 |
2300 |class SonicServerError(Exception): 2298 | """Generic Sonic Server exception""" 2299 | pass
2302 |