Skip to content

Commit

Permalink
Bump version to 1.0.0,
Browse files Browse the repository at this point in the history
- added support for csv files
- reference relationship no longer need 'model' key
- reference column no longer need 'model' key
  • Loading branch information
jedymatt committed Aug 27, 2021
1 parent 58b9c87 commit 956fec6
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 433 deletions.
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ install:
- pip install .
- pip install pytest
- pip install codecov
before_script:
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- chmod +x ./cc-test-reporter
# before_script:
# - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
# - chmod +x ./cc-test-reporter
script:
- pytest tests
# - pytest tests
- coverage run --source=sqlalchemyseed -m pytest tests
after_success:
# - bash <(curl -s https://codecov.io/bash)
Expand Down
24 changes: 4 additions & 20 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,14 @@
# TODO

## v1.0.0
## v1.x

- [ ] Add example of input in csv file in README.md
- [x] Support load entities from csv
- [ ] Customize prefix in seeder (default=`!`)
- [x] Customize prefix in seeder (default=`!`)
- [x] Customize prefix in validator (default=`!`)
- [ ] relationship entity no longer required `model` key since the program will search it for you, but can also be
- [x] relationship entity no longer required `model` key since the program will search it for you, but can also be
overridden by providing a model data instead as it saves performance time

# In Progress

- Customize prefix
- affected by changes: validator and seeder

- reference relationship attribute no longer need to add `model` key
- affected by changes: validator and seeder
- solution to get model class from a relationship attribute example:
- `models.Employee.company.mapper.class_`
- reference foreign key attribute no longer need `model` key
- affected by change: validator and seeder
- searching for possible solution by get model from foreign key attribute:
- by getting the mappers, we can check its classes by searching in `list(models.Employee.registry.mappers)`,
first, get the table name of the attribute with foreign
key `str(list(Employee.company_id.foreign_keys)[0].column.table.name)`, then use it to iterate through the
mappers by looking for its match table name `table_name == str(mapper.class_.__tablename__)`
- [ ] Add test case for overriding default reference prefix

## Tentative Features

Expand Down
4 changes: 2 additions & 2 deletions sqlalchemyseed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
"""

from .seeder import HybridSeeder
from ._future.seeder import Seeder
from .seeder import Seeder
from .loader import load_entities_from_json
from .loader import load_entities_from_yaml
from .loader import load_entities_from_csv

__version__ = '1.0.0.dev1'
__version__ = '1.0.0'

if __name__ == '__main__':
pass
188 changes: 0 additions & 188 deletions sqlalchemyseed/_future/seeder.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,191 +21,3 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

from typing import NamedTuple

import sqlalchemy
from sqlalchemy.orm.relationships import RelationshipProperty

from sqlalchemyseed import class_registry
from sqlalchemyseed import validator


class Entity(NamedTuple):
instance: object
attr_name: str

@property
def cls_attribute(self):
return getattr(self.instance.__class__, self.attr_name)

@property
def ins_attribute(self):
return getattr(self.instance, self.attr_name)

@ins_attribute.setter
def ins_attribute(self, value):
setattr(self.instance, self.attr_name, value)


# def instantiate_class(class_, filtered_kwargs: dict, key: validator.Key, session: sqlalchemy.orm.Session = None):
# if key is validator.Key.data():
# return class_(**filtered_kwargs)
#
# if key is validator.Key.filter() and session is not None:
# return session.query(class_).filter_by(**filtered_kwargs).one()


def filter_kwargs(kwargs: dict, class_, ref_prefix):
return {
k: v for k, v in kwargs.items()
if not str(k).startswith(ref_prefix) and not isinstance(getattr(class_, str(k)).property, RelationshipProperty)
}


def set_parent_attr_value(instance, parent: Entity):
if isinstance(parent.cls_attribute.property, RelationshipProperty):
if parent.cls_attribute.property.uselist is True:
parent.ins_attribute.append(instance)
else:
parent.ins_attribute = instance


def iter_ref_attr(attrs, ref_prefix):
for attr_name, value in attrs.items():
if str(attr_name).startswith(ref_prefix):
# remove prefix of attr_name
yield str(attr_name)[len(ref_prefix):], value


class Seeder:
__model_key = validator.Key.model()
__data_key = validator.Key.data()

def __init__(self, session: sqlalchemy.orm.Session = None, ref_prefix="!"):
self.session = session
self._class_registry = class_registry.ClassRegistry()
self._instances = []
self.ref_prefix = ref_prefix

@property
def instances(self):
return tuple(self._instances)

# def get_model_class(self, entity, parent: Entity):
# model_label = self.__model_key.label
# if model_label in entity:
# class_path = entity[model_label]
# return self._class_registry.register_class(class_path)
# # parent is not None
# if isinstance(parent.attribute.property, RelationshipProperty):
# return parent.attribute.mapper.class_
# else: # parent.attribute is instance of ColumnProperty
# table_name = parent.attribute.foreign_keys[0].table.name
# class_ = next(
# (mapper.class_
# for mapper in parent.instance.__class__.registry.mappers
# if mapper.class_.__tablename__ == table_name),
# errors.ClassNotFoundError(
# "A class with table name '{}' is not found in the mappers".format(table_name)),
# )
# return class_

def get_model_class(self, entity, parent: Entity):
if self.__model_key in entity:
return self._class_registry.register_class(entity[self.__model_key])
# parent is not None
if isinstance(parent.cls_attribute.property, RelationshipProperty):
return parent.cls_attribute.mapper.class_

def seed(self, entities, add_to_session=True):
validator.SchemaValidator.validate(
entities, ref_prefix=self.ref_prefix)

self._pre_seed(entities)

if add_to_session:
self.session.add_all(self.instances)

def _pre_seed(self, entity, parent: Entity = None):
if isinstance(entity, dict):
self._seed(entity, parent)
else: # is list
for item in entity:
self._pre_seed(item, parent)

def _seed(self, entity, parent: Entity = None):
class_ = self.get_model_class(entity, parent)
# source_key: validator.Key = next(
# (sk for sk in self.__source_keys if sk.label in entity), None)
# source_data = entity[source_key.label]

kwargs = entity[self.__data_key]

# kwargs is list
if isinstance(kwargs, list):
for kwargs_ in kwargs:
instance = self._setup_instance(class_, kwargs_, parent)
self._seed_children(instance, kwargs_)
return

# kwargs is dict
# instantiate object
instance = self._setup_instance(class_, kwargs, parent)
self._seed_children(instance, kwargs)

def _seed_children(self, instance, kwargs):
for attr_name, value in iter_ref_attr(kwargs, self.ref_prefix):
self._pre_seed(entity=value, parent=Entity(instance, attr_name))

def _setup_instance(self, class_, kwargs: dict, parent: Entity):
instance = class_(**filter_kwargs(kwargs, class_, self.ref_prefix))
if parent is not None:
set_parent_attr_value(instance, parent)
else:
self._instances.append(instance)
return instance

# def instantiate_class(self, class_, kwargs: dict, key: validator.Key):
# filtered_kwargs = {
# k: v
# for k, v in kwargs.items()
# if not k.startswith("!")
# and not isinstance(getattr(class_, k), RelationshipProperty)
# }
#
# if key is validator.Key.data():
# return class_(**filtered_kwargs)
#
# if key is validator.Key.filter() and self.session is not None:
# return self.session.query(class_).filter_by(**filtered_kwargs).one()

# class HybridSeeder:
# __model_key = validator.Key.model()
# __source_keys = [validator.Key.data(), validator.Key.filter()]
#
# def __init__(self, session: sqlalchemy.orm.Session, ref_prefix):
# self.session = session
# self._class_registry = class_registry.ClassRegistry()
# self._instances = []
# self.ref_prefix = ref_prefix
#
# @property
# def instances(self):
# return tuple(self._instances)
#
# def seed(self, entities):
# validator.SchemaValidator.validate(
# entities, ref_prefix=self.ref_prefix)
#
# self._pre_seed(entities)
#
# def _pre_seed(self, entity, parent=None):
# if isinstance(entity, dict):
# self._seed(entity, parent)
# else: # is list
# for item in entity:
# self._pre_seed(item, parent)
#
# def _seed(self, entity, parent):
# pass
12 changes: 10 additions & 2 deletions sqlalchemyseed/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@ class ClassNotFoundError(Exception):
pass


class MissingRequiredKeyError(Exception):
class MissingKeyError(Exception):
"""Raised when a required key is missing"""
pass


class MaxLengthExceededError(Exception):
"""Raised when maximum length of data exceeded"""
pass


class InvalidDataTypeError(Exception):
class InvalidTypeError(Exception):
"""Raised when a type of data is not accepted"""
pass


class EmptyDataError(Exception):
"""Raised when data is empty"""
pass


class InvalidKeyError(Exception):
"""Raised when an invalid key is invoked"""
pass
Loading

0 comments on commit 956fec6

Please sign in to comment.