Example #1
0
 def __init__(self, host, port, username, password, logfile, filename, ssl,
              read_only, timeout):
     self.logfile = logfile
     self.filename = filename
     self.read_only = read_only
     self.neo4j = Neo4j(host, port, username, password, ssl, timeout)
     self.cypher = Cypher()
Example #2
0
    def __init__(self, labels, relationship_types, properties):
        super(CypherCompleter, self).__init__()

        self.labels = labels
        self.relationship_types = relationship_types
        self.properties = properties
        self.cypher = Cypher()
Example #3
0
class CypherCompleter(Completer):
    def __init__(self, labels, relationship_types, properties):
        super(CypherCompleter, self).__init__()

        self.labels = labels
        self.relationship_types = relationship_types
        self.properties = properties
        self.cypher = Cypher()

    def get_completions(self, document, complete_event):
        text_before_cursor = document.text_before_cursor

        if currently_inside_quotes(text_before_cursor):
            return
        elif typing_label(text_before_cursor):
            choices = self.labels
            lookup = everything_after_last(":", text_before_cursor)
        elif typing_relationship(text_before_cursor):
            choices = self.relationship_types
            lookup = everything_after_last(":", text_before_cursor)
        elif typing_property(text_before_cursor):
            period_loc = text_before_cursor.rfind(".")
            variable_start_loc = max(
                text_before_cursor.rfind("(", 0, period_loc),
                text_before_cursor.rfind(" ", 0, period_loc))
            variable = text_before_cursor[variable_start_loc + 1:period_loc]

            if variable.isalnum() and any(c.isalpha() for c in variable):
                choices = self.properties
                lookup = everything_after_last(".", text_before_cursor)
            else:
                return
        elif text_before_cursor and text_before_cursor[-1].isalpha():
            last_cypher_word = self.most_recent_cypher_word(text_before_cursor)
            choices = self.cypher.most_probable_next_keyword(last_cypher_word)
            lookup = last_alphabetic_chunk(text_before_cursor)
        else:
            return

        completions = find_matches(lookup, choices)

        for completion in completions:
            yield Completion(completion, -len(lookup))

    def most_recent_cypher_word(self, chars):
        text = " " + chars
        keyword_indices = [(word, text.rfind(" " + word + " "))
                           for word in self.cypher.KEYWORDS]
        function_indices = [(word, text.rfind(" " + word + "("))
                            for word in self.cypher.FUNCTIONS]

        indices = keyword_indices + function_indices

        # If no keywords were found, we want to be in the "" state in the Markov model.
        if not any([i[1] > -1 for i in indices]):
            return ""

        most_recent = max(indices, key=lambda i: i[1])[0]
        return most_recent
Example #4
0
    def __init__(self, labels, relationship_types, properties):
        super(CypherCompleter, self).__init__()

        self.labels = labels
        self.relationship_types = relationship_types
        self.properties = properties

        self.cypher = Cypher()
Example #5
0
from cycli.cypher import Cypher
from misc.graphgist import get_all_queries
import re

cypher = Cypher()

queries = get_all_queries()

# Each Cypher word is a state in the Markov model. We're also adding a "" state; this means there are no previous words
# (we're at the beginning of the query).
cypher_words = [""] + cypher.words()

# Store the model in a dictionary of dictionaries.
markov = {i: {j:0.0 for j in cypher_words} for i in cypher_words}

for query in queries:
    # Find the indices of Cypher functions and keywords separately. This results in a list of tuples for each word and its
    # index, e.g. [('MATCH', 0), ('WHERE', 13), ('RETURN', 29)].

    function_indices = []

    for word in cypher.FUNCTIONS:
        idx = [m.start() for m in re.finditer(" " + word + "\s+\(", query)]

        for i in idx:
            function_indices.append((word, i))

    # Find the keywords. Make sure they're surrounded by spaces so that we don't grab words within words.
    keyword_indices = []

    for word in cypher.KEYWORDS:
Example #6
0
def cypher():
    from cycli.cypher import Cypher
    return Cypher()
Example #7
0
class Cycli:
  def __init__(self, host, port, username, password, logfile, filename, ssl, read_only, timeout):
    self.logfile = logfile
    self.filename = filename
    self.read_only = read_only
    self.neo4j = Neo4j(host, port, username, password, ssl, timeout)
    self.cypher = Cypher()

  def write_to_logfile(self, query, response):
    headers = response["headers"]
    rows = response["rows"]
    duration = response["duration"]
    error = response["error"]

    self.logfile.write("> {}\n".format(query))
    self.logfile.write("{}\n".format(pretty_table(headers, rows)))

    if error is False:
      self.logfile.write("{} ms\n\n".format(duration))

  @staticmethod
  def write_to_csvfile(headers, rows):
    filename = "cycli {}.csv".format(datetime.now().strftime("%Y-%m-%d at %I.%M.%S %p"))

    with open(filename, "wb") as csvfile:
      csvwriter = csv.writer(csvfile, quotechar=str('"'), quoting=csv.QUOTE_NONNUMERIC, delimiter=str(","))
      csvwriter.writerow(headers)

      for row in rows:
        csvwriter.writerow(row)

    csvfile.close()

    def run(self):
      labels = self.neo4j.get_labels()
      relationship_types = self.neo4j.get_relationship_types()
      properties = self.neo4j.get_property_keys()

      if self.filename:
        with open(self.filename, "rb") as f:
          queries = split_queries_on_semicolons(f.read())

          for query in queries:
            print("> " + query)
            self.handle_query(query)
            print()

          return

      click.secho(" ______     __  __     ______     __         __    ", fg="red")
      click.secho("/\  ___\   /\ \_\ \   /\  ___\   /\ \       /\ \   ", fg="yellow")
      click.secho("\ \ \____  \ \____ \  \ \ \____  \ \ \____  \ \ \  ", fg="green")
      click.secho(" \ \_____\  \/\_____\  \ \_____\  \ \_____\  \ \_\ ", fg="blue")
      click.secho("  \/_____/   \/_____/   \/_____/   \/_____/   \/_/ ", fg="magenta")

      print("Cycli version: {}".format(__version__))
      print("Neo4j version: {}".format(".".join(map(str, self.neo4j.neo4j_version))))
      print("Bug reports: https://github.com/nicolewhite/cycli/issues\n")

      completer = CypherCompleter(labels, relationship_types, properties)

      layout = create_prompt_layout(
        lexer=CypherLexer,
        get_prompt_tokens=get_tokens,
        reserve_space_for_menu=8,
      )

      buff = CypherBuffer(
        accept_action=AcceptAction.RETURN_DOCUMENT,
        history=FileHistory(filename=os.path.expanduser('~/.cycli_history')),
        completer=completer,
        complete_while_typing=True,
      )

      application = Application(
        style=PygmentsStyle(CypherStyle),
        buffer=buff,
        layout=layout,
        on_exit=AbortAction.RAISE_EXCEPTION,
        key_bindings_registry=CypherBinder.registry
      )

      cli = CommandLineInterface(application=application, eventloop=create_eventloop())

      try:
        while True:
          document = cli.run()
          query = document.text
          self.handle_query(query)
      except UserWantsOut:
        print("Goodbye!")
      except Exception as e:
        print(e)

  def handle_query(self, query):
    run_n = re.match('run-([0-9]+) (.*)', query, re.DOTALL)
    save_csv = query.startswith("save-csv ")

    if self.cypher.is_a_write_query(query) and self.read_only:
      print("Query aborted. You are in read-only mode.")
    elif query in ["quit", "exit"]:
      raise UserWantsOut
    elif query == "help":
      print_help()
    elif query == "refresh":
      self.neo4j.refresh()
    elif query == "schema":
      self.neo4j.print_schema()
    elif query == "schema-indexes":
      self.neo4j.print_indexes()
    elif query == "schema-constraints":
      self.neo4j.print_constraints()
    elif query == "schema-labels":
      self.neo4j.print_labels()
    elif query == "schema-rels":
      self.neo4j.print_relationship_types()
    elif query.startswith("env"):
      if query == "env":
        for key, value in self.neo4j.parameters.items():
          print("{0}={1}".format(key, value))
      else:
        key = query[3:]
        key = key.strip("'\"[]")
        value = self.neo4j.parameters.get(key)

        if value is not None:
          print(value)

    elif query.startswith("export "):
      if "=" not in query:
        print("Set parameters with export key=value.")
      else:
        params = query.replace("export ", "").strip()
        key, value = params.split("=", 1)
        key = key.strip()
        value = value.strip()

        try:
          value = eval(value)
          self.neo4j.update_parameters(key, value)
        except Exception as e:
          print(e)

    else:
      count = int(run_n.group(1)) if run_n else 1
      query = run_n.group(2) if run_n else query
      query = query[len("save-csv "):] if save_csv else query

      if count <= 0 or not query:
        print("Check your syntax. cycli expects run-{n} {query} where {n} is an integer > 0 and {query} is a Cypher query.")
        return

      error = False
      total_duration = 0
      index = 0

      while index < count:
        response = self.neo4j.cypher(query)

        headers = response["headers"]
        rows = response["rows"]
        duration = response["duration"]
        error = response["error"]
        profile = response.get("profile")

        if error is False:
          print(pretty_table(headers, rows))

          ms = "Run {}: {} ms\n".format(index + 1, duration) if run_n else "{} ms".format(duration)
          print(ms)

          if profile:
            self.neo4j.print_profile(profile)
          if save_csv:
            self.write_to_csvfile(headers, rows)
        else:
          print(error)

        if self.logfile:
          self.write_to_logfile(query, response)

        total_duration += duration
        index += 1

      if run_n and error is False:
        print("Total duration: {} ms".format(total_duration))
Example #8
0
class CypherCompleter(Completer):

    def __init__(self, labels, relationship_types, properties):
        super(CypherCompleter, self).__init__()

        self.labels = labels
        self.relationship_types = relationship_types
        self.properties = properties

        self.cypher = Cypher()

    def get_completions(self, document, complete_event):
        text_before_cursor = document.text_before_cursor
        choices = []
        lookup = ""

        if self.exists_unclosed_char("'", text_before_cursor) or self.exists_unclosed_char('"', text_before_cursor):
            return
        elif self.typing_label(text_before_cursor):
            choices = self.labels
            lookup = self.everything_after_last(":", text_before_cursor)
        elif self.typing_relationship(text_before_cursor):
            choices = self.relationship_types
            lookup = self.everything_after_last(":", text_before_cursor)
        elif self.typing_property(text_before_cursor):
            period_loc = text_before_cursor.rfind(".")
            variable_start_loc = max(text_before_cursor.rfind("(", 0, period_loc), text_before_cursor.rfind(" ", 0, period_loc))
            variable = text_before_cursor[variable_start_loc + 1:period_loc]

            if variable.isalnum() and any(c.isalpha() for c in variable):
                choices = self.properties
                lookup = self.everything_after_last(".", text_before_cursor)
            else:
                return
        elif text_before_cursor:
            if text_before_cursor[-1].isalpha():
                last_cypher_word = self.most_recent_cypher_word(text_before_cursor)
                choices = self.cypher.most_probable_next_keyword(last_cypher_word)
                lookup = self.last_alphabetic_chunk(text_before_cursor)
        else:
            return

        completions = self.find_matches(lookup, choices)

        for completion in completions:
            yield Completion(completion, -len(lookup))

    @staticmethod
    def find_matches(word, choices):
        word = word.lower()
        lower_choices = [x.lower() for x in choices]

        completions = []

        for i, choice in enumerate(lower_choices):
            if choice.startswith(word):
                completions.append(choices[i])

        return completions

    def most_recent_cypher_word(self, chars):
        text = " " + chars
        keyword_indices = [(word, text.rfind(" " + word + " ")) for word in self.cypher.KEYWORDS]
        function_indices = [(word, text.rfind(" " + word + "(")) for word in self.cypher.FUNCTIONS]

        indices = keyword_indices + function_indices

        # If no keywords were found, we want to be in the "" state in the Markov model.
        if not any([i[1] > -1 for i in indices]):
            return ""

        most_recent = max(indices, key=lambda i:i[1])[0]
        return most_recent

    @staticmethod
    def last_alphabetic_chunk(chars):
        chars = list(chars)
        chars.reverse()

        keep = []

        for c in chars:
            if c.isalpha():
                keep.append(c)
            else:
                break

        keep.reverse()
        return "".join(keep)

    @staticmethod
    def everything_after_last(char, chars):
        chars = chars.replace("`", "")
        loc = chars.rfind(char)
        return chars[loc + 1:]

    @staticmethod
    def exists_unclosed_char(char, chars):
        return chars.count(char) % 2 != 0

    @staticmethod
    def exists_unclosed_pattern(open_char, close_char, chars):
        return chars.count(open_char) != chars.count(close_char)

    def colon_inside_unclosed_pattern(self, open_char, close_char, chars):
        return self.exists_unclosed_pattern(open_char, close_char, chars) and chars.rfind(":") > chars.rfind(open_char)

    def typing_relationship(self, chars):
        return self.colon_inside_unclosed_pattern("[", "]", chars) and chars.rfind("[") > chars.rfind("(")

    def typing_label(self, chars):
        return self.colon_inside_unclosed_pattern("(", ")", chars) and chars.rfind("(") > chars.rfind("[")

    def typing_property(self, chars):
        chars = list(chars)
        chars.reverse()

        skip = ["_", "`"]

        # Skip spaces if we're inside backticks.
        if self.exists_unclosed_char("`", chars):
            skip.append(" ")

        for c in chars:
            if not c.isalnum() and c not in skip:
                return c == "."

        return False
Example #9
0
class CypherCompleter(Completer):
    def __init__(self, labels, relationship_types, properties):
        super(CypherCompleter, self).__init__()

        self.labels = labels
        self.relationship_types = relationship_types
        self.properties = properties

        self.cypher = Cypher()

    def get_completions(self, document, complete_event):
        text_before_cursor = document.text_before_cursor
        choices = []
        lookup = ""

        if self.exists_unclosed_char(
                "'", text_before_cursor) or self.exists_unclosed_char(
                    '"', text_before_cursor):
            return
        elif self.typing_label(text_before_cursor):
            choices = self.labels
            lookup = self.everything_after_last(":", text_before_cursor)
        elif self.typing_relationship(text_before_cursor):
            choices = self.relationship_types
            lookup = self.everything_after_last(":", text_before_cursor)
        elif self.typing_property(text_before_cursor):
            period_loc = text_before_cursor.rfind(".")
            variable_start_loc = max(
                text_before_cursor.rfind("(", 0, period_loc),
                text_before_cursor.rfind(" ", 0, period_loc))
            variable = text_before_cursor[variable_start_loc + 1:period_loc]

            if variable.isalnum() and any(c.isalpha() for c in variable):
                choices = self.properties
                lookup = self.everything_after_last(".", text_before_cursor)
            else:
                return
        elif text_before_cursor:
            if text_before_cursor[-1].isalpha():
                last_cypher_word = self.most_recent_cypher_word(
                    text_before_cursor)
                choices = self.cypher.most_probable_next_keyword(
                    last_cypher_word)
                lookup = self.last_alphabetic_chunk(text_before_cursor)
        else:
            return

        completions = self.find_matches(lookup, choices)

        for completion in completions:
            yield Completion(completion, -len(lookup))

    @staticmethod
    def find_matches(word, choices):
        word = word.lower()
        lower_choices = [x.lower() for x in choices]

        completions = []

        for i, choice in enumerate(lower_choices):
            if choice.startswith(word):
                completions.append(choices[i])

        return completions

    def most_recent_cypher_word(self, chars):
        text = " " + chars
        keyword_indices = [(word, text.rfind(" " + word + " "))
                           for word in self.cypher.KEYWORDS]
        function_indices = [(word, text.rfind(" " + word + "("))
                            for word in self.cypher.FUNCTIONS]

        indices = keyword_indices + function_indices

        # If no keywords were found, we want to be in the "" state in the Markov model.
        if not any([i[1] > -1 for i in indices]):
            return ""

        most_recent = max(indices, key=lambda i: i[1])[0]
        return most_recent

    @staticmethod
    def last_alphabetic_chunk(chars):
        chars = list(chars)
        chars.reverse()

        keep = []

        for c in chars:
            if c.isalpha():
                keep.append(c)
            else:
                break

        keep.reverse()
        return "".join(keep)

    @staticmethod
    def everything_after_last(char, chars):
        chars = chars.replace("`", "")
        loc = chars.rfind(char)
        return chars[loc + 1:]

    @staticmethod
    def exists_unclosed_char(char, chars):
        return chars.count(char) % 2 != 0

    @staticmethod
    def exists_unclosed_pattern(open_char, close_char, chars):
        return chars.count(open_char) != chars.count(close_char)

    def colon_inside_unclosed_pattern(self, open_char, close_char, chars):
        return self.exists_unclosed_pattern(
            open_char, close_char,
            chars) and chars.rfind(":") > chars.rfind(open_char)

    def typing_relationship(self, chars):
        return self.colon_inside_unclosed_pattern(
            "[", "]", chars) and chars.rfind("[") > chars.rfind("(")

    def typing_label(self, chars):
        return self.colon_inside_unclosed_pattern(
            "(", ")", chars) and chars.rfind("(") > chars.rfind("[")

    def typing_property(self, chars):
        chars = list(chars)
        chars.reverse()

        skip = ["_", "`"]

        # Skip spaces if we're inside backticks.
        if self.exists_unclosed_char("`", chars):
            skip.append(" ")

        for c in chars:
            if not c.isalnum() and c not in skip:
                return c == "."

        return False
Example #10
0
from cycli.cypher import Cypher
from misc.graphgist import get_all_queries
import re

cypher = Cypher()

queries = get_all_queries()

# Each Cypher word is a state in the Markov model. We're also adding a "" state; this means there are no previous words
# (we're at the beginning of the query).
cypher_words = [""] + cypher.words()

# Store the model in a dictionary of dictionaries.
markov = {i: {j: 0.0 for j in cypher_words} for i in cypher_words}

for query in queries:
    # Find the indices of Cypher functions and keywords separately. This results in a list of tuples for each word and its
    # index, e.g. [('MATCH', 0), ('WHERE', 13), ('RETURN', 29)].

    function_indices = []

    for word in cypher.FUNCTIONS:
        idx = [
            m.start()
            for m in re.finditer(" " + word + "\s+\(", query, re.IGNORECASE)
        ]

        for i in idx:
            function_indices.append((word, i))

    # Find the keywords. Make sure they're surrounded by spaces so that we don't grab words within words.
Example #11
0
 def __init__(self, host, port, username, password, logfile, filename, ssl, read_only, timeout):
     self.logfile = logfile
     self.filename = filename
     self.read_only = read_only
     self.neo4j = Neo4j(host, port, username, password, ssl, timeout)
     self.cypher = Cypher()
Example #12
0
class Cycli:

    def __init__(self, host, port, username, password, logfile, filename, ssl, read_only, timeout):
        self.logfile = logfile
        self.filename = filename
        self.read_only = read_only
        self.neo4j = Neo4j(host, port, username, password, ssl, timeout)
        self.cypher = Cypher()

    def write_to_logfile(self, query, response):
        headers = response["headers"]
        rows = response["rows"]
        duration = response["duration"]
        error = response["error"]

        self.logfile.write("> {}\n".format(query))
        self.logfile.write("{}\n".format(pretty_table(headers, rows)))

        if error is False:
            self.logfile.write("{} ms\n\n".format(duration))

    @staticmethod
    def write_to_csvfile(headers, rows):
        filename = "cycli {}.csv".format(datetime.now().strftime("%Y-%m-%d at %I.%M.%S %p"))

        with open(filename, "wt") as csvfile:
            csvwriter = csv.writer(csvfile, quotechar=str('"'), quoting=csv.QUOTE_NONNUMERIC, delimiter=str(","))
            csvwriter.writerow(headers)

            for row in rows:
                csvwriter.writerow(row)

        csvfile.close()

    def run(self):
        labels = self.neo4j.get_labels()
        relationship_types = self.neo4j.get_relationship_types()
        properties = self.neo4j.get_property_keys()

        if self.filename:
            queries = self.filename.read()
            queries = queries.split(";")[:-1]

            for query in queries:
                query += ";"
                query = query.strip()

                print("> " + query)
                self.handle_query(query)
                print()

            return

        click.secho(" ______     __  __     ______     __         __    ", fg="red")
        click.secho("/\  ___\   /\ \_\ \   /\  ___\   /\ \       /\ \   ", fg="yellow")
        click.secho("\ \ \____  \ \____ \  \ \ \____  \ \ \____  \ \ \  ", fg="green")
        click.secho(" \ \_____\  \/\_____\  \ \_____\  \ \_____\  \ \_\ ", fg="blue")
        click.secho("  \/_____/   \/_____/   \/_____/   \/_____/   \/_/ ", fg="magenta")

        print("Cycli version: {}".format(__version__))
        print("Neo4j version: {}".format(".".join(map(str, self.neo4j.neo4j_version))))
        print("Bug reports: https://github.com/nicolewhite/cycli/issues\n")

        completer = CypherCompleter(labels, relationship_types, properties)

        layout = create_prompt_layout(
            lexer=CypherLexer,
            get_prompt_tokens=get_tokens,
            reserve_space_for_menu=8,
        )

        buff = CypherBuffer(
            accept_action=AcceptAction.RETURN_DOCUMENT,
            history=FileHistory(filename=os.path.expanduser('~/.cycli_history')),
            completer=completer,
            complete_while_typing=True,
        )

        application = Application(
            style=PygmentsStyle(CypherStyle),
            buffer=buff,
            layout=layout,
            on_exit=AbortAction.RAISE_EXCEPTION,
            key_bindings_registry=CypherBinder.registry
        )

        cli = CommandLineInterface(application=application, eventloop=create_eventloop())

        try:
            while True:
                document = cli.run()
                query = document.text
                self.handle_query(query)
        except UserWantsOut:
            print("Goodbye!")
        except Exception as e:
            print(e)

    def handle_query(self, query):
        run_n = re.match('run-([0-9]+) (.*)', query, re.DOTALL)
        save_csv = query.startswith("save-csv ")

        if self.cypher.is_a_write_query(query) and self.read_only:
            print("Query aborted. You are in read-only mode.")
        elif query in ["quit", "exit"]:
            raise UserWantsOut
        elif query == "help":
            print_help()
        elif query == "refresh":
            self.neo4j.refresh()
        elif query == "schema":
            self.neo4j.print_schema()
        elif query == "schema-indexes":
            self.neo4j.print_indexes()
        elif query == "schema-constraints":
            self.neo4j.print_constraints()
        elif query == "schema-labels":
            self.neo4j.print_labels()
        elif query == "schema-rels":
            self.neo4j.print_relationship_types()
        elif query.startswith("env"):
            if query == "env":
                for key, value in self.neo4j.parameters.items():
                    print("{0}={1}".format(key, value))
            else:
                key = query[3:]
                key = key.strip("'\"[]")
                value = self.neo4j.parameters.get(key)

                if value is not None:
                    print(value)

        elif query.startswith("export "):
            if "=" not in query:
                print("Set parameters with export key=value.")
            else:
                params = query.replace("export ", "").strip()
                key, value = params.split("=", 1)
                key = key.strip()
                value = value.strip()

                try:
                    value = eval(value)
                    self.neo4j.update_parameters(key, value)
                except Exception as e:
                    print(e)

        else:
            count = int(run_n.group(1)) if run_n else 1
            query = run_n.group(2) if run_n else query
            query = query[len("save-csv "):] if save_csv else query

            if count <= 0 or not query:
                print("Check your syntax. cycli expects run-{n} {query} where {n} is an integer > 0 and {query} is a Cypher query.")
                return

            total_duration = 0
            index = 0

            while index < count:
                response = self.neo4j.cypher(query)

                headers = response["headers"]
                rows = response["rows"]
                duration = response["duration"]
                error = response["error"]
                profile = response.get("profile")

                if error is False:
                    print(pretty_table(headers, rows))

                    ms = "Run {}: {} ms\n".format(index + 1, duration) if run_n else "{} ms".format(duration)
                    print(ms)

                    if profile:
                        self.neo4j.print_profile(profile)
                    if save_csv:
                        self.write_to_csvfile(headers, rows)
                else:
                    print(error)

                if self.logfile:
                    self.write_to_logfile(query, response)

                total_duration += duration
                index += 1

            if run_n and error is False:
                print("Total duration: {} ms".format(total_duration))