Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to stream entities #1392

Closed
raulpopadineti opened this issue May 11, 2016 · 12 comments
Closed

Ability to stream entities #1392

raulpopadineti opened this issue May 11, 2016 · 12 comments

Comments

@raulpopadineti
Copy link

raulpopadineti commented May 11, 2016

Hi,

We'd like to build an API that is able to paginate entities and send them in chunks.

A really raw way of sending data in chunks would be:

require 'grape'

module Stream
  class API < Grape::API
    prefix 'api'
    format :txt
    default_format :txt

    class Streamer
      def initialize(pages:, page_size:, latency:)
        @pages = pages
        @page_size = page_size
        @latency = latency
      end

      def each
        (1..@pages).lazy.each do |p|
          yield "#{p}\n" * @page_size
          sleep(@latency)
        end
      end
    end

    resources :users do
      params do
        optional :pages, type: Integer, default: 10
        optional :page_size, type: Integer, default: 10
        optional :latency, type: Float, default: 1.0
      end

      get do
        status 200
        p = declared(params)
        stream Streamer.new(pages: p.pages, page_size: p.page_size, latency: p.latency)
      end
    end
  end
end

With this approach I get the deprecation warning:

[DEPRECATION] Argument as FileStreamer-like object is deprecated. Use path to file instead.

because Grape assumes the response is a file.

Will you support streaming of a Grape::Entity or any other non-file objects in the future?

@raulpopadineti raulpopadineti changed the title Ability to stream "non-file" objects Ability to stream entities May 11, 2016
@dblock
Copy link
Member

dblock commented May 11, 2016

I think the deprecation is just for files. It's still supporting a pure streaming interface. You should look at the code a bit more in depth and see what's missing? Generally I don't see why we wouldn't want this, so I'll leave this open.

@lfidnl
Copy link
Contributor

lfidnl commented May 12, 2016

Hello. I guess, now stream method can stream only files. Maybe we should to do:

  1. Add option stream: true to file method. This option will replace current stream method.
  2. Rewrite stream method in such a way that it can receive object which respond to each method and streams it.

@raulpopadineti
Copy link
Author

raulpopadineti commented May 13, 2016

@dblock @lfidnl I've managed to stream JSON by polishing a bit on the Streamer class I added:

class Streamer
      def initialize(entity_class:, enum:, root:, options:)
        @entity_class = entity_class
        @enum = enum
        @root = root
        @options = options
      end

      def each
        yield "{\n  \"#{@root}\": [\n    "
        first = true
        @enum.lazy.each do |object|
          buffer = ''
          buffer << ",\n  " unless first
          first = false
          data = represent_entity(object, @options).as_json
          buffer << JSON.pretty_generate(data)[1..-2].strip
          yield buffer
          # sleep 1
        end
        yield "  \n  ]\n}\n"
      end

      def represent_entity(objects, options = {})
        if objects.respond_to?(:to_ary)
          inner = objects.to_ary.map { |object| @entity_class.new(object, options.reverse_merge(collection: true)).presented }
        else
          inner = @entity_class.new(objects, options).presented
        end
        inner
      end
    end

Basically I overwritten the default represent method because the root_element is added in the each method. I'm also passing the objects as an Enumerator with lazy loading for memory optimization.

Let me know if you have any other suggestion or how can I improve it so I can wrap it all up and submit a PR.

@lfidnl
Copy link
Contributor

lfidnl commented May 13, 2016

@raulpopadineti one question: is it works now with deprecation warning, or not?

@raulpopadineti
Copy link
Author

@lfidnl it works with deprecation warning.

@lfidnl
Copy link
Contributor

lfidnl commented May 13, 2016

@raulpopadineti good, I think, @dblock should approve my suggestion (or change it) and anyone can create PR for fix this issue :)

@dblock
Copy link
Member

dblock commented May 13, 2016

I think what you suggest is sensible, would like to see a PR.

@raulpopadineti
Copy link
Author

@dblock to which solution are you referring?

@dblock
Copy link
Member

dblock commented May 16, 2016

I meant what @lfidnl says. But you're all looking at code, I suggest trying something and PRing it.

@code-bunny
Copy link

For those interested that may use JSON API, i have took the ideas here and built a basic gem https://github.com/Cabbit/grape_json_api_streamer for grape that i'll expand upon as my projects get more complex.

@dblock
Copy link
Member

dblock commented Jul 4, 2016

@cabbit contribute that to http://www.ruby-grape.org/projects/ pls.

@dm1try
Copy link
Member

dm1try commented Jun 11, 2020

fixed in the referenced PR

@dm1try dm1try closed this as completed Jun 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants