def create_input_filter_field(column): from graphene_sqlalchemy.converter import convert_sqlalchemy_type graphene_type = convert_sqlalchemy_type(column.type, column) if graphene_type.__class__ == Field: # TODO enum not supported return None name = str(graphene_type.__class__) + "Filter" if name in _INPUT_FIELDS_CACHE: return Field(_INPUT_FIELDS_CACHE[name]) field_class = Filter fields = OrderedDict() fields["eq"] = Field(graphene_type.__class__, description="Field should be equal to given value") fields["ne"] = Field( graphene_type.__class__, description="Field should not be equal to given value") fields["lt"] = Field(graphene_type.__class__, description="Field should be less then given value") fields["gt"] = Field(graphene_type.__class__, description="Field should be great then given value") fields["like"] = Field( graphene_type.__class__, description="Field should have a pattern of given value", ) # TODO construct operators based on __class__ # TODO complex filter support: OR field_class = type(name, (field_class, graphene.InputObjectType), {}) field_class._meta.fields.update(fields) _INPUT_FIELDS_CACHE[name] = field_class return Field(field_class)
def _generate_default_filters( cls, model, field_filters: 'Dict[str, Union[Iterable[str], Any]]') -> dict: """ Generate GraphQL fields from SQLAlchemy model columns. Args: model: SQLAlchemy model. field_filters: Filters for fields. Returns: GraphQL fields dictionary: field name (key) - field instance (value). """ graphql_filters = {} filters_map = cls.ALLOWED_FILTERS model_fields = { c.name: { 'column': c, 'type': c.type, 'nullable': c.nullable } for c in model.__table__.columns if c.name in field_filters } for field_name, field_object in model_fields.items(): column_type = field_object['type'] expressions = field_filters[field_name] if expressions == cls.ALL: type_class = column_type.__class__ try: expressions = filters_map[type_class].copy() except KeyError: for type_, exprs in filters_map.items(): if issubclass(type_class, type_): expressions = exprs.copy() break else: raise KeyError('Unsupported column type. ' 'Hint: use EXTRA_ALLOWED_FILTERS.') if field_object['nullable']: expressions.append(cls.IS_NULL) field_type = convert_sqlalchemy_type(column_type, field_object['column']) fields = cls._generate_filter_fields(expressions, field_name, field_type, field_object['nullable']) for name, field in fields.items(): graphql_filters[name] = get_field_as(field, graphene.InputField) return graphql_filters
def _generate_default_filters( cls, model, field_filters: 'Dict[str, Union[Iterable[str], Any]]') -> dict: """ Generate GraphQL fields from SQLAlchemy model columns. Args: model: SQLAlchemy model. field_filters: Filters for fields. Returns: GraphQL fields dictionary: field name (key) - field instance (value). """ graphql_filters = {} filters_map = cls.ALLOWED_FILTERS model_fields = { c.name: { 'column': c, 'type': c.type, 'nullable': c.nullable } for c in model.__table__.columns if c.name in field_filters } for field_name, field_object in model_fields.items(): column_type = field_object['type'] expressions = field_filters[field_name] if expressions == cls.ALL: expressions = filters_map[column_type.__class__].copy() if field_object['nullable']: expressions.append(cls.IS_NULL) field_type = convert_sqlalchemy_type(column_type, field_object['column']) fields = cls._generate_filter_fields(expressions, field_name, field_type, field_object['nullable']) for name, field in fields.items(): graphql_filters[name] = get_field_as(field, graphene.InputField) return graphql_filters
def _get_gql_type_from_sqla_type( cls, column_type, sqla_column ) -> 'Union[Type[graphene.ObjectType], Type[GenericScalar]]': """ Get GraphQL type from SQLAlchemy column. Args: column_type: SQLAlchemy column type. sqla_column: SQLAlchemy column or hybrid attribute. Returns: GraphQL type. """ if column_type is None: return GenericScalar else: return convert_sqlalchemy_type(column_type, sqla_column)
def _get_gql_type_from_sqla_type( cls, column_type, sqla_column ) -> 'Union[Type[graphene.ObjectType], Type[GenericScalar]]': """ Get GraphQL type from SQLAlchemy column. Args: column_type: SQLAlchemy column type. sqla_column: SQLAlchemy column or hybrid attribute. Returns: GraphQL type. """ if column_type is None: return GenericScalar else: _type = convert_sqlalchemy_type(column_type, sqla_column) if inspect.isfunction(_type): return _type() # only graphene-sqlalchemy>2.2.0 return _type
def create_auth_schema(): def login_mutate(cls, info, model=None, **kwargs): query = model.get_query(info) model = model._meta.model data = kwargs.get('data', {}) if 'email' in data and 'password' in data: try: user = query.filter(model.email == data['email']).one() except NoResultFound: raise CodeduExceptionHandler( HTTPBadRequest(description="USER NOT FOUND")) if user.password == hmac.new(Config.SECRET_KEY.encode(), data['password'].encode(), sha256).hexdigest(): encoded_jwt = jwt.encode( { 'iat': datetime.datetime.utcnow(), 'user_id': user.id, 'email': data['email'], 'admin': user.admin, # 'exp':datetime.datetime.utcnow() + datetime.timedelta(seconds=30), }, Config.SECRET_KEY, algorithm='HS256') return cls(**{'user': user, 'token': encoded_jwt.decode()}) else: raise CodeduExceptionHandler( HTTPBadRequest(description="INVALID PASSWORD")) else: raise CodeduExceptionHandler( HTTPBadRequest(description="INVALID PARAMETER")) def register_mutate(cls, info, model=None, **kwargs): model = model._meta.model data = kwargs.get('data', None) image_info = info.context.get('image_info', None) if data: validate_user_data(data) data['password'] = hmac.new(Config.SECRET_KEY.encode(), data['password'].encode(), sha256).hexdigest() db_session = info.context.get('session', None) if db_session: instance = model(**data) db_session.add(instance) db_session_flush(db_session) if image_info: image_handle('user', instance, image_info[0]) return cls(**{model.__tablename__: instance}) def update_user_info_mutate(cls, info, model=None, **kwargs): if info.context['auth']['data']: query = model.get_query(info) model = model._meta.model data = kwargs.get('data', None) image_info = info.context.get('image_info', None) if data: validate_user_data(data) if not info.context['auth']['data']['admin']: data['id'] = info.context['auth']['data']['user_id'] data['password'] = hmac.new(Config.SECRET_KEY.encode(), data['password'].encode(), sha256).hexdigest() else: if not 'id' in data: CodeduExceptionHandler( HTTPBadRequest(description="INVALID PARAMETER")) if 'password' in data: del data['password'] instance = get_instance_by_pk(query, model, data) if info.context['auth']['data']['admin'] or ( instance.one() and instance.one().password == data['password']): instance.update(data) if image_info: image_handle('user', instance.one(), image_info[0]) return cls(**{model.__tablename__: instance.one()}) else: raise CodeduExceptionHandler( HTTPBadRequest(description="INVALID PASSWORD")) else: raise CodeduExceptionHandler( HTTPUnauthorized( description=info.context['auth']['description'])) def update_password_mutate(cls, info, model=None, **kwargs): if info.context['auth']['data']: query = model.get_query(info) model = model._meta.model data = kwargs.get('data', None) if data: validate_user_data(data) if not info.context['auth']['data']['admin']: data['id'] = info.context['auth']['data']['user_id'] data['password'] = hmac.new(Config.SECRET_KEY.encode(), data['password'].encode(), sha256).hexdigest() data["password_modified"] = datetime.datetime.utcnow() instance = get_instance_by_pk(query, model, data) if info.context['auth']['data']['admin'] or instance.one( ).password == data['password']: data['password'] = hmac.new(Config.SECRET_KEY.encode(), data['new_password'].encode(), sha256).hexdigest() del data['new_password'] instance.update(data) return cls(**{model.__tablename__: instance.one()}) else: raise CodeduExceptionHandler( HTTPBadRequest(description="INVALID PASSWORD")) else: raise CodeduExceptionHandler( HTTPUnauthorized( description=info.context['auth']['description'])) def delete_account_mutate(cls, info, model=None, **kwargs): if info.context['auth']['data']: query = model.get_query(info) model = model._meta.model data = kwargs.get('data', None) if data: if not info.context['auth']['data']['admin']: data['id'] = info.context['auth']['data']['user_id'] data['password'] = hmac.new(Config.SECRET_KEY.encode(), data['password'].encode(), sha256).hexdigest() instance = get_instance_by_pk(query, model, data) if info.context['auth']['data']['admin'] or instance.one( ).password == data['password']: tmp_instance = instance.one() instance.delete() image_handle("user", tmp_instance, None) return cls(**{model.__tablename__: tmp_instance}) else: raise CodeduExceptionHandler( HTTPBadRequest(description="INVALID PASSWORD")) else: raise CodeduExceptionHandler( HTTPUnauthorized( description=info.context['auth']['description'])) query_field = {} mutation_field = {} query_field["login"] = create_mutation_field( "Login", gql_models['user'], login_mutate, create_input_class( 'LoginInput', { 'email': graphene.String(required=True), 'password': graphene.String(required=True), }), { 'token': graphene.String(), }) mutation_field["register"] = create_mutation_field( "Register", gql_models['user'], register_mutate, create_input_class( 'RegisterInput', { 'username': graphene.String(required=True), 'email': graphene.String(required=True), 'password': graphene.String(required=True), 'admin': graphene.Boolean(), }), ) fields = {} for colname, column in gql_models[ 'user']._meta.model.__table__.columns.items(): if not colname in ['created', 'modified', 'email']: fields[colname] = convert_sqlalchemy_type( getattr(column, 'type', None), column)() mutation_field["update_user_info"] = create_mutation_field( "UpdateUserInfo", gql_models['user'], update_user_info_mutate, create_input_class('UpdateUserInfoInput', fields), ) mutation_field["update_password"] = create_mutation_field( "UpdatePassword", gql_models['user'], update_password_mutate, create_input_class( 'UpdatePasswordInput', { 'password': graphene.String(), 'new_password': graphene.String(required=True), 'id': graphene.ID(), }), ) mutation_field["delete_account"] = create_mutation_field( "DeleteAccount", gql_models['user'], delete_account_mutate, create_input_class('DeleteAccountInput', { 'password': graphene.String(), 'id': graphene.ID(), }), ) return (query_field, mutation_field)
def create_base_schema(): def resolve_model(self, info, model, **kwargs): query = model.get_query(info) search = info.context.get('search', None) if 'password' in kwargs: raise CodeduExceptionHandler( HTTPBadRequest( description="you can't find user with password")) for arg, value in kwargs.items(): if search: print('search') query = query.filter( getattr(model._meta.model, arg).like(f"%{value}%")) else: print('match') query = query.filter(getattr(model._meta.model, arg) == value) user = query.all() return user def create_mutate(cls, info, model=None, **kwargs): if info.context['auth']['data']: if not info.context['auth']['data']['admin']: raise CodeduExceptionHandler( HTTPUnauthorized(description='PERMISSION DENIED')) model = model._meta.model data = kwargs.get('data', None) if data: db_session = info.context.get('session', None) if db_session: instance = model(**data) db_session.add(instance) db_session_flush(db_session) return cls(**{model.__tablename__: instance}) else: raise CodeduExceptionHandler( HTTPUnauthorized( description=info.context['auth']['description'])) def update_mutate(cls, info, model=None, **kwargs): if info.context['auth']['data']: if not info.context['auth']['data']['admin']: raise CodeduExceptionHandler( HTTPUnauthorized(description='PERMISSION DENIED')) query = model.get_query(info) model = model._meta.model data = kwargs.get('data', None) if data: instance = get_instance_by_pk(query, model, data) instance.update(data) return cls(**{model.__tablename__: instance.one()}) else: raise CodeduExceptionHandler( HTTPUnauthorized( description=info.context['auth']['description'])) def delete_mutate(cls, info, model=None, **kwargs): if info.context['auth']['data']: if not info.context['auth']['data']['admin']: raise CodeduExceptionHandler( HTTPUnauthorized(description='PERMISSION DENIED')) query = model.get_query(info) model = model._meta.model data = kwargs.get('data', None) if data: instance = get_instance_by_pk(query, model, data) tmp_instance = instance.one() instance.delete() return cls(**{model.__tablename__: tmp_instance}) else: raise CodeduExceptionHandler( HTTPUnauthorized( description=info.context['auth']['description'])) query_field = {} mutation_field = {} input_classes = {} filter_field = {} fcf_field = {} def random_quiz(cls, info, query, value, model): return query.order_by(func.rand()), None for tablename, model in gql_models.items(): filter_class_fields = {} if tablename in ['lesson_quiz']: filter_class_fields['random'] = graphene.Boolean() filter_class_fields['random_filter'] = classmethod( lambda cls, info, query, value, model=model: random_quiz( cls, info, query, value, model)) filter_field[tablename] = create_filter_class(f"{tablename}Filter", model._meta.model, filter_class_fields)() fcf_field[model._meta.model] = filter_field[tablename] FCF = type("FCF", (FilterableConnectionField, ), { "filters": fcf_field, }) except_table = [ 'user', 'post', 'post_comment', 'code', 'code_comment', 'question', 'answer', 'like' ] except_table += [ "post_like", "post_comment_like", "code_like", "code_comment_like", "question_like", "answer_like" ] for tablename, model in gql_models.items(): if not tablename in except_table: fields = {} for colname, column in model._meta.model.__table__.columns.items(): if not colname == 'created' and not colname == 'modified': fields[colname] = convert_sqlalchemy_type( getattr(column, 'type', None), column)() for colname, column in model._meta.model.__mapper__.relationships.items( ): fields[colname] = convert_sqlalchemy_type( getattr(column, 'type', None), column)() input_classes[tablename] = create_input_class( f"{tablename}Input", fields) mutation_field[f"create_{tablename}"] = create_mutation_field( f"Create{tablename}", model, create_mutate, input_classes[tablename]) mutation_field[f"update_{tablename}"] = create_mutation_field( f"Update{tablename}", model, update_mutate, input_classes[tablename]) mutation_field[f"delete_{tablename}"] = create_mutation_field( f"Delete{tablename}", model, delete_mutate, input_classes[tablename]) tmp_node = create_node_class(f"{tablename}Node", model._meta.model, FCF.factory) tmp_node._meta.connection.total_count = graphene.Int() tmp_node._meta.connection.resolve_total_count = lambda self, info, **kwargs: self.length tmp_node._meta.connection._meta.fields["total_count"] = graphene.Field( graphene.NonNull(graphene.Int)) tmp_connection = create_connection_class( f"{tablename}Connection", tmp_node, ) query_field[tablename] = FCF(tmp_connection) return (query_field, mutation_field)