├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── gremlinpy-logo.png ├── gremlinpy ├── __init__.py ├── config.py ├── exception.py ├── gremlin.py ├── statement.py ├── tests │ ├── __init__.py │ ├── gremlin.py │ └── statement.py └── version.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | .DS_Store 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | # PyBuilder 55 | target/ 56 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.8.0.1 2 | 3 | Hotfix: 4 | * Fixed an error where copied `gremlinpy.Gremlin` instances did not allow chaining from their last object 5 | 6 | ## 3.8.0 7 | 8 | Added: 9 | * A `copy` method on `gremlinpy.Gremlin`, `gremlinpy.Token`, and `gremlinpy.Predicate` instances that copies over the current query into a new object chain. 10 | 11 | ## 3.7.0 12 | 13 | Added: 14 | * Predicates for 'from', 'to', and 'or' -- https://github.com/emehrkay/gremlinpy/pull/5 15 | 16 | ## 3.6.1 17 | 18 | Fixed: 19 | * Issue where a predicate object followed a predicate object and it didn't convert the Gremlin representation of second predicate correctly. 20 | 21 | ## 3.6.0 22 | 23 | Added: 24 | * NOT 'not' predicate 25 | 26 | Changed: 27 | * All args passed to all functions are bound instead of only the last one. 28 | * If a Param object is passed to Gremlinpy.bind_param the remaining checks for the name/value are not run. 29 | 30 | ## 3.5.0 31 | 32 | Added: 33 | * Added Not predicate as NOT 34 | 35 | ## 3.4.4 36 | 37 | Fixed: 38 | * Error with binding params of parent Gremlin instances overwriting params inherited from children -- https://github.com/emehrkay/gremlinpy/pull/4 39 | 40 | ## 3.4.3 41 | 42 | Fixed: 43 | * Error with binding params of parent Gremlin instances overwriting params inherited from children -- https://github.com/emehrkay/gremlinpy/issues/2 44 | 45 | ## 3.4.2 46 | 47 | Fixed: 48 | * Error with GetEdge Statement regarding direction of other edge. It now takes into account which direction the statement is attempting to check. 49 | 50 | ## 3.4.1 51 | 52 | Fixed: 53 | * Error with install procedure. Moved version number outside of gremlinpy.__init__.py 54 | 55 | ## 3.4.0.1 56 | 57 | Added: 58 | * simple test runner to setup.py 59 | 60 | ## 3.4.0 61 | 62 | Added: 63 | * six dependency 64 | * Python 2.7 support 65 | 66 | ## 3.3.0 67 | 68 | Added: 69 | * _ function that allows you to dynamically create predicates. _('myPredicate', arg, arg2, ...argN) 70 | 71 | Changed: 72 | * Predicates are no longer forced to lower case. If there needs to be a different representation for the predidate, overwrite the _function method 73 | 74 | Fixed: 75 | * There was an error in type checking for Functions' arguments regarding Gremlin instances (fixed in a few other places too). 76 | 77 | ## 3.2.2 78 | 79 | Changed: 80 | * Predicates now back to binding arguments. 81 | 82 | Fixed: 83 | * When defining a parent gremlin object, the current parameters are bound up the chain 84 | * Fixed the GetEdge statement 85 | 86 | Removed: 87 | * A bunch of newly added predicates: out, outE, outV, in, inE, inV, both, bothE, bothV -- they were deemed unnecessary 88 | 89 | ## 3.2.1 90 | 91 | Changed: 92 | * Added new predicates: out, outE, outV, in, inE, inV, both, bothE, bothV 93 | 94 | ## 3.2.0 95 | 96 | Fixed: 97 | * Predicates now work correctly 98 | 99 | Changed: 100 | * When manually adding a token via gremlin.add_token the arguments for the token itself previously required them to be wrapped in a list. This was confusing and caused pain within the tiny library. I removed this in favor of *args and since the lib is in its infancy, this will not cause a version change. 101 | * Made AS (which evals to as()) a predicate because its arguments should not be bound when called. 102 | * Predicates now nolonger bind parameters. They utilitze UnboundFunction under the hood. 103 | 104 | ## 3.1.2 105 | 106 | Fixed: 107 | * Get edge built-in statement 108 | 109 | ## 3.1.1 110 | 111 | Fixed: 112 | * Bug where Gremlin.stack\_bound_params was not redefining the parent variable causing an infinite loop. 113 | 114 | ## 3.1.0 115 | 116 | Added: 117 | * Parameter value caching. The system will attempt to utilize previously defined parameters for newly bound values when that value has been bound before. 118 | 119 | ## 3.0.0 120 | 121 | Added: 122 | * Skipped 3 version numbers just to align with the TinkerPop version. TinerPop2 support is in the gremlinpy 2.X.X release. 123 | * Predicate support. This allows simple gremlinpy strings to be built utiling some keywords defined in the TinkerPop3 library as predicates. 124 | * Python3 support 125 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mark Henderson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Note: 3.6.0 changes the way arguments are automatically bound in Function objects. The old way was to only bind the last argument in a function call, but with 3.6.0 all arguments will be bound. This may break some tests that check the contents of the generated scripts. Please update accordingly 2 | 3 | Gremlinpy 4 | ========= 5 | 6 | Gremlinpy is a small library that allows you to write pure Python and output GremlinGroovy script complete with bound parameters that can be run against a Tinkerpop 3 Gremlin server instance. It is meant to be a low-level way for your application to communicate its intent with the graph server. The best way to think of Gremlinpy is as a very flexible query builder that doesn't limit what can be expressed with Python for GremlinGroovy. 7 | 8 | ![gremlinpy logo by Marko A. Rodriguez ](gremlinpy-logo.png) 9 | 10 | ## Setup 11 | 12 | python setup.py install 13 | 14 | or 15 | 16 | pip install gremlinpy 17 | 18 | or 19 | 20 |    easy_install gremlinpy 21 | 22 | ## Overview 23 | 24 | Python's syntax nearly mirrors Groovy's one-to-one so Grelinpy allows for an easy to manipulate Python object that will produce a Gremlin string as a result. 25 | 26 | Gremlinpy works by tokenizing every action taken on the `gremlinpy.gremlin.Gremlin` instance into a simple linked list. Each `Gremlin` instance starts with a `GraphVariable` and chains the rest of the tokens to it. 27 | 28 | If you wanted to produce a Gremlin script like this: 29 | 30 | g.v(12).outE('knows').inV 31 | 32 | You would simply write: 33 | 34 | ```python 35 | g = Gremlin() 36 | g.v(12).outE('knows').inV 37 | ``` 38 | 39 | Once that is converted to a string, your gremlin instance will hold the bound params (you have the ability to control the names of the bindings). 40 | 41 | ```python 42 | script = str(g) #g.v(GP_KKEI_1).outE(GP_KKEI_2).inV 43 | params = g.bound_params #{'GP_KKEI_1': 12, 'GP_KKEI_2': 'knows'} 44 | ``` 45 | 46 | It's that simple, but Gremlinpy allows you to write very complex Gremlin syntax in pure Python. And if you're having trouble expressing the Gremlin/Groovy in the Python model, it allows for straight string manipulation complete with parameter binding. 47 | 48 | Though the usage of Python's magic methods, we can compose a Gremlin string by recording every attribute, item, or call against the instance and creating a token for it. There are multiple token types that allow you to express Gremlin with Python: 49 | 50 | __GraphVariable__: This is the root token in the list. It is always present and will be used in every Gremlin string produced. It can be nullified by calling `Gremlin.set_graph_variable('')` or during instantiation `g = Gremlin('')`. That is useful for when you are running scripts that may not interact with the graph or when your graph variable is something other than the letter "g". 51 | 52 | __Attribute__: Attributes are things on the Gremlin chain that are not functions, arent closures, and are not indexes. They stand alone and are defined when you call any sequence without parenthesis on your Gremlinpy instance. 53 | 54 | ```python 55 | g.a.b #g.a.b -- a and b are the attribues 56 | ``` 57 | 58 | __Function__: Functions are called when you add parenthesis after an attribute. The function object will bind every argument passed into it. 59 | 60 | ```python 61 | g.V(12) #g.v(GP_UUID_1) 62 | g.bound_params # {'GP_UUID_1': 12} 63 | 64 | g.v.has('name', 'mark') #g.v.has(GP_CXZ_1, GP_CXZ_2) 65 | g.bound_params # {'GP_CXG_2': 'mark', 'GP_CXZ_1': 'name'} 66 | ``` 67 | 68 | A function can also be added to the chain by calling the `func` method on the `Gremlin` instance. The first argument is the name of the function, the rest are bound arguments in the final resulting string. This is useful for function names that are reserved words in Python. 69 | 70 | ```python 71 | g.V(12).func('myMagicFunction', 'arg') # g.V(GP_III_1).myMagicFunction(GP_III_2) 72 | g.bound_params # {'GP_III_1': 12, 'GP_III_2': 'arg'} 73 | ``` 74 | 75 | __FunctionRaw__: Works as `Function` but does not prepend the dot before the function name. 76 | 77 | ```python 78 | g.set_graph_variable('') 79 | g.function('arg')func_raw('some_function', 'raw content') 80 | # function(GP_IUNX_1)some_function(raw content) 81 | ``` 82 | 83 | __UnboundFunction__: This allows you to call a function, but not have the instance automatically bind any of the params. It has a different syntax than just chaning a function in the previous exmaple: 84 | 85 | ```python 86 | g.unbound('function', 'arg', 1, 3, 5) #g.function(arg, 1, 3, 5) 87 | ``` 88 | 89 | __UnboundFunctionRaw__: This works like `UnboundFunction` except it does not prepend a dot before the function definition. 90 | 91 | ```python 92 | g.set_graph_variable('') 93 | g.func_raw_unbound('if', '1 == 2').close('1 is 2?').func_raw_unbound('elseif', '2 == 2').close('2 is 2') 94 | # if(1 == 2){1 is 2?}elseif(2 == 2){2 is 2} 95 | ``` 96 | 97 | __Closure__: Closures simply allow you to put things between curly braces. Since it is an error to add curly braces to the end of Python objects, this has its own method on the `Gremlin` instance: 98 | 99 | ```python 100 | g.func.close('im closing this') # g.func{im closing this} 101 | ``` 102 | 103 | __ClosureArguments__: Groovy allows for inline lambda functions in a syntax that isn't supported by Python. To define the signature for the closure you simply pass in args after the first argument on a closure: 104 | 105 | ```python 106 | g.func.close('body', 'x', 'y') # g.func{x, y -> body } 107 | ``` 108 | 109 | __Raw__: Raw allows you to put anything in and have it passed out the same way. It doesn't put anything before or after the call. It is useful for when you're doing something that cannot easily map: 110 | 111 | ```python 112 | g.set_graph_variable('') 113 | .raw('if(').raw('1 == 2').raw(')') 114 | .close("'never'") 115 | .else.close("'always'") 116 | # if(1 == 2){'never'}else{'always'} 117 | ``` 118 | 119 | > note: this is just an example, there are better ways to do complex composition 120 | 121 | 122 | ### Predicates 123 | 124 | GremlinGroovy has an idea called [predicates](http://tinkerpop.apache.org/docs/3.0.0-incubating/#a-note-on-predicates). A predicate can simply be seen as a traversal that starts with a function and not a graph variable. 125 | 126 | Gremlinpy supports all of the GremlinGroovy defined predicates by sub-classing a `Predicate` object. 127 | 128 | * eq 129 | * neq 130 | * lt 131 | * lte 132 | * gt 133 | * gte 134 | * inside 135 | * outside 136 | * between 137 | * within 138 | * without 139 | 140 | Gremlinpy also aliases some of the predicates that are reserved words in Python: 141 | 142 | * NOT -- not 143 | * IS -- is 144 | * AS -- as 145 | * IN -- in 146 | * FROM -- from 147 | * TO -- to 148 | * OR -- or 149 | 150 | Predicates are used just like any other Gremlin instance: 151 | 152 | ```python 153 | from gremlinpy import Gremlin 154 | 155 | g = Gremlin() 156 | g.V().has('name', neq('mark')) # g.V().has(GP_OOP_1, neq(GP_OOP_2)) 157 | g.bound_params # {'GP_OOP_1': 'name', 'GP_OOP_2': 'mark'} 158 | ``` 159 | 160 | Gremlinpy allows you to create a predicate on the fly using the `_` function: 161 | 162 | ```python 163 | from gremlinpy import Gremlin, _ 164 | 165 | g = Gremlin() 166 | g.V().has(_('myPredicate', [1, 2])) # g.V().has(myPredicate(GP_UIY_1, GP_IUY_2)) 167 | g.bound_params # {'GP_UIY_1': 1, 'GP_UIY_2': 2} 168 | ``` 169 | 170 | 171 | ### Overloading 172 | 173 | The `Gremlin` instance has members that are basically reserved words and will not be passed to your resulting gremlin script. 174 | 175 | These include: 176 | 177 | * \__init__ 178 | * reset 179 | * \__getitem__ 180 | * set_graph_variable 181 | * any other mehtod or property on the object 182 | 183 | If you need the resulting gremlin script to print out '\__init__' or one of the reserved words, you can simply call `add_token` on your instance: 184 | 185 | ```python 186 | init = Function(g, '__init__', ['arg']) 187 | g.add_token(init) # g.__init__(GP_XXX_1) 188 | 189 | init = Attribute(g, '__init__') 190 | g.add_token(init).xxx() # g.__init__.xxx() 191 | 192 | add_token = UnboudFunction(g, 'add_token', [5, 6]) 193 | g.add_token(add_token) # g.add_token(5, 6) 194 | ``` 195 | 196 | ### Binding Params 197 | 198 | All parameters passed into a function are automatically bound. Each `Gremlin` instance creates a unique key to hold the bound parameter values to. However, you can manually bind the param and pass a name that you desire. 199 | 200 | ```python 201 | bound = g.bind_param('my_value', 'MY_PARAM') 202 | 203 | g.v(bound[0]) # g.v(MY_PARAM) 204 | g.bound_params # {'MY_PARAM': 'my_value'} 205 | ``` 206 | 207 | Gremlinpy will attempt to reuse binding names in the generated script. If you previously bound 'mark is great' and bind it again, the script will utilize the same parameters for each instance. This will save on the overall payload that is sent to the Gremlin server and it will save on what the Gremlin server has to cache. 208 | 209 | ~~~python 210 | 211 | bound = g.bind_param('some value', 'SOME\_KEY') 212 | g.function(bound[0]).func2(bound[0]) # g.function(SOME\_KEY).func2(SOME_KEY) 213 | ~~~ 214 | 215 | The easiest way to bind params is to use the `Param` object and pass it where needed: 216 | 217 | ```python 218 | g = Gremlin() 219 | name_param = Param('name', 'mark') 220 | g.V().has('name', name_param) ... 221 | ``` 222 | 223 | ### Nesting Instances 224 | 225 | Gremlinpy gets interesting when you want to compose a very complex string. It will allow you to nest `Gremlin` instances passing any bound params up to the root instance. 226 | 227 | Nesting allows you to have more control over query creation, it offers some sanity when dealing with huge strings. 228 | 229 | ```python 230 | g = Gremlin() 231 | i = Gremlin() 232 | 233 | i.set_graph_variable('').it.setProperty('age', 33) 234 | g.v(12).close(i) # g.v(GP_XXQ_1){it.setProperty(GP_UYI_3, GP_UYI_1)} 235 | 236 | g.bound_params # {'GP_XXQ_1': 12, 'GP_UYI': 33, 'GP_UYI_3': 'age'} 237 | ``` 238 | 239 | ### Copying 240 | 241 | Gremlinpy has a built-in way to copy instances. This is useful for when you want to branch at a current point along the path, but keep the preceding definitions in tact. 242 | 243 | ```python 244 | page = get_page() 245 | per_page = get_per_page() 246 | 247 | g = Gremlin().hasLabel('user').hasId(15).out('blog_post') # get all of the blog posts for user 15 248 | 249 | # we will branch here to get the total number of posts for the user 250 | total = g.copy().size() 251 | 252 | # we continue here to paginate 253 | g.range(page, page * per_page) 254 | 255 | # run queries 256 | ``` 257 | 258 | ## Statements 259 | 260 | Gremlinpy allows you to compose very complex gremlin chains. A Gremlinpy Statement object allows you to encapsulate and package a part of that construction. 261 | 262 | Gremlinpy works by tokenizing every action against the object instance into a simple linked list, a statement will allow you apply a preset token definiton to a `Gremlin` instance. 263 | 264 | ### Usage 265 | 266 | Statements can be used in a few ways, the simplest is to apply it directly to a Gremlin instance. When used this way the statement will augment the Gremlin instance that is directly applied to. 267 | 268 | ```python 269 | class HasMark(Statement): 270 | """ 271 | this statement simply appends .has('name', 'Mark') to a gremlin script 272 | """ 273 | def build(self): 274 | g = self.gremlin() 275 | 276 | g.has('name', 'Mark') 277 | 278 | g = Gremlin() 279 | mark = HasMark() 280 | g.V.apply_statement(mark) 281 | 282 | str(g) # g.V.has(GP_IOKH_1, GP_IOKH_2) 283 | g.bound_params # {'CP_IOKH_1': 'name', 'GP_IOKH_2': 'Mark'} 284 | ``` 285 | 286 | Statements can also be chained: 287 | 288 | ```python 289 | class HasSex(Statement): 290 | def __init__(self, sex): 291 | self.sex = sex 292 | 293 | def build(self): 294 | self.gremlin.has('sex', self.sex) 295 | 296 | g = Gremlin() 297 | mark = HasMark() 298 | sex = HasSex('male') 299 | 300 | g.V.apply_statement(mark).apply_statement(sex) 301 | 302 | str(g) # g.V.has(GP_IOKH_1, GP_IOKH_2).has(GP_IOKH_3, GP_IOKH_4) 303 | g.bound_params # {'CP_IOKH_1': 'name', 'GP_IOKH_2': 'Mark', 'GP_IOKH_3': 'sex', 'GP_IOKH_4': 'male'} 304 | ``` 305 | 306 | A statement can be passed into a Gremlin instance Function, Raw, Closure call. These statements will not modify the Gremlin instance that they are passed into. If you want the statement to have a specialized Gremlin instance, you must pass it into the statement. Otherwise a blank Gremlin instance is created and passed into the Statement. 307 | 308 | > Note: do not pass in the outer Gremlin instance to Statements that will be used this way as an infinite loop will be created 309 | 310 | ```python 311 | class GetV(Statement): 312 | def __init__(self, id): 313 | self.id = id 314 | 315 | def build(self): 316 | self.gremlin.v(self.id) 317 | 318 | g = Gremlin() 319 | v = GetV(44) 320 | if_con = UnboundFunction(g, 'if', '1 == 1') 321 | 322 | g.set_graph_variable('').add_token(if_con).close(v) 323 | 324 | str(g) # if(1 == 1){g.v(GP_DDIO_1)} 325 | g.bound_params # {'GP_DDIO_1': 44} 326 | ``` 327 | 328 | ## Performance Tweaks 329 | ### Always Manually Bind Params 330 | 331 | If your Gremlin server instance has query caching turned on, manually binding params will allow you to create statements on the server that will pre-parse your query the second time you run it an return results quicker. 332 | 333 | If you are not manually binding params, every time you call a script, even the same script, a different one is being sent to the server. 334 | 335 | ```python 336 | g.v(12) # g.v(GP_XSX_1) 337 | 338 | #later 339 | g.v(12) # g.v(GP_POI_1) 340 | ``` 341 | 342 | If you manually bind the param with a name, the same script will be sent to the server and it will drastically cut down on execution times. This is true even if the param values are changed: 343 | 344 | ```python 345 | id = g.bind_param(12, 'eyed') 346 | g.v('eyed') #g.v(eye_d) 347 | 348 | id = g.bind_param(9999, 'eyed') 349 | g.v('eyed') #g.v(eye_d) <--- this one executes faster than the first 350 | ``` 351 | -------------------------------------------------------------------------------- /gremlinpy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emehrkay/gremlinpy/cbbd1b77d2b9f66647df73ae796c313345cddf6d/gremlinpy-logo.png -------------------------------------------------------------------------------- /gremlinpy/__init__.py: -------------------------------------------------------------------------------- 1 | from .version import __version__ 2 | from .gremlin import * 3 | from .config import * 4 | from .exception import * 5 | from .statement import * 6 | -------------------------------------------------------------------------------- /gremlinpy/config.py: -------------------------------------------------------------------------------- 1 | GRAPH_VARIABLE = 'g' 2 | -------------------------------------------------------------------------------- /gremlinpy/exception.py: -------------------------------------------------------------------------------- 1 | import gremlinpy.config 2 | 3 | 4 | class GremlinError(Exception): 5 | pass 6 | 7 | 8 | class StatementError(GremlinError): 9 | pass 10 | 11 | 12 | class TokenError(GremlinError): 13 | pass 14 | 15 | 16 | class PredicateError(GremlinError): 17 | pass 18 | -------------------------------------------------------------------------------- /gremlinpy/gremlin.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import uuid 3 | import copy 4 | import re 5 | 6 | from six import with_metaclass 7 | 8 | from .exception import * 9 | from .statement import Statement 10 | from .config import GRAPH_VARIABLE 11 | 12 | 13 | _PREDICATES = {} 14 | MODULE = sys.modules[__name__] 15 | 16 | 17 | class LinkList(object): 18 | top = None 19 | bottom = None 20 | 21 | def add(self, link): 22 | self.bottom.next = link 23 | self.bottom = link 24 | 25 | return self 26 | 27 | def remove(self, link, drop_after=False): 28 | next = None 29 | token = self.top 30 | 31 | while token: 32 | if link == token: 33 | if drop_after: 34 | token.bottom = None 35 | break 36 | else: 37 | token.bottom = token.next.next if token.next else None 38 | 39 | token = token.next 40 | 41 | return self 42 | 43 | def can_use(self, prev, link): 44 | return True 45 | 46 | def __str__(self): 47 | return self.__unicode__() 48 | 49 | def __unicode__(self): 50 | token = self.top 51 | prev = token 52 | tokens = [] 53 | variable = '' 54 | 55 | """ 56 | prepare the gremlin string 57 | use the token's concat value only if the preceding token is 58 | not Raw or an empty string (this happens when the graph variable 59 | is set to '' 60 | """ 61 | while token: 62 | string = str(token) 63 | 64 | if len(tokens) and token.concat and self.can_use(prev, token): 65 | if type(prev) == GraphVariable: 66 | append = len(tokens[-1]) > 0 67 | else: 68 | append = True 69 | 70 | if append: 71 | tokens.append(token.concat) 72 | 73 | tokens.append(string) 74 | 75 | prev = token 76 | token = token.next 77 | 78 | return '{}{}'.format(variable, ''.join(tokens)) 79 | 80 | 81 | class Link(object): 82 | next = None 83 | 84 | 85 | class Param(object): 86 | 87 | def __init__(self, name, value=None): 88 | self.name = name 89 | self.value = value if value is not None else name 90 | 91 | 92 | class Gremlin(LinkList): 93 | PARAM_PREFIX = 'GPY_PARAM' 94 | 95 | def __init__(self, graph_variable=GRAPH_VARIABLE, parent=None): 96 | self.gv = graph_variable 97 | self.top = GraphVariable(self, graph_variable) 98 | self._gremlins = [] 99 | self.parent = None 100 | 101 | self.reset() 102 | 103 | if parent: 104 | self.set_parent_gremlin(parent) 105 | 106 | def reset(self): 107 | if self.parent: 108 | self.parent.reset() 109 | 110 | for gremlin in self._gremlins: 111 | gremlin.reset() 112 | 113 | self.parent = None 114 | self.bottom = self.top 115 | self.bound_params = {} 116 | self.bound_param = str(uuid.uuid4())[-5:] 117 | self.bound_count = 0 118 | self.top.next = None 119 | self.return_var = None 120 | self._gremlins = [] 121 | 122 | return self.set_graph_variable(self.gv) 123 | 124 | def copy(self, gremlin=None): 125 | if not gremlin: 126 | gremlin = Gremlin(self.gv) 127 | gremlin.bound_params = copy.deepcopy(self.bound_params) 128 | 129 | if self.top: 130 | gremlin.top = self.top.copy(gremlin) 131 | 132 | if self.return_var: 133 | gremlin.return_var = self.return_var 134 | 135 | return gremlin 136 | 137 | @property 138 | def stack_bound_params(self): 139 | parent = self.parent 140 | params = copy.deepcopy(self.bound_params) 141 | 142 | while parent: 143 | params.update(parent.stack_bound_params) 144 | parent = parent.parent 145 | 146 | return params 147 | 148 | def can_use(self, prev, link): 149 | return type(prev) is not Raw 150 | 151 | def __unicode__(self): 152 | string = super(Gremlin, self).__unicode__() 153 | variable = '' 154 | 155 | if self.return_var is not None: 156 | variable = '{} = '.format(self.return_var) 157 | 158 | return '{}{}'.format(variable, string) 159 | 160 | def __getattr__(self, attr): 161 | attr = Attribute(self, attr) 162 | 163 | return self.add_token(attr) 164 | 165 | def __call__(self, *args): 166 | func_name = str(self.bottom) 167 | 168 | if func_name in _PREDICATES.keys() and hasattr(MODULE, func_name): 169 | pred = getattr(MODULE, func_name)(*args, gremlin=self) 170 | func = pred.bottom 171 | elif len(args) and issubclass(type(args[-1]), Predicate): 172 | func = UnboudFunction(self, func_name, *args) 173 | else: 174 | func = Function(self, func_name, *args) 175 | 176 | self.bottom.next = func 177 | 178 | return self.remove_token(self.bottom).add_token(func) 179 | 180 | def __getitem__(self, val): 181 | # TODO: clean this up 182 | if type(val) is not slice: 183 | val = val if type(val) is list or type(val) is tuple else [val] 184 | 185 | try: 186 | start = val[0] 187 | except Exception as e: 188 | start = None 189 | 190 | try: 191 | end = val[1] 192 | except Exception as e: 193 | end = None 194 | 195 | try: 196 | step = val[2] 197 | except Exception as e: 198 | step = None 199 | 200 | val = slice(start, end, step) 201 | index = Index(self, val) 202 | 203 | return self.add_token(index) 204 | 205 | def set_parent_gremlin(self, gremlin): 206 | self.parent = gremlin 207 | 208 | self._gremlins.append(gremlin) 209 | gremlin.bind_params(self.bound_params) 210 | 211 | return self.bind_params(gremlin.bound_params) 212 | 213 | def bind_params(self, params=None): 214 | if params is None: 215 | params = [] 216 | 217 | if isinstance(params, dict): 218 | for name, value in params.items(): 219 | self.bind_param(value, name) 220 | else: 221 | for value in params: 222 | self.bind_param(value) 223 | 224 | return self.bound_params 225 | 226 | def bind_param(self, value, name=None): 227 | self.bound_count += 1 228 | 229 | if isinstance(value, Param): 230 | name = value.name 231 | value = value.value 232 | elif not name and value in self.stack_bound_params.values(): 233 | for n, v in self.bound_params.items(): 234 | if v == value: 235 | name = n 236 | break 237 | elif not name and value in self.stack_bound_params.keys(): 238 | for n, v in self.bound_params.items(): 239 | if n == value: 240 | name = n 241 | value = v 242 | break 243 | 244 | if name is None: 245 | name = '{}_{}_{}'.format(self.PARAM_PREFIX, self.bound_param, 246 | self.bound_count) 247 | 248 | self.bound_params[name] = value 249 | 250 | if self.parent is not None: 251 | self.parent.bind_param(value, name) 252 | 253 | return (name, value) 254 | 255 | def range(self, start, end): 256 | if isinstance(start, (int, float, complex)): 257 | start = str(start) 258 | 259 | if isinstance(end, (int, float, complex)): 260 | end = str(end) 261 | 262 | return self.func('range', *(start, end)) 263 | 264 | def unbound(self, function, *args): 265 | unbound = UnboudFunction(self, function, *args) 266 | 267 | return self.add_token(unbound) 268 | 269 | def func(self, function, *args): 270 | func = Function(self, function, *args) 271 | 272 | return self.add_token(func) 273 | 274 | def func_raw(self, function, *args): 275 | func_raw = FunctionRaw(self, function, *args) 276 | 277 | return self.add_token(func_raw) 278 | 279 | def func_raw_unbound(self, function, *args): 280 | func_raw = UnboudFunctionRaw(self, function, *args) 281 | 282 | return self.add_token(func_raw) 283 | 284 | def close(self, value, *args): 285 | if args: 286 | close = ClosureArguments(self, value, *args) 287 | else: 288 | close = Closure(self, value) 289 | 290 | return self.add_token(close) 291 | 292 | def raw(self, value): 293 | raw = Raw(self, value) 294 | 295 | return self.add_token(raw) 296 | 297 | def add_token(self, token): 298 | self.bottom.next = token 299 | self.bottom = token 300 | 301 | return self 302 | 303 | def remove_token(self, remove): 304 | token = self.top 305 | 306 | while token: 307 | if token.next == remove: 308 | token.next = token.next.next 309 | break 310 | 311 | token = token.next 312 | 313 | return self 314 | 315 | def set_ret_variable(self, return_var=None): 316 | self.return_var = return_var 317 | 318 | return self 319 | 320 | def set_graph_variable(self, graph_variable='g'): 321 | if not graph_variable: 322 | graph_variable = '' 323 | 324 | self.top.value = graph_variable 325 | 326 | return self 327 | 328 | def apply_statement(self, statement): 329 | statement.set_gremlin(self).build() 330 | 331 | return self 332 | 333 | 334 | class _Tokenable(object): 335 | 336 | def __str__(self): 337 | return str(self.__unicode__()) 338 | 339 | def __unicode__(self): 340 | return self.value 341 | 342 | def apply_statement(self, statement): 343 | if hasattr(statement, 'gremlin') == False: 344 | statement.set_gremlin(Gremlin()) 345 | 346 | statement.gremlin.set_parent_gremlin(self.gremlin) 347 | 348 | return statement 349 | 350 | def fix_value(self, value): 351 | if isinstance(value, Param): 352 | return self.gremlin.bind_param(value)[0] 353 | elif isinstance(value, Token): 354 | return value 355 | elif isinstance(value, Predicate): 356 | value.gremlin = Gremlin(self.gremlin.gv) 357 | value.set_parent_gremlin(self.gremlin) 358 | 359 | return str(value) 360 | elif isinstance(value, (list, tuple)): 361 | value = [self.fix_value(a) for a in value] 362 | 363 | return value if isinstance(value, list) else tuple(value) 364 | elif issubclass(type(value), Statement): 365 | self.apply_statement(value) 366 | return str(value) 367 | elif isinstance(value, Gremlin): 368 | value.set_parent_gremlin(self.gremlin) 369 | 370 | return str(value) 371 | else: 372 | return value 373 | 374 | 375 | class Token(Link, _Tokenable): 376 | next = None 377 | value = None 378 | args = [] 379 | concat = '' 380 | 381 | def __init__(self, gremlin, value, *args): 382 | self.gremlin = gremlin 383 | self.value = self.fix_value(value) 384 | self.args = list(args) 385 | 386 | def copy(self, gremlin): 387 | value = copy.deepcopy(self.value) 388 | args = [] 389 | 390 | for arg in self.args: 391 | if isinstance(arg, Gremlin): 392 | args.append(arg.copy()) 393 | else: 394 | args.append(copy.deepcopy(arg)) 395 | 396 | token = getattr(MODULE, self.__class__.__name__)(gremlin, value, *args) 397 | gremlin.bottom = token 398 | 399 | if self.next: 400 | nxt = self.next.copy(gremlin) 401 | token.next = nxt 402 | 403 | return token 404 | 405 | 406 | class GraphVariable(Token): 407 | 408 | def __unicode__(self): 409 | if self.value == '': 410 | self.concat = '' 411 | 412 | return self.value 413 | 414 | 415 | class Attribute(Token): 416 | concat = '.' 417 | 418 | 419 | class Function(Token): 420 | """ 421 | class used to create a Gremlin function 422 | it assumes that the last argument passed to the function is the only thing 423 | that will be bound 424 | if you need more than the last argument bound, you can do: 425 | 426 | g = Gremlin() 427 | value1 = g.bind_param('value1')[0] 428 | value2 = g.bind_param('value2')[0] 429 | g.functionName('not_bound', value1, value2, ...) 430 | """ 431 | concat = '.' 432 | 433 | def __unicode__(self): 434 | params = [] 435 | 436 | if len(self.args): 437 | for arg in self.args: 438 | if issubclass(type(arg), Statement): 439 | self.apply_statment(arg) 440 | 441 | params.append(str(arg)) 442 | elif issubclass(type(arg), Gremlin): 443 | arg.set_parent_gremlin(self.gremlin) 444 | 445 | params.append(str(arg)) 446 | elif isinstance(arg, Param): 447 | params.append(self.gremlin.bind_param(arg)[0]) 448 | else: 449 | params.append(self.gremlin.bind_param(arg)[0]) 450 | 451 | return '{}({})'.format(self.value, ', '.join(params)) 452 | 453 | 454 | class FunctionRaw(Function): 455 | concat = '' 456 | 457 | 458 | class UnboudFunction(Token): 459 | concat = '.' 460 | 461 | def __unicode__(self): 462 | args = [self.fix_value(a) for a in self.args] 463 | 464 | return '{}({})'.format(self.value, ', '.join(args)) 465 | 466 | 467 | class UnboudFunctionRaw(UnboudFunction): 468 | concat = '' 469 | 470 | 471 | class Index(Token): 472 | 473 | def __unicode__(self): 474 | if self.value.stop is not None: 475 | index = '[{}..{}]'.format(self.value.start, self.value.stop) 476 | else: 477 | index = '[{}]'.format(self.value.start) 478 | 479 | return index 480 | 481 | 482 | class Closure(Token): 483 | 484 | def __unicode__(self): 485 | if issubclass(type(self.value), Statement): 486 | self.gremlin.apply_statment(self.value) 487 | 488 | self.value = str(self.gremlin) 489 | elif issubclass(type(self.value), Gremlin): 490 | self.value.set_parent_gremlin(self.gremlin) 491 | 492 | return '{%s}' % str(self.value) 493 | 494 | 495 | class ClosureArguments(Token): 496 | 497 | def __unicode__(self): 498 | if issubclass(type(bound), Statement): 499 | self.gremlin.apply_statment(self.value) 500 | 501 | self.value = str(self.gremlin) 502 | elif issubclass(type(self.value), Gremlin): 503 | self.value.set_parent_gremlin(self.gremlin) 504 | 505 | return '{{} -> {}}'.format(','.join(self.args), str(self.value)) 506 | 507 | 508 | class Raw(Token): 509 | 510 | def __unicode__(self): 511 | if issubclass(type(self.value), Statement): 512 | self.apply_statement(self.value) 513 | 514 | self.value = str(self.value) 515 | elif issubclass(type(self.value), Gremlin): 516 | self.value.set_parent_gremlin(self.gremlin) 517 | 518 | return str(self.value) 519 | 520 | 521 | class _MetaPredicate(type): 522 | 523 | def __new__(cls, name, bases, attrs): 524 | cls = super(_MetaPredicate, cls).__new__(cls, name, bases, attrs) 525 | _PREDICATES[name] = cls 526 | 527 | return cls 528 | 529 | 530 | class Predicate(with_metaclass(_MetaPredicate, Gremlin)): 531 | 532 | def __init__(self, *args, **kwargs): 533 | gremlin = kwargs.get('gremlin', None) 534 | super(Predicate, self).__init__(None, gremlin) 535 | 536 | self.args = args 537 | self.func(self._function, *args) 538 | 539 | @property 540 | def _function(self, *args): 541 | return str(self.__class__.__name__) 542 | 543 | 544 | class p(Predicate): 545 | pass 546 | 547 | 548 | class pp(Predicate): 549 | pass 550 | 551 | 552 | class eq(Predicate): 553 | pass 554 | 555 | 556 | class neq(Predicate): 557 | pass 558 | 559 | 560 | class lt(Predicate): 561 | pass 562 | 563 | 564 | class lte(Predicate): 565 | pass 566 | 567 | 568 | class gt(Predicate): 569 | pass 570 | 571 | 572 | class gte(Predicate): 573 | pass 574 | 575 | 576 | class inside(Predicate): 577 | pass 578 | 579 | 580 | class NOT(Predicate): 581 | """Allows for easy use of 'not' in steps""" 582 | 583 | @property 584 | def _function(self, *args): 585 | return 'not' 586 | 587 | 588 | class outside(Predicate): 589 | pass 590 | 591 | 592 | class between(Predicate): 593 | pass 594 | 595 | 596 | class within(Predicate): 597 | pass 598 | 599 | 600 | class without(Predicate): 601 | pass 602 | 603 | 604 | class IS(Predicate): 605 | """Allows for easy use of 'is' in steps""" 606 | 607 | @property 608 | def _function(self, *args): 609 | return 'is' 610 | 611 | 612 | class select(Predicate): 613 | """Allows for easy use of select steps""" 614 | pass 615 | 616 | 617 | class AS(Predicate): 618 | """Allows for easy use of as in steps""" 619 | 620 | @property 621 | def _function(self, *args): 622 | return 'as' 623 | 624 | 625 | class AND(Predicate): 626 | """Allows for easy use of and in steps""" 627 | 628 | @property 629 | def _function(self, *args): 630 | return 'and' 631 | 632 | class OR(Predicate): 633 | """Allows for easy use of 'or' in steps""" 634 | 635 | @property 636 | def _function(self, *args): 637 | return 'or' 638 | 639 | 640 | class IN(Predicate): 641 | """Allows for easy use of 'in' in steps""" 642 | 643 | @property 644 | def _function(self, *args): 645 | return 'in' 646 | 647 | class TO(Predicate): 648 | """Allows for easy use of 'to' in steps""" 649 | 650 | @property 651 | def _function(self, *args): 652 | return 'to' 653 | 654 | class FROM(Predicate): 655 | """Allows for easy use of 'from' in steps""" 656 | 657 | @property 658 | def _function(self, *args): 659 | return 'from' 660 | 661 | 662 | def _(method, *args): 663 | """method used to create predicates dynamically""" 664 | kls = type(method, (Predicate,), {}) 665 | 666 | return kls(*args) 667 | 668 | 669 | class Anon(object): 670 | """class used to create new Gremlin instances every time an anonymous 671 | traversal is started with the __""" 672 | 673 | def __init__(self): 674 | pass 675 | 676 | def __getattr__(self, attr): 677 | gremlin = Gremlin('__') 678 | 679 | getattr(gremlin, attr) 680 | 681 | return gremlin 682 | 683 | 684 | __ = Anon() 685 | -------------------------------------------------------------------------------- /gremlinpy/statement.py: -------------------------------------------------------------------------------- 1 | import gremlinpy.config 2 | from gremlinpy.exception import GremlinError, StatementError 3 | 4 | 5 | class Statement(object): 6 | 7 | def set_gremlin(self, gremlin): 8 | self.gremlin = gremlin 9 | 10 | return self 11 | 12 | def build(self): 13 | raise NotImplementedError('Gremlinpy statements need a build method') 14 | 15 | def __str__(self): 16 | return self.__unicode__() 17 | 18 | def __unicode__(self): 19 | self.build() 20 | 21 | return str(self.gremlin) 22 | 23 | 24 | class Conditional(Statement): 25 | 26 | def __init__(self): 27 | self.if_condition = None 28 | self.if_body = None 29 | self.else_body = None 30 | self.elif_condition = [] 31 | self.elif_body = [] 32 | 33 | def set_if(self, condition, body): 34 | self.if_condition = condition 35 | self.if_body = body 36 | 37 | return self 38 | 39 | def set_elif(self, condition, body): 40 | self.elif_condition.append(condition) 41 | self.elif_body.append(body) 42 | 43 | return self 44 | 45 | def set_else(self, body): 46 | self.else_body = body 47 | 48 | return self 49 | 50 | def build(self): 51 | from gremlinpy.gremlin import UnboudFunctionRaw, Raw 52 | 53 | gremlin = self.gremlin 54 | 55 | if self.if_condition is None or self.if_body is None: 56 | error = 'Your statement needs at least both an if clause and body' 57 | raise StatmentError(error) 58 | 59 | #build the if statement 60 | if_condition = UnboudFunctionRaw(gremlin, 'if', self.if_condition) 61 | 62 | gremlin.set_graph_variable('').add_token(if_condition) 63 | gremlin.close(self.if_body) 64 | 65 | #build all of the elif statements 66 | for i, condition in enumerate(self.elif_condition): 67 | body = self.elif_body[i] 68 | elif_condition = UnboudFunctionRaw(gremlin, 'elseif', condition) 69 | 70 | gremlin.add_token(elif_condition).close(body) 71 | 72 | #build else statement 73 | if self.else_body is not None: 74 | else_condition = Raw(gremlin, 'else') 75 | 76 | gremlin.add_token(else_condition).close(self.else_body) 77 | 78 | return self 79 | 80 | 81 | class GetEdge(Statement): 82 | bound_in_id = 'V_IN_ID' 83 | bound_out_id = 'V_OUT_ID' 84 | bound_label = 'EDGE_LABEL_NAME' 85 | bound_entity = 'EDGE_ENTITY' 86 | directions = { 87 | 'both': 'bothE', 88 | 'in': 'inE', 89 | 'out': 'outE'} 90 | vertex_directions = { 91 | 'in': 'inV', 92 | 'out': 'outV'} 93 | 94 | def __init__(self, out_v_id, in_v_id, label, direction='both', \ 95 | bind_ids=True): 96 | if direction not in self.directions: 97 | error = 'The direction must be: ' + \ 98 | ', '.join(self.directions.keys()) 99 | raise ValueError(error) 100 | other = 'in' 101 | 102 | if direction != 'both': 103 | other = 'in' if direction == 'out' else 'out' 104 | 105 | self.out_v_id = out_v_id 106 | self.in_v_id = in_v_id 107 | self.label = label 108 | self.bind_ids = bind_ids 109 | self.direction = self.directions[direction] 110 | self.vertex_direction = self.vertex_directions[other] 111 | 112 | def build(self): 113 | if self.bind_ids: 114 | out_id = self.gremlin.bind_param(self.out_v_id, self.bound_out_id) 115 | in_id = self.gremlin.bind_param(self.in_v_id, self.bound_in_id) 116 | else: 117 | out_id = [self.out_v_id] 118 | in_id = [self.in_v_id] 119 | 120 | label = self.gremlin.bind_param(self.label, self.bound_label) 121 | back = self.gremlin.bind_param(self.bound_entity.lower(), 122 | self.bound_entity) 123 | 124 | self.gremlin.V(out_id[0]) 125 | getattr(self.gremlin, self.direction)(self.label) 126 | self.gremlin.AS(back[0]).func(self.vertex_direction) 127 | self.gremlin.hasId(in_id[0]).select(back[0]) 128 | -------------------------------------------------------------------------------- /gremlinpy/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .gremlin import * 2 | from .statement import * 3 | -------------------------------------------------------------------------------- /gremlinpy/tests/gremlin.py: -------------------------------------------------------------------------------- 1 | from random import randrange, random 2 | 3 | import unittest 4 | import re 5 | 6 | from gremlinpy.gremlin import * 7 | from gremlinpy.gremlin import _ 8 | 9 | 10 | def get_dict_key(dict, value): 11 | for k, v in dict.items(): 12 | if v == value: 13 | return k 14 | 15 | return None 16 | 17 | 18 | def gremlin_as_string(gremlin): 19 | script = str(gremlin) 20 | params = gremlin.bound_params 21 | pattern = re.compile(r'\b(' + '|'.join(params.keys()) + r')\b') 22 | 23 | def su(x): 24 | if not params: 25 | return '' 26 | 27 | x = str(params[x.group()]) if params[x.group()] else '' 28 | return "'%s'" % x 29 | 30 | return pattern.sub(su, script) 31 | 32 | 33 | class GremlinTests(unittest.TestCase): 34 | 35 | def test_gremlin_instance(self): 36 | g = Gremlin() 37 | 38 | self.assertEqual(type(g), Gremlin) 39 | 40 | def test_can_drop_graph_variable(self): 41 | g = Gremlin().set_graph_variable('') 42 | 43 | self.assertEqual(str(g), '') 44 | 45 | def test_can_change_graph_variable(self): 46 | g = Gremlin().set_graph_variable('x') 47 | 48 | self.assertEqual(str(g), 'x') 49 | 50 | def test_can_add_one_attribute(self): 51 | g = Gremlin().a 52 | expected = 'g.a' 53 | 54 | self.assertEqual(str(g), expected) 55 | 56 | def test_can_add_one_attribute_and_drop_graph_variable(self): 57 | g = Gremlin().a.set_graph_variable('') 58 | expected = 'a' 59 | 60 | self.assertEqual(str(g), expected) 61 | 62 | def test_can_add_two_attributes(self): 63 | g = Gremlin().a.b 64 | expected = 'g.a.b' 65 | 66 | self.assertEqual(str(g), expected) 67 | 68 | def test_can_add_random_attributes(self): 69 | g = Gremlin() 70 | exp = ['g'] 71 | 72 | for x in range(1, randrange(5, 22)): 73 | getattr(g, str(x)) 74 | exp.append(str(x)) 75 | 76 | expected = '.'.join(exp) 77 | 78 | self.assertEqual(str(g), expected) 79 | 80 | def test_can_add_random_attributes_drop_graph_variable(self): 81 | g = Gremlin().set_graph_variable('') 82 | exp = [] 83 | 84 | for x in range(1, randrange(5, 22)): 85 | getattr(g, str(x)) 86 | exp.append(str(x)) 87 | 88 | expected = '.'.join(exp) 89 | 90 | self.assertEqual(str(g), expected) 91 | 92 | def test_can_add_return_variable(self): 93 | g = Gremlin() 94 | v = 'ret_var' 95 | 96 | g.set_ret_variable(v).function() 97 | 98 | expected = '%s = g.function()' % v 99 | script = str(g) 100 | 101 | self.assertEqual(expected, script) 102 | 103 | def test_can_add_and_remove_return_variable(self): 104 | g = Gremlin() 105 | v = 'ret_var' 106 | 107 | g.set_ret_variable(v).function().set_ret_variable(None) 108 | 109 | expected = 'g.function()' 110 | script = str(g) 111 | 112 | self.assertEqual(expected, script) 113 | 114 | def test_can_add_functon(self): 115 | g = Gremlin().function() 116 | e = 'g.function()' 117 | 118 | self.assertEqual(str(g), e) 119 | self.assertEqual(len(g.bound_params), 0) 120 | 121 | def test_can_add_unbound_function(self): 122 | g = Gremlin().unbound('mark') 123 | e = 'g.mark()' 124 | 125 | self.assertEqual(str(g), e) 126 | self.assertEqual(len(g.bound_params), 0) 127 | 128 | def test_can_add_raw_function(self): 129 | g = Gremlin().func_raw('mark') 130 | e = 'gmark()' 131 | 132 | self.assertEqual(str(g), e) 133 | self.assertEqual(len(g.bound_params), 0) 134 | 135 | def test_can_add_functon_one_arg(self): 136 | g = Gremlin().function('arg') 137 | string = str(g) 138 | bind, value = g.bound_params.copy().popitem() 139 | expected = 'g.function(%s)' % bind 140 | 141 | self.assertEqual(string, expected) 142 | self.assertEqual(len(g.bound_params), 1) 143 | 144 | def test_will_bind_all_params_in_function(self): 145 | g = Gremlin().some_function('one', 'two', 'three') 146 | string = str(g) 147 | params = g.bound_params 148 | one = get_dict_key(params, 'one') 149 | two = get_dict_key(params, 'two') 150 | three = get_dict_key(params, 'three') 151 | expected = 'g.some_function({}, {}, {})'.format(one, two, three) 152 | 153 | self.assertEqual(3, len(params)) 154 | self.assertEqual(expected, string) 155 | 156 | def test_will_bind_all_params_in_function_with_mixed_manually_bound_args(self): 157 | g = Gremlin() 158 | b_one = Param('one', 'one') 159 | b_two = g.bind_param('two', 'two') 160 | 161 | g.some_function(b_one, b_two[0], 'three') 162 | 163 | string = str(g) 164 | params = g.bound_params 165 | one = get_dict_key(params, 'one') 166 | two = get_dict_key(params, 'two') 167 | three = get_dict_key(params, 'three') 168 | expected = 'g.some_function({}, {}, {})'.format(one, two, three) 169 | 170 | self.assertEqual(3, len(params)) 171 | self.assertEqual(expected, string) 172 | 173 | def test_will_bind_all_params_in_function_with_all_manually_bound_args(self): 174 | g = Gremlin() 175 | b_one = Param('one', 'one') 176 | b_two = g.bind_param('two', 'two') 177 | b_three = Param('three', 'three') 178 | 179 | g.some_function(b_one, b_two[0], b_three) 180 | 181 | string = str(g) 182 | params = g.bound_params 183 | one = get_dict_key(params, 'one') 184 | two = get_dict_key(params, 'two') 185 | three = get_dict_key(params, 'three') 186 | expected = 'g.some_function({}, {}, {})'.format(one, two, three) 187 | 188 | self.assertEqual(3, len(params)) 189 | self.assertEqual(expected, string) 190 | 191 | def test_can_add_fuctions_binding_same_value_with_one_bound_param(self): 192 | val = 'random' + str(random()) 193 | g = Gremlin() 194 | g.func1(val).func2(val) 195 | s = str(g) 196 | params = g.bound_params 197 | one = get_dict_key(params, val) 198 | two = get_dict_key(params, val) 199 | expected = 'g.func1({}).func2({})'.format(one, two) 200 | 201 | self.assertEqual(1, len(params)) 202 | self.assertEqual(expected, s) 203 | 204 | def test_can_add_unbound_functon_one_arg(self): 205 | g = Gremlin().unbound('function', 'arg') 206 | string = str(g) 207 | expected = 'g.function(arg)' 208 | 209 | self.assertEqual(string, expected) 210 | self.assertEqual(len(g.bound_params), 0) 211 | 212 | def test_can_add_raw_functon_one_arg(self): 213 | g = Gremlin().func_raw('function', 'arg') 214 | string = str(g) 215 | 216 | bind, value = g.bound_params.copy().popitem() 217 | expected = 'gfunction(%s)' % bind 218 | 219 | self.assertEqual(string, expected) 220 | self.assertEqual(len(g.bound_params), 1) 221 | 222 | def test_can_add_functon_two_args(self): 223 | g = Gremlin().function('one', 'two') 224 | string = str(g) 225 | params = g.bound_params 226 | one = get_dict_key(params, 'one') 227 | two = get_dict_key(params, 'two') 228 | expected = 'g.function({}, {})'.format(one, two) 229 | 230 | self.assertEqual(string, expected) 231 | self.assertEqual(len(g.bound_params), 2) 232 | 233 | def test_can_add_unbound_functon_two_args(self): 234 | g = Gremlin().unbound('function', 'arg', 'two') 235 | string = str(g) 236 | expected = 'g.function(arg, two)' 237 | 238 | self.assertEqual(string, expected) 239 | self.assertEqual(len(g.bound_params), 0) 240 | 241 | def test_can_add_raw_functon_two_args(self): 242 | g = Gremlin().func_raw('function', 'arg', 'two') 243 | string = str(g) 244 | params = g.bound_params 245 | arg = get_dict_key(params, 'arg') 246 | two = get_dict_key(params, 'two') 247 | expected = 'gfunction({}, {})'.format(arg, two) 248 | 249 | self.assertEqual(string, expected) 250 | self.assertEqual(len(g.bound_params), 2) 251 | 252 | def test_can_add_functon_three_args(self): 253 | g = Gremlin().function('one', 'two', 'three') 254 | string = str(g) 255 | params = g.bound_params 256 | one = get_dict_key(params, 'one') 257 | two = get_dict_key(params, 'two') 258 | three = get_dict_key(params, 'three') 259 | expected = 'g.function({}, {}, {})'.format(one, two, three) 260 | 261 | self.assertEqual(string, expected) 262 | self.assertEqual(len(g.bound_params), 3) 263 | 264 | def test_can_add_functon_manually_bind_one_arg(self): 265 | g = Gremlin() 266 | bound = g.bind_param('arg') 267 | 268 | g.function(bound[0]) 269 | 270 | string = str(g) 271 | bind, value = g.bound_params.copy().popitem() 272 | expected = 'g.function(%s)' % bind 273 | 274 | self.assertEqual(string, expected) 275 | self.assertEqual(bind, bound[0]) 276 | self.assertEqual(value, bound[1]) 277 | self.assertEqual(len(g.bound_params), 1) 278 | 279 | def test_can_add_functon_manually_bind_two_args(self): 280 | g = Gremlin() 281 | bound = g.bind_param('arg') 282 | bound2 = g.bind_param('arg2') 283 | 284 | g.function(bound[0], bound2[0]) 285 | 286 | string = str(g) 287 | expected = 'g.function(%s, %s)' % (bound[0], bound2[0]) 288 | 289 | self.assertEqual(string, expected) 290 | self.assertEqual(len(g.bound_params), 2) 291 | 292 | def test_can_add_function_with_arg_before_attribute(self): 293 | g = Gremlin() 294 | 295 | g.function('val').a 296 | 297 | string = str(g) 298 | bind, value = g.bound_params.copy().popitem() 299 | expected = 'g.function(%s).a' % bind 300 | 301 | self.assertEqual(string, expected) 302 | self.assertEqual(len(g.bound_params), 1) 303 | 304 | def test_can_add_function_with_arg_after_attribute(self): 305 | g = Gremlin() 306 | 307 | g.a.function('val') 308 | 309 | string = str(g) 310 | bind, value = g.bound_params.copy().popitem() 311 | expected = 'g.a.function(%s)' % bind 312 | 313 | self.assertEqual(string, expected) 314 | self.assertEqual(len(g.bound_params), 1) 315 | 316 | def test_can_add_function_with_arg_after_attribute_drop_graph_var(self): 317 | g = Gremlin().set_graph_variable('') 318 | 319 | g.a.function('val') 320 | 321 | string = str(g) 322 | bind, value = g.bound_params.copy().popitem() 323 | expected = 'a.function(%s)' % bind 324 | 325 | self.assertEqual(string, expected) 326 | self.assertEqual(len(g.bound_params), 1) 327 | 328 | def test_can_add_function_where_no_params_are_bound(self): 329 | g = Gremlin() 330 | 331 | g.unbound('function', 'val1', 'val2') 332 | 333 | expected = 'g.function(val1, val2)' 334 | 335 | self.assertEqual(str(g), expected) 336 | self.assertEqual(len(g.bound_params), 0) 337 | 338 | def test_can_add_function_where_no_params_are_bound_and_func_with_bound_params(self): 339 | g = Gremlin() 340 | 341 | g.unbound('function', 'val1', 'val2').isbound('hello') 342 | 343 | s = str(g) 344 | params = g.bound_params 345 | hello = get_dict_key(params, 'hello') 346 | expected = 'g.function(val1, val2).isbound(%s)' % hello 347 | 348 | self.assertEqual(s, expected) 349 | self.assertEqual(len(g.bound_params), 1) 350 | 351 | def test_can_add_function_with_closure(self): 352 | g = Gremlin() 353 | 354 | g.condition('x').close('body') 355 | 356 | s = str(g) 357 | params = g.bound_params 358 | x = get_dict_key(params, 'x') 359 | expected = 'g.condition(%s){body}' % x 360 | 361 | self.assertEqual(s, expected) 362 | self.assertEqual(len(params), 1) 363 | 364 | def test_can_add_an_index_with_single_value(self): 365 | g = Gremlin() 366 | 367 | g.function()[1] 368 | 369 | s = str(g) 370 | expected = 'g.function()[1]' 371 | 372 | self.assertEqual(s, expected) 373 | 374 | def test_can_add_an_index_with_range(self): 375 | g = Gremlin() 376 | 377 | g.function()[1:2] 378 | 379 | s = str(g) 380 | expected = 'g.function()[1..2]' 381 | 382 | self.assertEqual(s, expected) 383 | 384 | def test_can_add_an_index_after_closure(self): 385 | g = Gremlin() 386 | 387 | g.condition('x').close('body')[12:44] 388 | 389 | s = str(g) 390 | params = g.bound_params 391 | x = get_dict_key(params, 'x') 392 | expected = 'g.condition(%s){body}[12..44]' % x 393 | 394 | self.assertEqual(s, expected) 395 | self.assertEqual(len(params), 1) 396 | 397 | def test_can_add_raw_after_function(self): 398 | g = Gremlin() 399 | r = '--raw-text--' 400 | 401 | g.function().raw(r) 402 | 403 | s = str(g) 404 | params = g.bound_params 405 | expected = 'g.function()%s' % r 406 | 407 | self.assertEqual(s, expected) 408 | self.assertEqual(len(params), 0) 409 | 410 | def test_can_add_raw_drop_graph_variable(self): 411 | g = Gremlin() 412 | r = '--raw-text--' 413 | 414 | g.raw(r).set_graph_variable('') 415 | 416 | s = str(g) 417 | params = g.bound_params 418 | expected = '%s' % r 419 | 420 | self.assertEqual(s, expected) 421 | self.assertEqual(len(params), 0) 422 | 423 | def test_can_add_raw_between_functions(self): 424 | g = Gremlin() 425 | r = '--raw-text--' 426 | a = 'arg' 427 | 428 | g.function().raw(r).func2(a) 429 | 430 | s = str(g) 431 | params = g.bound_params 432 | arg = get_dict_key(params, a) 433 | expected = 'g.function()%sfunc2(%s)' % (r, arg) 434 | 435 | self.assertEqual(s, expected) 436 | self.assertEqual(len(params), 1) 437 | 438 | def test_can_add_raw_after_closure(self): 439 | g = Gremlin() 440 | r = '--raw-text--' 441 | c = '[[[]]]' 442 | 443 | g.function().close(c).raw(r) 444 | 445 | s = str(g) 446 | params = g.bound_params 447 | expected = 'g.function(){%s}%s' % (c, r) 448 | 449 | self.assertEqual(s, expected) 450 | self.assertEqual(len(params), 0) 451 | 452 | def test_can_add_a_reserved_word_as_apart_of_the_query_as_function_with_args(self): 453 | g = Gremlin(); 454 | init = Function(g, '__init__', 'arg') 455 | 456 | g.add_token(init) 457 | 458 | s = str(g) 459 | params = g.bound_params 460 | arg = get_dict_key(params, 'arg') 461 | expected = 'g.__init__(%s)' % arg 462 | 463 | self.assertEqual(s, expected) 464 | self.assertEqual(len(params), 1) 465 | 466 | def test_can_add_a_reserved_word_as_apart_of_the_query_as_function_with_args_after_other_calls(self): 467 | g = Gremlin(); 468 | args = ['arg', 'LOOK', 'AT'] 469 | init = Function(g, '__init__', args[0]) 470 | 471 | g.look(args[1]).at(args[2]).close('--this--').add_token(init) 472 | 473 | s = str(g) 474 | params = g.bound_params 475 | arg = get_dict_key(params, args[0]) 476 | look = get_dict_key(params, args[1]) 477 | at = get_dict_key(params, args[2]) 478 | expected = 'g.look(%s).at(%s){--this--}.__init__(%s)' % (look, at, arg) 479 | 480 | self.assertEqual(s, expected) 481 | self.assertEqual(len(params), 3) 482 | 483 | def test_can_add_a_reserved_word_as_apart_of_the_query_as_function_without_args(self): 484 | g = Gremlin(); 485 | unbound = ['arg', '2'] 486 | init = UnboudFunction(g, '__init__', *unbound) 487 | 488 | g.add_token(init) 489 | 490 | s = str(g) 491 | params = g.bound_params 492 | expected = 'g.__init__(%s, %s)' % (unbound[0], unbound[1]) 493 | 494 | self.assertEqual(s, expected) 495 | self.assertEqual(len(params), 0) 496 | 497 | def test_can_add_a_reserved_word_as_apart_of_the_query_as_function_without_args_after_other_calls(self): 498 | g = Gremlin(); 499 | unbound = ['arg', '2'] 500 | arg = 'some_arg' 501 | init = UnboudFunction(g, '__init__', *unbound) 502 | 503 | g.someFunc(arg).add_token(init) 504 | 505 | s = str(g) 506 | params = g.bound_params 507 | expected = 'g.someFunc(%s).__init__(%s, %s)' % (get_dict_key(params, arg), unbound[0], unbound[1]) 508 | 509 | self.assertEqual(s, expected) 510 | self.assertEqual(len(params), 1) 511 | 512 | def test_can_copy_gremlin_instance(self): 513 | g = Gremlin('xxxx').out().someThing('value') 514 | gg = g.copy() 515 | 516 | str(g) 517 | str(gg) 518 | 519 | string_g = gremlin_as_string(g) 520 | string_gg = gremlin_as_string(gg) 521 | params_g = g.bound_params 522 | params_gg = gg.bound_params 523 | 524 | self.assertEqual(string_g, string_gg) 525 | self.assertEqual(len(params_g), len(params_gg)) 526 | 527 | def test_can_copy_gremlin_instance_and_reset_one_independently(self): 528 | g = Gremlin('xxxx').out().someThing('value') 529 | gg = g.copy() 530 | g.reset() 531 | str(g) 532 | str(gg) 533 | 534 | string_g = gremlin_as_string(g) 535 | string_gg = gremlin_as_string(gg) 536 | params_g = g.bound_params 537 | params_gg = gg.bound_params 538 | 539 | self.assertNotEqual(string_g, string_gg) 540 | self.assertNotEqual(len(params_g), len(params_gg)) 541 | 542 | def test_can_copy_gremlin_instance_with_predicates(self): 543 | arg = str(random()) 544 | g = Gremlin('xxxx').has(lt(arg)).someThing('value') 545 | gg = g.copy() 546 | 547 | str(g) 548 | str(gg) 549 | 550 | string_g = gremlin_as_string(g) 551 | string_gg = gremlin_as_string(gg) 552 | params_g = g.bound_params 553 | params_gg = gg.bound_params 554 | 555 | self.assertEqual(string_g, string_gg) 556 | self.assertEqual(len(params_g), len(params_gg)) 557 | 558 | def test_can_copy_gremlin_instance_with_predicates_and_reset_one_independently(self): 559 | arg = str(random()) 560 | g = Gremlin('xxxx').has(lt(arg)).someThing('value') 561 | gg = g.copy() 562 | 563 | g.reset() 564 | str(g) 565 | str(gg) 566 | 567 | string_g = gremlin_as_string(g) 568 | string_gg = gremlin_as_string(gg) 569 | params_g = g.bound_params 570 | params_gg = gg.bound_params 571 | 572 | self.assertNotEqual(string_g, string_gg) 573 | self.assertNotEqual(len(params_g), len(params_gg)) 574 | 575 | def test_can_copy_gremlin_instance_but_modify_one_independently(self): 576 | g = Gremlin('xxxx').out().someThing('value') 577 | gg = g.copy() 578 | 579 | g.some().other.Mehtod().Chain('with', 'arguments') 580 | 581 | string_g = str(g) 582 | string_gg = str(gg) 583 | params_g = g.bound_params 584 | params_gg = gg.bound_params 585 | 586 | self.assertNotEqual(string_g, string_gg) 587 | self.assertNotEqual(len(params_g), len(params_gg)) 588 | 589 | def test_can_copy_gremlin_instance_with_predicates_but_modify_one_independently(self): 590 | arg = str(random()) 591 | g = Gremlin('xxxx').has(lt(arg)).someThing('value') 592 | gg = g.copy() 593 | 594 | g.some().other.Mehtod().Chain('with', 'arguments') 595 | 596 | str(g) 597 | str(gg) 598 | 599 | string_g = gremlin_as_string(g) 600 | string_gg = gremlin_as_string(gg) 601 | params_g = g.bound_params 602 | params_gg = gg.bound_params 603 | 604 | self.assertNotEqual(string_g, string_gg) 605 | self.assertNotEqual(len(params_g), len(params_gg)) 606 | 607 | def test_can_copy_gremlin_instance_but_modify_copy_independently(self): 608 | g = Gremlin('xxxx').out().someThing('value') 609 | gg = g.copy() 610 | 611 | gg.some().other.Method().Chain('with', 'arguments') 612 | 613 | string_g = str(g) 614 | string_gg = str(gg) 615 | expected = "xxxx.out().someThing('value').some().other.Method().Chain('with', 'arguments')" 616 | gg_string = gremlin_as_string(gg) 617 | params_g = g.bound_params 618 | params_gg = gg.bound_params 619 | 620 | self.assertNotEqual(string_g, string_gg) 621 | self.assertEqual(expected, gg_string) 622 | self.assertNotEqual(len(params_g), len(params_gg)) 623 | 624 | 625 | class GremlinInjectionTests(unittest.TestCase): 626 | 627 | def test_can_nest_gremlin(self): 628 | g = Gremlin() 629 | n = Gremlin() 630 | 631 | g.nest(n.nested()) 632 | 633 | expected = 'g.nest(g.nested())' 634 | string = str(g) 635 | params = g.bound_params 636 | 637 | self.assertEqual(expected, string) 638 | self.assertEqual(len(params), 0) 639 | 640 | def test_can_copy_nested_gremlin(self): 641 | g = Gremlin() 642 | n = Gremlin() 643 | 644 | g.nest(n.nested()) 645 | 646 | gg = g.copy() 647 | 648 | str(g) 649 | str(gg) 650 | 651 | str_g = gremlin_as_string(g) 652 | str_gg = gremlin_as_string(gg) 653 | params_g = g.bound_params 654 | params_gg = gg.bound_params 655 | 656 | self.assertEqual(str_g, str_gg) 657 | self.assertEqual(len(params_g), len(params_gg)) 658 | 659 | def test_can_nest_double_nest_gremlin(self): 660 | g = Gremlin() 661 | n = Gremlin() 662 | d = Gremlin() 663 | 664 | g.nest(n.nested(d.deep())) 665 | 666 | expected = 'g.nest(g.nested(g.deep()))' 667 | string = str(g) 668 | params = g.bound_params 669 | 670 | self.assertEqual(expected, string) 671 | self.assertEqual(len(params), 0) 672 | 673 | def test_can_copy_double_nested_gremlin(self): 674 | g = Gremlin() 675 | n = Gremlin() 676 | d = Gremlin() 677 | 678 | g.nest(n.nested(d.deep())) 679 | 680 | gg = g.copy() 681 | 682 | str(g) 683 | str(gg) 684 | 685 | str_g = gremlin_as_string(g) 686 | str_gg = gremlin_as_string(gg) 687 | params_g = g.bound_params 688 | params_gg = gg.bound_params 689 | 690 | self.assertEqual(str_g, str_gg) 691 | self.assertEqual(len(params_g), len(params_gg)) 692 | 693 | def test_can_nest_with_bound_params(self): 694 | g = Gremlin() 695 | d = {'name': 'parent'} 696 | n = Gremlin() 697 | p = {'prop': 'child'} 698 | 699 | n.set_graph_variable('').setSubProp('prop', p['prop']) 700 | g.function('name', d['name']).nest(n) 701 | 702 | string = str(g) 703 | params = g.bound_params 704 | name_field = get_dict_key(params, 'name') 705 | name = get_dict_key(params, 'parent') 706 | prop_field = get_dict_key(params, 'prop') 707 | child = get_dict_key(params, 'child') 708 | expected = 'g.function(%s, %s).nest(setSubProp(%s, %s))' % (name_field, 709 | name, prop_field, child) 710 | 711 | self.assertEqual(expected, string) 712 | self.assertEqual(len(params), 4) 713 | 714 | def test_can_nest_with_unbound_params_of_same_value(self): 715 | g = Gremlin() 716 | n = Gremlin() 717 | d = {'name': str(random()), 'age': str(random())} 718 | 719 | n.set_graph_variable('__').has('name', d['name']) 720 | n.func('age', d['age']) 721 | g.function('name', d['name']).nest(n) 722 | 723 | string = str(g) 724 | params = g.bound_params 725 | name_field = get_dict_key(params, 'name') 726 | name = get_dict_key(params, d['name']) 727 | age = get_dict_key(params, d['age']) 728 | expected = ("g.function({}, {})" 729 | ".nest(__.has({}, {}).age({}))").format(name_field, 730 | name, name_field, name, age) 731 | 732 | self.assertEqual(3, len(params)) 733 | self.assertEqual(expected, string) 734 | 735 | def test_can_double_nest_with_unbound_params_of_same_value(self): 736 | g = Gremlin() 737 | n = Gremlin() 738 | nn = Gremlin() 739 | d = { 740 | 'name_val': str(random()), 741 | 'age_val': str(random()), 742 | } 743 | 744 | nn.set_graph_variable('_').func('name', d['name_val']) 745 | n.set_graph_variable('__').has('name', d['name_val']) 746 | n.func('age', d['age_val']).nest(nn) 747 | g.function('name', d['name_val']).nest(n) 748 | 749 | string = str(g) 750 | params = g.bound_params 751 | name_field = get_dict_key(params, 'name') 752 | name_val = get_dict_key(params, d['name_val']) 753 | age_val = get_dict_key(params, d['age_val']) 754 | expected = ("g.function({}, {})" 755 | ".nest(__.has({}, {}).age({})" 756 | ".nest(_.name({})))").format(name_field, name_val, 757 | name_field, name_val, age_val, name_val) 758 | 759 | self.assertEqual(3, len(params)) 760 | self.assertEqual(expected, string) 761 | 762 | 763 | class PredicateTests(unittest.TestCase): 764 | 765 | def test_can_pass_single_predicate_to_function(self): 766 | arg = str(random()) 767 | g = Gremlin() 768 | g.function(lt(arg).out()) 769 | 770 | string = str(g) 771 | params = g.bound_params 772 | argument = get_dict_key(params, arg) 773 | expected = 'g.function(lt(%s).out())' % (argument) 774 | 775 | self.assertEqual(expected, string) 776 | 777 | def test_can_pass_bound_param_to_predicate(self): 778 | g = Gremlin() 779 | val = 'test'+ str(random()) 780 | param = 'param' 781 | bound = g.bind_param(val, param) 782 | pred = lt(bound[0], gremlin=g) 783 | expected = 'lt({})'.format(param) 784 | actual = str(pred) 785 | 786 | self.assertEqual(expected, actual) 787 | 788 | def test_can_pass_bound_param_to_embedded_predicate(self): 789 | g = Gremlin() 790 | val = 'test'+ str(random()) 791 | param = 'param' 792 | bound = g.bind_param(val, param) 793 | pred = lt(bound[0], gremlin=g) 794 | g.function(pred) 795 | expected = 'g.function(lt({}))'.format(param) 796 | actual = str(g) 797 | 798 | self.assertEqual(expected, actual) 799 | 800 | def test_can_pass_two_predicates_to_function(self): 801 | arg = str(random()) 802 | arg2 = '222_' + str(random()) 803 | g = Gremlin() 804 | g.function(lt(arg).out(), lt(arg2)) 805 | 806 | string = str(g) 807 | params = g.bound_params 808 | argument = get_dict_key(params, arg) 809 | argument2 = get_dict_key(params, arg2) 810 | expected = 'g.function(lt(%s).out(), lt(%s))' % (argument, argument2) 811 | 812 | self.assertEqual(expected, string) 813 | 814 | def test_can_nest_predicates(self): 815 | arg = str(random()) 816 | arg2 = '222_' + str(random()) 817 | g = Gremlin() 818 | g.function(lt(arg).out(lt(arg2))) 819 | 820 | string = str(g) 821 | params = g.bound_params 822 | argument = get_dict_key(params, arg) 823 | argument2 = get_dict_key(params, arg2) 824 | expected = 'g.function(lt(%s).out(lt(%s)))' % (argument, argument2) 825 | 826 | self.assertEqual(expected, string) 827 | 828 | def test_can_create_dynamic_predicate(self): 829 | ran = 'someFunc'+ str(random()) 830 | pred = _(ran) 831 | g = Gremlin() 832 | g.function(pred) 833 | 834 | expected = 'g.function({}())'.format(ran) 835 | s = str(g) 836 | 837 | self.assertEqual(s, expected) 838 | 839 | def test_can_nest_dynamically_created_predicates(self): 840 | ran = 'someFunc'+ str(random()) 841 | ran2 = 'outer'+ str(random()) 842 | pred = _(ran) 843 | pred2 = _(ran2, pred) 844 | g = Gremlin() 845 | g.function(pred2) 846 | 847 | expected = 'g.function({}({}()))'.format(ran2, ran) 848 | s = str(g) 849 | 850 | self.assertEqual(s, expected) 851 | 852 | def test_can_start_anon_traversal(self): 853 | a = Anon().call() 854 | g = Gremlin().subAnon(a) 855 | 856 | string = str(g) 857 | params = g.bound_params 858 | expected = 'g.subAnon(__.call())' 859 | 860 | self.assertEqual(0, len(params)) 861 | self.assertEqual(expected, string) 862 | 863 | def test_can_handle_problematic_predicates_in_diff_contexes(self): 864 | g = Gremlin().IN().AND(AS().IS().NOT()) 865 | string = str(g) 866 | expected = 'g.in().and(as().is().not())' 867 | params = g.bound_params 868 | 869 | self.assertEqual(0, len(params)) 870 | self.assertEqual(expected, string) 871 | 872 | def test_can_handle_problematic_predicates_in_diff_contexes_and_copy_it(self): 873 | g = Gremlin().IN().AND(AS().IS().NOT('some', 'param')) 874 | gg = g.copy() 875 | 876 | str(g) 877 | str(gg) 878 | 879 | g_string = gremlin_as_string(g) 880 | gg_string = gremlin_as_string(gg) 881 | params_g = g.bound_params 882 | params_gg = gg.bound_params 883 | 884 | self.assertEqual(len(params_g), len(params_gg)) 885 | self.assertEqual(g_string, gg_string) 886 | 887 | 888 | if __name__ == '__main__': 889 | unittest.main() 890 | -------------------------------------------------------------------------------- /gremlinpy/tests/statement.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from gremlinpy.statement import Statement, Conditional, GetEdge 3 | from gremlinpy.gremlin import Gremlin 4 | 5 | 6 | def get_dict_key(dict, value): 7 | for k, v in dict.items(): 8 | if v == value: 9 | return k 10 | 11 | return None 12 | 13 | 14 | class StatementTests(unittest.TestCase): 15 | 16 | def test_can_apply_statement(self): 17 | 18 | class statement(Statement): 19 | 20 | def build(self): 21 | g = self.gremlin 22 | 23 | g.random().statement() 24 | 25 | s = statement() 26 | g = Gremlin() 27 | 28 | g.apply_statement(s) 29 | 30 | string = str(g) 31 | expected = 'g.random().statement()' 32 | 33 | self.assertTrue(string == expected) 34 | 35 | def test_can_apply_statement_with_args(self): 36 | arg = 'statement_arg' 37 | 38 | class statement(Statement): 39 | 40 | def build(self): 41 | g = self.gremlin 42 | 43 | g.statementFunction(arg) 44 | 45 | s = statement() 46 | g = Gremlin() 47 | 48 | g.apply_statement(s) 49 | 50 | string = str(g) 51 | params = g.bound_params 52 | expected = 'g.statementFunction(%s)' % get_dict_key(params, arg) 53 | 54 | self.assertTrue(string == expected) 55 | 56 | def test_can_apply_two_statements_with_args(self): 57 | one = 'one' 58 | two = 'two' 59 | 60 | class First(Statement): 61 | 62 | def build(self): 63 | g = self.gremlin 64 | 65 | g.first(one) 66 | 67 | class Second(Statement): 68 | 69 | def build(self): 70 | g = self.gremlin 71 | 72 | g.second(two) 73 | 74 | f = First() 75 | s = Second() 76 | g = Gremlin() 77 | 78 | g.apply_statement(f).apply_statement(s) 79 | 80 | string = str(g) 81 | params = g.bound_params 82 | arg_1 = get_dict_key(params, one) 83 | arg_2 = get_dict_key(params, two) 84 | expected = 'g.first(%s).second(%s)' % (arg_1, arg_2) 85 | 86 | self.assertTrue(string == expected) 87 | 88 | 89 | class NestedStatementTests(unittest.TestCase): 90 | 91 | def test_can_nest_one_statement_into_another_via_unbound(self): 92 | 93 | class Outer(Statement): 94 | 95 | def build(self): 96 | g = self.gremlin 97 | i = Inner() 98 | 99 | g.unbound('outer', i) 100 | 101 | class Inner(Statement): 102 | 103 | def build(self): 104 | g = self.gremlin 105 | g.inner_statement 106 | 107 | g = Gremlin() 108 | o = Outer() 109 | 110 | g.apply_statement(o) 111 | 112 | string = str(g) 113 | params = g.bound_params 114 | expected = 'g.outer(g.inner_statement)' 115 | 116 | self.assertTrue(string == expected) 117 | self.assertTrue(len(params) == 0) 118 | 119 | def test_can_nest_one_statement_into_another_via_raw(self): 120 | 121 | class Outer(Statement): 122 | 123 | def build(self): 124 | g = self.gremlin 125 | i = Inner() 126 | 127 | g.will_be_raw.raw(i) 128 | 129 | class Inner(Statement): 130 | 131 | def build(self): 132 | g = self.gremlin 133 | g.inner_statement 134 | 135 | g = Gremlin() 136 | o = Outer() 137 | 138 | g.apply_statement(o) 139 | 140 | string = str(g) 141 | params = g.bound_params 142 | expected = 'g.will_be_rawg.inner_statement' 143 | 144 | self.assertTrue(string == expected) 145 | self.assertTrue(len(params) == 0) 146 | 147 | 148 | class PackagedStatementTests(unittest.TestCase): 149 | 150 | def test_can_make_conditional_statement(self): 151 | g = Gremlin() 152 | c = Conditional() 153 | cond = '1 == 2' 154 | body = 'nope' 155 | else_c = 'one doesnt equal two' 156 | 157 | c.set_if(cond, body) 158 | c.set_else(else_c) 159 | g.apply_statement(c) 160 | 161 | string = str(g) 162 | params = g.bound_params 163 | expected = 'if(%s){%s}else{%s}' % (cond, body, else_c) 164 | 165 | self.assertTrue(string == expected) 166 | self.assertTrue(len(params) == 0) 167 | 168 | def test_can_make_conditional_statement_with_elseif(self): 169 | g = Gremlin() 170 | c = Conditional() 171 | cond = '1 == 2' 172 | body = 'nope' 173 | else_i = '2 == 2' 174 | else_b = 'two does equal 2' 175 | else_c = 'one doesnt equal two' 176 | 177 | c.set_if(cond, body) 178 | c.set_elif(else_i, else_b) 179 | c.set_else(else_c) 180 | g.apply_statement(c) 181 | 182 | string = str(g) 183 | params = g.bound_params 184 | expected = 'if(%s){%s}elseif(%s){%s}else{%s}' % (cond, 185 | body, 186 | else_i, 187 | else_b, else_c) 188 | 189 | self.assertTrue(string == expected) 190 | self.assertTrue(len(params) == 0) 191 | 192 | def test_can_make_conditional_statement_with_two_elseif(self): 193 | g = Gremlin() 194 | c = Conditional() 195 | cond = '1 == 2' 196 | body = 'nope' 197 | else_i = '2 == 2' 198 | else_b = 'two does equal 2' 199 | else_i2 = '20 == 20' 200 | else_b2 = 'already caught above' 201 | else_c = 'one doesnt equal two' 202 | 203 | c.set_if(cond, body) 204 | c.set_elif(else_i, else_b) 205 | c.set_elif(else_i2, else_b2) 206 | c.set_else(else_c) 207 | g.apply_statement(c) 208 | 209 | string = str(g) 210 | params = g.bound_params 211 | exp = (cond, body, else_i, else_b, else_i2, else_b2, else_c) 212 | expected = 'if(%s){%s}elseif(%s){%s}elseif(%s){%s}else{%s}' % exp 213 | 214 | self.assertTrue(string == expected) 215 | self.assertTrue(len(params) == 0) 216 | 217 | def test_can_make_get_edge_statement_both_direction(self): 218 | out_id = 1 219 | in_id = 9 220 | label = 'knows' 221 | g = Gremlin() 222 | e = GetEdge(out_id, in_id, label) 223 | 224 | g.apply_statement(e) 225 | 226 | string = str(g) 227 | params = g.bound_params 228 | oid = get_dict_key(params, out_id) 229 | iid = get_dict_key(params, in_id) 230 | label_b = get_dict_key(params, label) 231 | as_b = e.bound_entity 232 | bound = (oid, label_b, as_b, iid, as_b) 233 | expected = 'g.V(%s).bothE(%s).as(%s).inV().hasId(%s).select(%s)' % \ 234 | bound 235 | 236 | self.assertEqual(expected, string) 237 | 238 | def test_can_make_get_edge_statement_in_direction(self): 239 | out_id = 1 240 | in_id = 9 241 | label = 'knows' 242 | g = Gremlin() 243 | e = GetEdge(out_id, in_id, label, 'in') 244 | 245 | g.apply_statement(e) 246 | 247 | string = str(g) 248 | params = g.bound_params 249 | oid = get_dict_key(params, out_id) 250 | iid = get_dict_key(params, in_id) 251 | label_b = get_dict_key(params, label) 252 | as_b = e.bound_entity 253 | bound = (oid, label_b, as_b, iid, as_b) 254 | expected = 'g.V(%s).inE(%s).as(%s).outV().hasId(%s).select(%s)' % \ 255 | bound 256 | 257 | self.assertEqual(expected, string) 258 | 259 | def test_can_make_get_edge_statement_out_direction(self): 260 | out_id = 1 261 | in_id = 9 262 | label = 'knows' 263 | g = Gremlin() 264 | e = GetEdge(out_id, in_id, label, 'out') 265 | 266 | g.apply_statement(e) 267 | 268 | string = str(g) 269 | params = g.bound_params 270 | oid = get_dict_key(params, out_id) 271 | iid = get_dict_key(params, in_id) 272 | label_b = get_dict_key(params, label) 273 | as_b = e.bound_entity 274 | bound = (oid, label_b, as_b, iid, as_b) 275 | expected = 'g.V(%s).outE(%s).as(%s).inV().hasId(%s).select(%s)' % \ 276 | bound 277 | 278 | self.assertEqual(expected, string) 279 | 280 | 281 | if __name__ == '__main__': 282 | unittest.main() 283 | -------------------------------------------------------------------------------- /gremlinpy/version.py: -------------------------------------------------------------------------------- 1 | __version_info__ = ('3', '8', '0', '1') 2 | __version__ = '.'.join(__version_info__) 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Gremlinpy 3 | ------ 4 | 5 | Gremlinpy is a graph abstraction layer that converts Python objects 6 | to Gremlin/Groovy strings. 7 | """ 8 | from setuptools import setup, find_packages 9 | 10 | # get the version information 11 | exec(open('gremlinpy/version.py').read()) 12 | 13 | setup( 14 | name = 'gremlinpy', 15 | packages = find_packages(), 16 | version = __version__, 17 | description = 'Python GAL for Gremlin/Groovy syntax', 18 | url = 'https://github.com/emehrkay/gremlinpy', 19 | author = 'Mark Henderson', 20 | author_email = 'emehrkay@gmail.com', 21 | long_description = __doc__, 22 | install_requires = [ 23 | 'six', 24 | ], 25 | classifiers = [ 26 | 'License :: OSI Approved :: MIT License', 27 | 'Development Status :: 3 - Alpha', 28 | 'Programming Language :: Python :: 2.7', 29 | 'Environment :: Web Environment', 30 | 'Topic :: Database', 31 | 'Topic :: Database :: Front-Ends', 32 | 'Topic :: Internet :: WWW/HTTP', 33 | 'Topic :: Software Development', 34 | 'Topic :: Software Development :: Libraries :: Python Modules', 35 | 'Topic :: System :: Distributed Computing', 36 | 'Intended Audience :: Developers', 37 | 'Operating System :: POSIX :: Linux', 38 | 'Operating System :: MacOS', 39 | 'Operating System :: MacOS :: MacOS X', 40 | ], 41 | test_suite = 'gremlinpy.tests', 42 | ) 43 | --------------------------------------------------------------------------------