Coder Social home page Coder Social logo

getstream / stream-python Goto Github PK

View Code? Open in Web Editor NEW
141.0 42.0 40.0 574 KB

Python Client - Build Activity Feeds & Streams with GetStream.io

Home Page: https://getstream.io

License: BSD 3-Clause "New" or "Revised" License

Python 97.62% Shell 1.39% Makefile 0.44% JavaScript 0.55%
python stream-python activity-feed getstream-io notification-feed news-feed feed timeline

stream-python's Introduction

Official Python SDK for Stream Feeds

build PyPI version PyPI - Python Version

Official Python API client for Stream Feeds, a web service for building scalable newsfeeds and activity streams.
Explore the docs »

Django Code Sample · Report Bug · Request Feature

📝 About Stream

💡 Note: this is a library for the Feeds product. The Chat SDKs can be found here.

You can sign up for a Stream account at our Get Started page.

You can use this library to access feeds API endpoints server-side.

For the client-side integrations (web and mobile) have a look at the JavaScript, iOS and Android SDK libraries (docs).

💡 We have a Django integration available here.

⚙️ Installation

$ pip install stream-python

📚 Full documentation

Documentation for this Python client are available at the Stream website.

✨ Getting started

import datetime

# Create a new client
import stream
client = stream.connect('YOUR_API_KEY', 'API_KEY_SECRET')

# Create a new client specifying data center location
client = stream.connect('YOUR_API_KEY', 'API_KEY_SECRET', location='us-east')
# Find your API keys here https://getstream.io/dashboard/

# Create a feed object
user_feed_1 = client.feed('user', '1')

# Get activities from 5 to 10 (slow pagination)
result = user_feed_1.get(limit=5, offset=5)
# (Recommended & faster) Filter on an id less than the given UUID
result = user_feed_1.get(limit=5, id_lt="e561de8f-00f1-11e4-b400-0cc47a024be0")

# Create a new activity
activity_data = {'actor': 1, 'verb': 'tweet', 'object': 1, 'foreign_id': 'tweet:1'}
activity_response = user_feed_1.add_activity(activity_data)
# Create a bit more complex activity
activity_data = {'actor': 1, 'verb': 'run', 'object': 1, 'foreign_id': 'run:1',
	'course': {'name': 'Golden Gate park', 'distance': 10},
	'participants': ['Thierry', 'Tommaso'],
	'started_at': datetime.datetime.now()
}
user_feed_1.add_activity(activity_data)

# Remove an activity by its id
user_feed_1.remove_activity("e561de8f-00f1-11e4-b400-0cc47a024be0")
# or by foreign id
user_feed_1.remove_activity(foreign_id='tweet:1')

# Follow another feed
user_feed_1.follow('flat', '42')

# Stop following another feed
user_feed_1.unfollow('flat', '42')

# List followers/following
following = user_feed_1.following(offset=0, limit=2)
followers = user_feed_1.followers(offset=0, limit=10)

# Creates many follow relationships in one request
follows = [
    {'source': 'flat:1', 'target': 'user:1'},
    {'source': 'flat:1', 'target': 'user:2'},
    {'source': 'flat:1', 'target': 'user:3'}
]
client.follow_many(follows)

# Batch adding activities
activities = [
	{'actor': 1, 'verb': 'tweet', 'object': 1},
	{'actor': 2, 'verb': 'watch', 'object': 3}
]
user_feed_1.add_activities(activities)

# Add an activity and push it to other feeds too using the `to` field
activity = {
    "actor":"1",
    "verb":"like",
    "object":"3",
    "to":["user:44", "user:45"]
}
user_feed_1.add_activity(activity)

# Retrieve an activity by its ID
client.get_activities(ids=[activity_id])

# Retrieve an activity by the combination of foreign_id and time
client.get_activities(foreign_id_times=[
    (foreign_id, activity_time),
])

# Enrich while getting activities
client.get_activities(ids=[activity_id], enrich=True, reactions={"counts": True})

# Update some parts of an activity with activity_partial_update
set = {
    'product.name': 'boots',
    'colors': {
        'red': '0xFF0000',
        'green': '0x00FF00'
    }
}
unset = [ 'popularity', 'details.info' ]
# ...by ID
client.activity_partial_update(id=activity_id, set=set, unset=unset)
# ...or by combination of foreign_id and time
client.activity_partial_update(foreign_id=foreign_id, time=activity_time, set=set, unset=unset)

# Generating user token for client side usage (JS client)
user_token = client.create_user_token("user-42")

# Javascript client side feed initialization
# client = stream.connect(apiKey, userToken, appId);

# Generate a redirect url for the Stream Analytics platform to track
# events/impressions on url clicks
impression = {
    'content_list': ['tweet:1', 'tweet:2', 'tweet:3'],
    'user_data': 'tommaso',
    'location': 'email',
    'feed_id': 'user:global'
}

engagement = {
    'content': 'tweet:2',
    'label': 'click',
    'position': 1,
    'user_data': 'tommaso',
    'location': 'email',
    'feed_id':
    'user:global'
}

events = [impression, engagement]

redirect_url = client.create_redirect_url('http://google.com/', 'user_id', events)

Async code usage

import datetime
import stream
client = stream.connect('YOUR_API_KEY', 'API_KEY_SECRET', use_async=True)


# Create a new client specifying data center location
client = stream.connect('YOUR_API_KEY', 'API_KEY_SECRET', location='us-east', use_async=True)
# Find your API keys here https://getstream.io/dashboard/

# Create a feed object
user_feed_1 = client.feed('user', '1')

# Get activities from 5 to 10 (slow pagination)
result = await user_feed_1.get(limit=5, offset=5)
# (Recommended & faster) Filter on an id less than the given UUID
result = await user_feed_1.get(limit=5, id_lt="e561de8f-00f1-11e4-b400-0cc47a024be0")

# Create a new activity
activity_data = {'actor': 1, 'verb': 'tweet', 'object': 1, 'foreign_id': 'tweet:1'}
activity_response = await user_feed_1.add_activity(activity_data)
# Create a bit more complex activity
activity_data = {'actor': 1, 'verb': 'run', 'object': 1, 'foreign_id': 'run:1',
	'course': {'name': 'Golden Gate park', 'distance': 10},
	'participants': ['Thierry', 'Tommaso'],
	'started_at': datetime.datetime.now()
}
await user_feed_1.add_activity(activity_data)

# Remove an activity by its id
await user_feed_1.remove_activity("e561de8f-00f1-11e4-b400-0cc47a024be0")
# or by foreign id
await user_feed_1.remove_activity(foreign_id='tweet:1')

# Follow another feed
await user_feed_1.follow('flat', '42')

# Stop following another feed
await user_feed_1.unfollow('flat', '42')

# List followers/following
following = await user_feed_1.following(offset=0, limit=2)
followers = await user_feed_1.followers(offset=0, limit=10)

# Creates many follow relationships in one request
follows = [
    {'source': 'flat:1', 'target': 'user:1'},
    {'source': 'flat:1', 'target': 'user:2'},
    {'source': 'flat:1', 'target': 'user:3'}
]
await client.follow_many(follows)

# Batch adding activities
activities = [
	{'actor': 1, 'verb': 'tweet', 'object': 1},
	{'actor': 2, 'verb': 'watch', 'object': 3}
]
await user_feed_1.add_activities(activities)

# Add an activity and push it to other feeds too using the `to` field
activity = {
    "actor":"1",
    "verb":"like",
    "object":"3",
    "to":["user:44", "user:45"]
}
await user_feed_1.add_activity(activity)

# Retrieve an activity by its ID
await client.get_activities(ids=[activity_id])

# Retrieve an activity by the combination of foreign_id and time
await client.get_activities(foreign_id_times=[
    (foreign_id, activity_time),
])

# Enrich while getting activities
await client.get_activities(ids=[activity_id], enrich=True, reactions={"counts": True})

# Update some parts of an activity with activity_partial_update
set = {
    'product.name': 'boots',
    'colors': {
        'red': '0xFF0000',
        'green': '0x00FF00'
    }
}
unset = [ 'popularity', 'details.info' ]
# ...by ID
await client.activity_partial_update(id=activity_id, set=set, unset=unset)
# ...or by combination of foreign_id and time
await client.activity_partial_update(foreign_id=foreign_id, time=activity_time, set=set, unset=unset)

# Generating user token for client side usage (JS client)
user_token = client.create_user_token("user-42")

# Javascript client side feed initialization
# client = stream.connect(apiKey, userToken, appId);

# Generate a redirect url for the Stream Analytics platform to track
# events/impressions on url clicks
impression = {
    'content_list': ['tweet:1', 'tweet:2', 'tweet:3'],
    'user_data': 'tommaso',
    'location': 'email',
    'feed_id': 'user:global'
}

engagement = {
    'content': 'tweet:2',
    'label': 'click',
    'position': 1,
    'user_data': 'tommaso',
    'location': 'email',
    'feed_id':
    'user:global'
}

events = [impression, engagement]

redirect_url = client.create_redirect_url('http://google.com/', 'user_id', events)

JS client.

✍️ Contributing

=======

We welcome code changes that improve this library or fix a problem, please make sure to follow all best practices and add tests if applicable before submitting a Pull Request on Github. We are very happy to merge your code in the official repository. Make sure to sign our Contributor License Agreement (CLA) first. See our license file for more details.

🧑‍💻 We are hiring!

We've recently closed a $38 million Series B funding round and we keep actively growing. Our APIs are used by more than a billion end-users, and you'll have a chance to make a huge impact on the product within a team of the strongest engineers all over the world.

Check out our current openings and apply via Stream's website.

stream-python's People

Contributors

aarcro avatar aigeruth avatar dwightgunning avatar ferhatelmas avatar followben avatar github-actions[bot] avatar hannesvdvreken avatar iandouglas avatar jeltef avatar jimmypettersson85 avatar kennym avatar link512 avatar marco-ulge avatar matthisk avatar peterdeme avatar pterk avatar ruggi avatar sidatnsio avatar tbarbugli avatar tschellenbach avatar wangsha avatar werded avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stream-python's Issues

Unable to retrive specific kind of reactions.

Unable to retrive specific kind of reactions.

Steps to reproduce

client.reactions.filter(activity_id="ed2837a6-0a3b-4679-adc1-778a1704852d", kind="like", limit=4)
here I want to retrive the reactions of kind "like" but in response it gives all kinds of reactions.

Sample JSON Output:

{
  'next': '/api/v1.0/reaction/activity_id/9fa96c96-94e9-808003-cde8-d71/?api_key=ztusu5zkphgg&id_lt=c2bd55be-972b-4ddf63bfcc&kind=like&limit=4',
  'results': [
    {
      'created_at': '2019-06-21T12:16:48.945974Z',
      'updated_at': '2019-06-21T12:16:48.945974Z',
      'id': '2c38fd40-ea42-45ee-adda-7cf8447c633e',
      'user_id': '27',
      },
      'kind': 'like',
      'activity_id': '9fa96c96-9407-11e9-8080-80003cde8d71',
      
    },
    {
      'created_at': '2019-06-21T11:15:16.156967Z',
      'updated_at': '2019-06-21T11:15:16.156967Z',
      'id': '41ad8abb-4814-4ad2-a1ab-6a090ebc0d63',
      'user_id': '45',
      },
      'kind': 'like',
      'activity_id': '9fa96c96-9407-11e9-8080-80003cde8d71',
      'data': {
        
      },
      'parent': '',
      'latest_children': {
        
      },
      'children_counts': {
        
      }
    },
    {
      'created_at': '2019-06-21T10:39:39.460565Z',
      'updated_at': '2019-06-21T10:39:39.460565Z',
      'id': '6990fcb2-bbda-462e-aa0d-992fe236be71',
      'user_id': '45',
      'kind': 'comment',
      'activity_id': '9fa96c96-9407-11e9-8080-80003cde8d71',
      'data': {
        'text': 'Hello'
      },
      'parent': '',
      'latest_children': {
        
      },
      'children_counts': {
        
      }
    },
    {
      'created_at': '2019-06-21T10:38:28.438077Z',
      'updated_at': '2019-06-21T10:38:28.438077Z',
      'id': 'a1ff6918-423a-428b-b3c8-6dbaddbfb028',
      'user_id': '45',
      'kind': 'comment',
      'activity_id': '9fa96c96-9407-11e9-8080-80003cde8d71',
      'data': {
        'text': 'Nice'
      },
      'parent': '',
      'latest_children': {
        
      },
      'children_counts': {
        
      }
    }
  ],
  'duration': '2.48ms'
}

Client breaks when raising errors

Traceback (most recent call last):
File "examples/etoro_example.py", line 46, in
activity_response = user_feed_crembo.add_activities(activities)
File "/Users/thierryschellenbach/workspace/stream-python/stream/feed.py", line 82, in add_activities
self.feed_url, data=data, signature=token)
File "/Users/thierryschellenbach/workspace/stream-python/stream/client.py", line 203, in post
return self._make_request(self.session.post, _args, *_kwargs)
File "/Users/thierryschellenbach/workspace/stream-python/stream/client.py", line 170, in _make_request
return self._parse_response(response)
File "/Users/thierryschellenbach/workspace/stream-python/stream/client.py", line 115, in _parse_response
self.raise_exception(parsed_result, status_code=response.status_code)
File "/Users/thierryschellenbach/workspace/stream-python/stream/client.py", line 186, in raise_exception
for field, errors in exception_fields.items():
AttributeError: 'list' object has no attribute 'items'

own reactions

Problem explanation

I expect to see only user's own reactions, but instead I get all reactions, regardless of who they came from.

Steps to reproduce

feed = client.feed('user', 'alice');
response = feed.get(limit=30, enrich=True, reactions: {"counts": true, "recent": true, "own": true})

Environment info

Operating System:
Python version:
Library version:

Error traceback (if applicable)

[put traceback here]

Code snippet that causes the problem

feed = client.feed('user', 'alice');
response = feed.get(limit=30, enrich=True, reactions: {"counts": true, "recent": true, "own": true})

Bulk UnFollow Relationship

Is there a feature for bulk un-follow just like follow_many?

[ to: ] keyword v/s follow feature. Can you elaborate case which one to use specifically?

Stop using strict versions in requires

Problem explanation

It is currently not possible to use both stream-python and stream-chat-python within the same python environment, due to pycryptodomex requires version dependency.

It would be pretty easy to relax the dependency with >= (instead of strict equal), in both projects

Steps to reproduce

pip install stream-python stream-chat-python

Environment info

OSX 10.14.5
Python version: 3.7.4
Library version: 3.1.0

Error traceback (if applicable)

ERROR: stream-chat 1.0.1 has requirement pycryptodomex==3.8.1, but you'll have pycryptodomex 3.4.7 which is incompatible.

Code snippet that causes the problem

n/a

SDK has extra unused parameter for fetching feeds I follow

Problem explanation

Our documentation at Stream advises Python users to fetch a list of feeds a user follows by calling the .following() method with a parameter to pass in a list of feeds to filter by, but also includes a filter parameter which goes unused. Our documentation will be updated, but perhaps this unused parameter should be removed from the SDK as well? Since this could potentially cause a breaking change for our SDK users, perhaps we should also bump the minor version from 2.3 to 2.4.

Code snippet that causes the problem

def following(self, offset=0, limit=25, feeds=None, filter=None):

How can I sort all the activity in the feed by a time field.

I want the most recent edited, so I want to update the activity and have it show up firs

This is what Im currently using.

 37         feeds = feed_manager.get_news_feeds(self.request.user.id)                                                                                                                                               
 38         activities = feeds.get('timeline').get()['results'] 

find out what's wrong with httpsig

very often setup install fails with this error:

running test
Searching for httpsig==1.1.2
Reading https://pypi.python.org/simple/httpsig/
No local packages or download links found for httpsig==1.1.2
error: Could not find suitable distribution for Requirement.parse('httpsig==1.1.2')

There is clearly something really broken in httpsig library

token returned from get_readonly_token() does not authenticate

My server side generation of read only tokens looks like the following:

from stream_django.client import stream_client
def jwt_response_payload_handler(token, user=None, request=None):
    user_feed_1 = stream_client.feed('user', str(user.id))
    readonly_token = user_feed_1.get_readonly_token()

    return {
        'token': token,
        'stream': str(readonly_token)
    }

JWT_AUTH = {
    'JWT_RESPONSE_PAYLOAD_HANDLER': jwt_response_payload_handler
}

My client side code looks like the following:

function setupStream (token, id) {
  var client = stream.connect('te5vptfdhrss', null, '10287')
  var user1 = client.feed('user', id, token)
  debugger
  function callback (data) {
    console.log(data)
  }

  function failCallback (data) {
    alert('something went wrong, check the console logs')
    console.log(data)
  }
  user1.get({ limit: 5, offset: 0 })
  .then(callback)
  .catch(failCallback)
  // user1.subscribe(callback).then(() => {}, failCallback)
}

I get an error message of the following:

{
    "code": null,
    "detail": "url signature missing or invalid",
    "duration": "10ms",
    "exception": "AuthenticationFailed",
    "status_code": 403
}

HOWEVER:
if i change the token to be read/write, the client side code doesn't produce an error.

    readonly_token = user_feed_1.token

How can I delete all the activity in a stream?

Hello, I have been using the stream api to develop an app .. one of the models that I had in the stream was removed from the database .. and now I would like to remove that data from the stream ..

Failing to pip install

The httpsig dependency is failing to install because it's 404ing on PyPI, which means stream-python fails to install. Just wanted to make you guys aware:

$ pip install stream-python
Downloading/unpacking stream-python
  Downloading stream-python-2.2.1.tar.gz
  Running setup.py egg_info for package stream-python

Downloading/unpacking requests>=2.3.0 (from stream-python)
  Downloading requests-2.7.0.tar.gz (451kB): 451kB downloaded
  Running setup.py egg_info for package requests

Downloading/unpacking six>=1.8.0 (from stream-python)
  Downloading six-1.9.0.tar.gz
  Running setup.py egg_info for package six

    no previously-included directories found matching 'documentation/_build'
Downloading/unpacking httpsig==1.1.0 (from stream-python)
  Could not find any downloads that satisfy the requirement httpsig==1.1.0 (from stream-python)
Cleaning up...
No distributions at all found for httpsig==1.1.0 (from stream-python)
Storing complete log in /Users/max/.pip/pip.log

Mentioned here: ahknight/httpsig#5

Using requests_toolbelt on App Engine to use Stream client

Hi all,

We're trying to integrate this library into our app, but have been having some issues because our app is hosted on Google App Engine Standard Environment. A similar issue was filed a few months ago and your team sent a PR to requests_toolbelt to fix the error, but the error is still there.

I followed the advice of the OP of issue #43 and changed line 9 in client.py and so far it seems to be working. Would it be possible to add this to the library?

Also, the fix your team merged into requests_toolbelt is no longer in their codebase, and manually adding that fix back in there produces a recursion error.

Steps to reproduce

Calling feed.add_activity(activity_data) without changing line 9 in client.py produces the following error:

ConnectionError: ('Connection aborted.', error(13, 'Permission denied'))

Environment info

Operating System: Ubuntu 14.04
Python version: 2.7
Library version:
requests-toolbelt==0.7.0
stream-python==2.3.9

Parse activity.time as UTC

Problem explanation

Right now activity.time does not have timezone information making life harder. Stream returns UTC datetimes so it is correct to localize them as such.

Allow for client use on app engine with request_toolbelt

Hey team,

I just started playing around with this client and because of some issues with App Engine and the requests library it took me a while.

In order to get it working I used requests_toolbelt which uses an Adapter compatible with App Engine.

A quick fix on your end to make it compatible would just be to update line 9 in client.py to

from requests.sessions import HTTPAdapter

as this is what's being patched by request_toolbelt as seen in the link below.

https://github.com/sigmavirus24/requests-toolbelt/blob/master/requests_toolbelt/adapters/appengine.py#L126

support for private feeds

send target_token as part of post body containing the signature token of the target feed id (eg. secret:42)

the test account has a private feed configured (slug secret)

Is there a bulk unfollow uperator?

Whenever i delete a user in my staging enviorment because of the way my model delete cascade a singal request is made per deleted model of Follow type.

This causes a rate limit error to be hit

However if there was a bulk unfollow method I could than in my user model pre delete signal
get all the follow models that are related and bulk delete them from the stream api.

Im not to concern for the signal being firred when i delete the Follows because it would be a bulk delete operation that doesn't fire a signal.

Proposal: simplify implementation by dropping intermediary variable

Problem explanation

Nothing specifically wrong but could be improved, IMHO.

        response = self.client.post(
            resource,
            service_name="personalization",
            params=params,
            signature=self.token,
            data=data,
        )
        return response

Generally, previous idiom is followed but response could be returned directly without losing any significant information but it might improve readability by making implementation shorter since it's repeated many times.

Suggestion is as following:

-        response = self.client.post(
+        return self.client.post(
             resource,
             service_name="personalization",
             params=params,
             signature=self.token,
             data=data,
         )
-        return response

Steps to reproduce

None, above description is self contained.

Environment info

N/A

Error traceback (if applicable)

N/A

Code snippet that causes the problem

N/A

Support JWT > 1.3.0

Problem explanation

Having stream-python installed with say djangorestframework-jwt raises this notice
image
PyJWT is at 1.6.4, but stream-python is still tied to 1.3.0

Steps to reproduce

Install stream-python, then install latest pyjwt (or any package that depends on the latest pyjwt)

Environment info

Windows 10
Python version: 3.6.2
Library version: stream-python 2.9.0 (installing stream_django)

Issue has been opened for this in the past
#20

exception handling is broken

Something is going wrong with exception handling and the response exception is replaced by an InputException without any message.

TO parameter is edited in place

if you add an activity

activity = dict(to=['a'])
feed.add_activity(activity)
activity.to no longer equals ['a']
it's edited in place....

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.