Skip to content

Commit

Permalink
Merge pull request #6 from nebula-contrib/2-framework-agnostic
Browse files Browse the repository at this point in the history
2 framework agnostic
  • Loading branch information
SwordElucidator authored Sep 14, 2022
2 parents 7021143 + a805a55 commit 6d6c782
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 36 deletions.
42 changes: 37 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The goals of this project include providing an object-oriented description of th

This project is based on the official package nebula-python https://github.com/vesoft-inc/nebula-python.

At present, the project has just started and is not stable yet, the code changes rapidly and the method is unstable.
At present, the project is not stable yet, the code changes rapidly and the methods are unstable.
If you have any ideas for improving this project, you are very welcome to contact me via issue or email.

## Requirements
Expand Down Expand Up @@ -150,7 +150,8 @@ class LimitedCharacter(models.VertexModel):
* An EdgeModel is used to define a nebula edge. But note that there will be no subclasses for edge model since we don't need it.

### Migrations
use `make_migrations` and `migrate` to synchronize the schema to current space.
Use `make_migrations` and `migrate` to synchronize the schema to current space.
Please note that only final consistency on DB schema is currently supported.

```python
from nebula_carina.models.migrations import make_migrations, migrate
Expand All @@ -162,6 +163,12 @@ make_migrations()
migrate(make_migrations())
```

#### Django
If you are using Django, you can do the migration by the following command:
```
python manage.py nebulamigrate
```

### Data Model Method
```python
from example.models import VirtualCharacter, Figure, Source, LimitedCharacter, Love, Support
Expand Down Expand Up @@ -281,16 +288,41 @@ async def what_a_complex_human_relation(character_id: str):

#### Django
Basically things are the same as in fastapi except that you have to use `.dict()` method to serialize a model before using it in response.
Match method would be harder to use. A wrapper that support `.dict()` method will be implemented in 0.3.0.
In django, you can use `ModelBuilder.serialized_match` method to directly get the serialized result of match method.
```python
from example.models import VirtualCharacter
from django.http import JsonResponse
from nebula_carina.models.models import EdgeModel
from nebula_carina.ngql.query.conditions import Q
from nebula_carina.models.model_builder import ModelBuilder

def some_view(request, character_id: str):
vr = VirtualCharacter.objects.get(character_id)
# make sure that use .dict() function to serialize the result
return JsonResponse(vr.dict())


def what_a_complex_human_relation(request, character_id: str):
return JsonResponse(ModelBuilder.serialized_match(
'(v)-[e:love]->(v2)-[e2:love]->(v3)', {
'v': VirtualCharacter, 'e': EdgeModel, 'v2': VirtualCharacter,
'e2': EdgeModel, 'v3': VirtualCharacter
},
condition=Q(v__id=character_id),
), safe=False) # returns a list of dict with keys {v, e, v2, e2, v3}


# same method as the previous one if you would like to access the models
def what_a_complex_human_relation_another(request, character_id: str):
return JsonResponse([
single_match_result.dict() for single_match_result in ModelBuilder.match(
'(v)-[e:love]->(v2)-[e2:love]->(v3)', {
'v': VirtualCharacter, 'e': EdgeModel, 'v2': VirtualCharacter,
'e2': EdgeModel, 'v3': VirtualCharacter
},
condition=Q(v__id=character_id),
)
], safe=False) # returns a list of dict with keys {v, e, v2, e2, v3}
```

#### Flask
Expand All @@ -306,5 +338,5 @@ Flask usage is quite similar to the Django usage. Basically use `.dict()` functi
- [ ] Default values for schema models
- [ ] Generic Vertex Model
- [x] Basic Django Support
- [ ] Django management.py
- [ ] Match Result Wrapper
- [x] Django management.py
- [x] Match Result Wrapper
1 change: 0 additions & 1 deletion example/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from datetime import datetime
from typing import Optional

from nebula_carina.models import models
from nebula_carina.models.fields import create_nebula_field as _
Expand Down
34 changes: 17 additions & 17 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@

@app.get("/")
async def root():
return ModelBuilder.match(
'(v)-[e:love]->(v2)-[e2:love]->(v3)', {
'v': VirtualCharacter, 'e': EdgeModel, 'v2': VirtualCharacter,
'e2': EdgeModel, 'v3': VirtualCharacter
},
condition=Q(v__id="char_test1"),
)
# run_ngql('SHOW SPACES;')
# print(show_spaces())
# print(use_space('main'))
Expand Down Expand Up @@ -112,20 +105,27 @@ async def root():
# ).save()
# EdgeModel(src_vid='char_test1', dst_vid='char_test2', ranking=0, edge_type=Love(way='gun', times=40)).save()
# return EdgeModel.objects.find_between('char_test1', 'char_test2')
# character1 = VirtualCharacter.objects.get('char_test1')
character1 = VirtualCharacter.objects.get('char_test1')
# LocalSession().session.release()
# character2 = VirtualCharacter.objects.get('char_test2')
# character1.get_out_edges(Love)
# character2.get_reverse_edges(Love)
# character1.get_out_edge_and_destinations(Love, VirtualCharacter)
# character2.get_reverse_edge_and_sources(Love, VirtualCharacter)
# return VirtualCharacter.objects.find_destinations('char_test1', Love)
# return VirtualCharacter.objects.find_sources('char_test2', Love, distinct=False, limit=Limit(1))
# return character2.get_sources(Love, VirtualCharacter)
# return character1.get_destinations(Love, VirtualCharacter)
character2 = VirtualCharacter.objects.get('char_test2')
character1.get_out_edges(Love)
character2.get_reverse_edges(Love)
character1.get_out_edge_and_destinations(Love, VirtualCharacter)
character2.get_reverse_edge_and_sources(Love, VirtualCharacter)
VirtualCharacter.objects.find_destinations('char_test1', Love)
VirtualCharacter.objects.find_sources('char_test2', Love, distinct=False, limit=Limit(1))
character2.get_sources(Love, VirtualCharacter)
character1.get_destinations(Love, VirtualCharacter)
# return character1
# return rst
# return ModelBuilder.match(
# '(v)-[e:love]->(v2)', {'v': VirtualCharacter, 'e': EdgeModel, 'v2': VirtualCharacter},
# condition=Q(v__id__in=[112, 113]),
# )
# EdgeModel(src_vid='char_test1', dst_vid='char_test2', ranking=0, edge_type=Love(way='gun', times=40)).save()
return ModelBuilder.serialized_match(
'(v)-[e:love]->(v2)', {
'v': VirtualCharacter, 'e': EdgeModel, 'v2': VirtualCharacter,
},
condition=Q(v__id="char_test1"),
)
Empty file.
Empty file.
22 changes: 22 additions & 0 deletions nebula_carina/management/commands/nebulamigrate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
try:
from django.core.management.base import BaseCommand, CommandError
from nebula_carina.models.migrations import make_migrations, migrate


class Command(BaseCommand):
help = 'Run Nebula Graph migrations'

def handle(self, *args, **options):
migrations = make_migrations()
if not migrations:
self.stdout.write('No Nebula Graph schema change found')
return
self.stdout.write('The following migration NGQLs will be executed: \n\n%s' % ('\n'.join(migrations)))
if input("Type 'yes' to continue, or 'no' to cancel\n") == 'yes':
migrate(make_migrations())
self.stdout.write(self.style.SUCCESS('Nebula migration succeeded.'))
else:
self.stdout.write(self.style.NOTICE('Nebula migration cancelled.'))

except ModuleNotFoundError:
pass
6 changes: 5 additions & 1 deletion nebula_carina/models/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
from nebula3.common.ttypes import Vertex, Edge


class NebulaAdaptor(ABC):
class NebulaConvertableProtocol(ABC):
@classmethod
def from_nebula_db_cls(cls, raw_db_item: Vertex | Edge):
pass

def dict(self, *args, **kwargs):
# this method will be overridden by pydantic
raise NotImplementedError
2 changes: 1 addition & 1 deletion nebula_carina/models/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def get(self, src_vid: str | int, dst_vid: str | int, edge_type):
try:
return self.find_between(src_vid, dst_vid, edge_type)[0]
except IndexError:
raise EdgeDoesNotExistError
raise EdgeDoesNotExistError(src_vid, dst_vid)

def delete(self, edge_definitions: list[EdgeDefinition]):
return run_ngql(delete_edge_ngql(self.model.get_edge_type_and_model()[1].db_name(), edge_definitions))
32 changes: 26 additions & 6 deletions nebula_carina/models/model_builder.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
from typing import Iterable, Type
from nebula_carina.models.abstract import NebulaAdaptor
from typing import Type, Iterable
from nebula_carina.models.abstract import NebulaConvertableProtocol
from nebula_carina.ngql.query.conditions import Condition
from nebula_carina.ngql.query.match import match, OrderBy, Limit


class SingleMatchResult(object):

def __init__(self, result: dict[str, NebulaConvertableProtocol]):
self.__data = result

def dict(self):
return {k: v.dict() for k, v in self.__data.items()}

def __getitem__(self, item):
return self.__data[item]

def __iter__(self):
for key, value in self.__data.items():
yield key, value


class ModelBuilder(object):
@staticmethod
def match(
pattern: str, to_model_dict: dict[str, Type[NebulaAdaptor]], # should be model
pattern: str, to_model_dict: dict[str, Type[NebulaConvertableProtocol]],
*, distinct_field: str = None,
condition: Condition = None, order_by: OrderBy = None, limit: Limit = None
) -> Iterable[dict[str, NebulaAdaptor]]: # should be model
) -> Iterable[SingleMatchResult]: # should be model
output = ', '.join(
("DISTINCT " if key == distinct_field else "") + key
for key in to_model_dict.keys()
)
results = match(pattern, output, condition, order_by, limit)
return (
{
SingleMatchResult({
key: to_model_dict[key].from_nebula_db_cls(value.value)
for key, value in zip(results.keys(), row.values) if key in to_model_dict
} for row in results.rows()
}) for row in results.rows()
)

@staticmethod
def serialized_match(*args, **kwargs):
return [res.dict() for res in ModelBuilder.match(*args, **kwargs)]
8 changes: 4 additions & 4 deletions nebula_carina/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pydantic.fields import ModelField
from pydantic.main import ModelMetaclass

from nebula_carina.models.abstract import NebulaAdaptor
from nebula_carina.models.abstract import NebulaConvertableProtocol
from nebula_carina.models.errors import VertexDoesNotExistError, EdgeDoesNotExistError, DuplicateEdgeTypeNameError
from nebula_carina.models.fields import NebulaFieldInfo
from nebula_carina.models.managers import Manager, BaseVertexManager, BaseEdgeManager
Expand Down Expand Up @@ -176,7 +176,7 @@ def __init__(cls, *args, **kwargs):
val.register(cls)


class NebulaRecordModel(BaseModel, NebulaAdaptor, metaclass=NebulaRecordModelMetaClass):
class NebulaRecordModel(BaseModel, NebulaConvertableProtocol, metaclass=NebulaRecordModelMetaClass):
objects = BaseVertexManager()


Expand Down Expand Up @@ -272,7 +272,7 @@ def get_out_edges(self, edge_type: EdgeTypeModel = None, *, limit: Limit = None)
return EdgeModel.objects.find_by_source(self.vid, edge_type, limit=limit)

def get_out_edge_and_destinations(self, edge_type, dst_vertex_model, *, limit: Limit = None) \
-> Iterable[dict[str, NebulaAdaptor]]:
-> Iterable[dict[str, NebulaConvertableProtocol]]:
if edge_type is None:
edge_type = EdgeTypeModel
return (
Expand All @@ -290,7 +290,7 @@ def get_reverse_edges(self, edge_type: EdgeTypeModel = None, *, limit: Limit = N
return EdgeModel.objects.find_by_destination(self.vid, edge_type, limit=limit)

def get_reverse_edge_and_sources(self, edge_type, src_vertex_model, *, limit: Limit = None) \
-> Iterable[dict[str, NebulaAdaptor]]:
-> Iterable[dict[str, NebulaConvertableProtocol]]:
if edge_type is None:
edge_type = EdgeTypeModel
return (
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name='nebula-carina',
version='0.2.1',
version='0.3.0',
author='Sword Elucidator',
author_email='[email protected]',
url='https://github.com/SwordElucidator/nebula-carina',
Expand Down

0 comments on commit 6d6c782

Please sign in to comment.