from bwapi.bwproject import BWProject, BWUser
from bwapi.bwresources import BWQueries, BWGroups, BWAuthorLists, BWSiteLists, BWLocationLists, BWTags, BWCategories, BWRules, BWMentions, BWSignals
import datetime

"""
Explanation: SDK for the Brandwatch API: Demo
Introduction
The goal of this notebook is to demonstrate the capabilities of the Python Software Development Kit for Brandwatch's API.  The SDK was designed to address many of the challenges involved in building complex applications which interact with RESTful API's in general and Brandwatch's API in particular:
<ul>
<li>The SDK's object hierarchy roughly mirrors the API's resource hierarchy, making the code intuitive for those familiar with the Brandwatch platform</li>
<li>All required parameters are enforced, and most optional parameters are supported and documented</li>
<li>Typical Brandwatch workflows are supported behind the scenes; for instance, one can validate, upload, and backfill a query with a single function call</li>
<li>The SDK is designed to support simple and readable code: sensible defaults are chosen for rarely used parameters and all resource IDs are handled behind the scenes
</ul>

From the user's perspective, the basic structure of the SDK is as follows.  One first creates an instance of the class BWProject; this class handles authentication (via a user name and password or API key) and keeps track of project-level data such as the project's ID.  (Behind the scenes, the user-level operations are handled by the class BWUser from which BWProject is inherited.)  One passes BWProject instance as an argument in the constructor for a series of classes which manage the various Brandwatch resources: queries, groups, tags, categories, etc.  These resource classes manage all resource-level operations: for example a single BWQueries instance handles all HTTP requests associated with queries in its attached project.
Typically, you'd import only the classes you plan on using, but for this demo all classes are listed except for superclasses which you do not use explicitly)
End of explanation
"""


import logging
logger = logging.getLogger("bwapi")

#(Default) All logging messages enabled
#logger.setLevel(logging.DEBUG)

#Does not report URL's of API requests, but all other messages enabled
#logger.setLevel(logging.INFO)

#Report only errors and warnings
#logger.setLevel(logging.WARN)

#Report only errors
#logger.setLevel(logging.ERROR)

#Disable logging
#logger.setLevel(logging.CRITICAL)

"""
Explanation: The SDK uses the Python logging module to tell you what it's doing; if desired you can control what sort of output you see by uncommenting one of the lines below:
End of explanation
"""


BWUser(username="user@example.com", password="YOUR_PASSWORD", token_path="tokens.txt")

"""
Explanation: Project
When you use the API for the first time you have to authenticate with Brandwatch. This will get you an access token. The access token is stored in a credentials file (tokens.txt in this example). Once you've authenticated your access token will be read from that file so you won't need to enter your password again.
You can authenticate from command line using the provided console script bwapi-authenticate:
$ bwapi-authenticate
Please enter your Brandwatch credentials below
Username: example@example
Password:
Authenticating user: example@example
Writing access token for user: example@example
Writing access token for user: example@example
Success! Access token: 00000000-0000-0000-0000-000000000000
Alternatively, you can authenticate directly:
End of explanation
"""


YOUR_ACCOUNT = your_account
YOUR_PROJECT = your_project

project = BWProject(username=YOUR_ACCOUNT, project=YOUR_PROJECT)

"""
Explanation: Now you have authenticated you can load your project:
End of explanation
"""


help(BWProject)

"""
Explanation: Before we really begin, please note that you can get documentation for any class or function by viewing the help documentation
End of explanation
"""


queries = BWQueries(project)

"""
Explanation: Queries
Now we create some objects which can manipulate queries and groups in our project:
End of explanation
"""


queries.names

"""
Explanation: Let's check what queries already exist in the account
End of explanation
"""


queries.upload(name = "Brandwatch Engagement", 
             includedTerms = "at_mentions:Brandwatch",
             backfill_date = "2015-09-01")

"""
Explanation: We can also upload queries directly via the API by handing the "name", "searchTerms" and "backfillDate" to the upload funcion.  If you don't pass a backfillDate, then the query will not backfill.
The BWQueries class inserts default values for the "languages", "type", "industry", and "samplePercent" parameters, but we can override the defaults by including them as keyword arguments if we want. 
Upload accepts two boolean keyword arguments - "create_only" and "modify_only" (both defaulting to False) - which specifies what API verbs the function is allowed to use; for instance, if we set "create_only" to True then the function will post a new query if it can and otherwise it will do nothing.  Note: this is true of all upload functions in this package.
End of explanation
"""


queries.upload_all([
    {"name":"Pets", 
     "includedTerms":"dogs OR cats", 
     "backfill_date":"2016-01-01T05:00:00"}, 
        
    {"name":"ice cream cake", 
     "includedTerms":"(\"ice cream\" OR icecream) AND (cake)"},
        
    {"name": "Test1",
     "includedTerms": "akdnvaoifg;anf"},
    
    {"name": "Test2",
     "includedTerms": "anvoapihajkvn"},
        
    {"name": "Test3",
     "includedTerms": "nviuphabaveh"},

    ])

"""
Explanation: If you're uploading many queries at a time, you can upload in batches.  This saves API calls and allows you to just pass in a list rather than iterating over the upload function.
End of explanation
"""


queries.delete(name = "Brandwatch Engagement")
queries.delete_all(["Pets", "Test3", "Brandwatch", "BWReact", "Brandwatch Careers"])

"""
Explanation: We can delete queries one at a time, or in batches.
End of explanation
"""


groups = BWGroups(project)

"""
Explanation: Groups
You'll notice that a lot of the things that were true for queries are also true for groups.  Many of the functions are nearly identical with any adaptations necessary handled behind the scenes for ease of use.
Again (as with queries), we need to create an object with which we can manipulate groups within the account
End of explanation
"""


groups.names

"""
Explanation: And can check for exisiting groups in the same way as before.
End of explanation
"""


for group in groups.names:
    print(group)
    print(groups.get_group_queries(group))
    print()

"""
Explanation: Now let's check which queries are in each group in the account
End of explanation
"""


groups.upload(name = "group 1", queries = ["Test1", "Test2"])

"""
Explanation: We can easily create a group with any preexisting queries.
(Recall that upload accepts two boolean keyword arguments - "create_only" and "modify_only" (both defaulting to False) - which specifies what API verbs the function is allowed to use; for instance, if we set "create_only" to True then the function will post a new query if it can and otherwise it will do nothing.)
End of explanation
"""


groups.upload_queries_as_group(group_name = "group 2", 
                               query_data_list = [{"name": "Test3",
                                           "includedTerms": "adcioahnanva"},
                                          
                                          {"name": "Test4",
                                           "includedTerms": "ioanvauhekanv;"}])

"""
Explanation: Or upload new queries and create a group with them, all in one call
End of explanation
"""


groups.delete("group 1")
print()
groups.deep_delete("group 2")

"""
Explanation: We can either delete just the group, or delete the group and the queries at the same time.
End of explanation
"""


today = (datetime.date.today() + datetime.timedelta(days=1)).isoformat() + "T05:00:00"
start = (datetime.date.today() - datetime.timedelta(days=29)).isoformat() + "T05:00:00"

"""
Explanation: Downloading Mentions (From a Query or a Group)
You can download mentions from a Query or from a Group (the code does not yet support Channels)
There is a function get_mentions() in the classes BWQueries and in BWGroups. They are used the same way.
Be careful with time zones, as they affect the date range and alter the results. If you're using the same date range for all your operations, I reccomend setting some variables at the start with dates and time zones. 
Here, today is set to the current day, and start is set to 30 days ago. Each number is offset by one to make it accurate.
End of explanation
"""


filtered = queries.get_mentions(name = "ice cream cake",
                                startDate = start, 
                                endDate = today)

"""
Explanation: To use get_mentions(), the minimum parameters needed are name (query name in this case, or group name if downloading mentions from a group), startDate, and endDate
End of explanation
"""


filtered = queries.get_mentions(name = "ice cream cake", 
                                startDate = start, 
                                endDate = today, 
                                sentiment = "positive", 
                                twitterVerified = False, 
                                impactMin = 50, 
                                xprofession = ["Politician", "Legal"])

"""
Explanation: There are over a hundred filters you can use to only download the mentions that qualify. see the full list in the file filters.py
Here, different filters are used, which take different data types. filters.py details which data type is used with each filter. Some filters, like sentiment and xprofession below, have a limited number of settings to choose from.
You can filter many things by inclusion or exclusion. The x in xprofession stands for exclusion, for example.
End of explanation
"""


filtered = queries.get_mentions(name = "ice cream cake", 
                                startDate = start, 
                                endDate = today,
                                parentCategory = ["Colors", "Days"],
                                category = {"Colors": ["Blue", "Yellow"], 
                                            "Days": ["Monday"]}, 
                                tag = ["Tastes Good"])

filtered[0]

"""
Explanation: To filter by tags, pass in a list of strings where each string is a tag name.
You can filter by categories in two differnt ways: on a subcategory level or a parent category level.  To filter on a subcategory level, use the category keyword and pass in a dictionary, where each the keys are the parent categories and the values are lists of the subcategories.  To filter on a parent category level, use the parentCategory keyword and pass in a list of parent category names.
Note: In the following call the parentCategory filter is redundant, but executed for illustrative purposes.
End of explanation
"""


categories = BWCategories(project)

categories.ids

"""
Explanation: Categories
Instantiate a BWCategories object by passing in your project as a parameter, which loads all of the categories in your project.
Print out ids to see which categories are currently in your project.
End of explanation
"""


categories.upload(name = "Droids", 
                  children = ["r2d2", "c3po"])

"""
Explanation: Upload categories individually with upload(), or in bulk with upload_all(). If you are uploading many categories, it is more efficient to use upload_all().
For upload(), pass in name and children. name is the string which represents the parent category, and children is a list of dictionaries where each dictionary is a child category- its key is "name" and its value is the name of the child category.
By default, a category will allow multiple subcategories to be applies, so the keyword argument "multiple" is set to True. You can manually set it to False by passing in multipe=False as another parameter when uploading a category.
For upload_all(), pass in a list of dictionaries, where each dictionary corrosponds to one category, and contains the parameters described above.
Let's upload a category and then check what's in the category.
End of explanation
"""


categories.upload_all([{"name":"month", 
                        "children":["January","February"]}, 
                       {"name":"Time of Day", 
                        "children":["morning", "evening"]}])

"""
Explanation: Now let's upload a few categories and then check what parent categories are in the system
End of explanation
"""


categories.upload(name = "Droids", children = ["bb8"])

"""
Explanation: To add children/subcategories, call upload() and pass in the parent category name and a list of the new subcategories to add. 
If you'd like to instead overwrite the existing subcategories with new subcategories, call upload() and pass in the parameter overwrite_children = True.
End of explanation
"""


categories.rename(name = "month", new_name = "Months")
categories.ids["Months"]

"""
Explanation: To rename a category, call rename(), with parameters name and new_name.
End of explanation
"""


categories.delete({"name": "Months", "children":["February"]})
categories.delete_all([{"name": "Droids", "children": ["bb8", "c3po"]}])

categories.delete("Droids")
categories.delete_all(["Months", "Time of Day"])

categories.ids

"""
Explanation: You can delete categories either individually with delete(), or in bulk with delete_all(). 
You also have the option to delete the entire parent category or just some of the subcategories.  
To delete ALL CATEGORIES in a project, call clear_all_in_project with no parameters. Be careful with this one, and do not use unless you want to delete all categories in the current project.
First let's delete just some subcategories.
End of explanation
"""


tags = BWTags(project)

tags.names

"""
Explanation: Tags
Instantiate a BWTags object by passing in your project as a parameter, which loads all of the tags in your project.
Print out ids to see which tags are currently in your project.
End of explanation
"""


tags.upload(name = "yellow")
tags.upload_all([{"name":"green"}, 
                 {"name":"blue"}, 
                 {"name":"purple"}])

tags.names

"""
Explanation: There are two ways to upload tags: individually and in bulk. When uploading many tags, it is more efficient to use upload_all. 
In upload, pass in the name of the tag.
In upload_all, pass in a list of dictionaries, where each dictionary contains "name" as the key and the tag name as the its value
End of explanation
"""


tags.upload(name = "yellow", new_name = "yellow-orange blend")

tags.names

"""
Explanation: To change the name of a tag, but mantain its id, upload it with keyword arguments name and new_name.
End of explanation
"""


tags.delete("purple")
tags.delete_all(["blue", "green", "yellow-orange blend"])

tags.names

"""
Explanation: As with categories, there are three ways of deleting tags. 
Delete one tag by calling delete and passing in a string, the name of the tag to delete
Delete multiple tags by calling delete_all and passing in a list of strings, where each string is a name of a tag to delete
To delete ALL TAGS in a project, call clear_all_in_project with no parameters. Be careful with this one, and do not use unless you want to delete all tags in the current project
End of explanation
"""


authorlists = BWAuthorLists(project)
authorlists.names

"""
Explanation: Brandwatch Lists
Note: to avoid ambiguity between the python data type "list" and a Brandwatch author list, site list, or location list, the latter is referred to in this demo as a "Brandwatch List."
BWAuthorLists, BWSiteLists, BWLocationLists work almost identically.
First, instantiate your the object which contains the Brandwatch Lists in your project, with your project as a the parameter. This will load the data from your project so you can see what's there, upload more Brandwatch Lists, edit existing Brandwatch Lists, and delete Brandwatch Lists from your project
Printing out ids will show you the Brandwatch Lists (by name and ID) that are currently in your project.
End of explanation
"""


authorlists.upload(name = "Writers", 
                   authors = ["Edward Albee", "Tenessee Williams", "Anna Deavere Smith"])

authorlists.get("Writers")["authors"]

authorlists.upload(name = "Writers", 
                   new_name = "Playwrights", 
                   authors = ["Edward Albee", "Tenessee Williams", "Anna Deavere Smith", "Susan Glaspell"])

authorlists.get("Playwrights")["authors"]

"""
Explanation: To upload a Brandwatch List, pass in a name as a string and the contents of your Brandwatch List as a list of strings. The keyword "authors" is used for BWAuthorLists, shown below. The keyword "domains"is used for BWSiteLists. The keyword "locations" is used for BWLocationLists.
To see the contents of a Brandwatch List, call get_list with the name as the parameter
Uploading is done with either a POST call, for new Brandwatch Lists, or a PUT call, for existing Brandwatch Lists, where the ID of the Brandwatch Lists is mantained, so if you upload and then upload a list with the same name and different contents, the first upload will create a new Brandwatch List, and the second upload will modify the existing list and keep its ID. Similarly, you can change the name of an existing Brandwatch List by passing in both "name" and "new_name"
End of explanation
"""


authorlists.add_items(name = "Playwrights", 
                      items = ["Eugene O'Neill"])

authorlists.get("Playwrights")["authors"]

"""
Explanation: To add items to a Brandwatch List without reentering all of the existing items, call add_items
End of explanation
"""


authorlists.names

authorlists.delete("Playwrights")

authorlists.names

"""
Explanation: To delete a Brandwatch List, pass in its name. Note the ids before the Brandwatch List is deleted, compared to after it is deleted. The BWLists object is updated to reflect the Brandwatch Lists in the project after each upload and each delete
End of explanation
"""


rules = BWRules(project)
rules.names

"""
Explanation: The only difference between how you use BWAuthorlists compared to how you use BWSiteLists and BWLocationLists is the parameter which is passed in. 
BWAuthorlists:
authors = ["edward albee", "tenessee williams", "Anna Deavere Smith"]
BWSiteLists:
domains = ["github.com", "stackoverflow.com", "docs.python.org"]
*BWLocationLists:
locations = [{"id": "mai4", "name": "Maine", "type": "state", "fullName": "Maine, United States, North America"}, 
{"id": "verf", "name": "Vermont", "type": "state", "fullName": "Vermont, United States, North America"},  
{"id": "rho4", "name": "Rhode Island", "type": "state", "fullName": "Rhode Island, United States, North America"} ]
*Requires dictionary of location data instead of a string
Rules
Instantiate a BWRules object by passing in your project as a parameter, which loads all of the rules in your project.
Print out names and IDs to see which rules are currently in your project.
End of explanation
"""


filters = rules.filters(queryName = "ice cream cake", 
                        sentiment = "positive", 
                        twitterVerified = False, 
                        impactMin = 50, 
                        xprofession = ["Politician", "Legal"])

filters = rules.filters(queryName = ["Australian Animals", "ice cream cake"], 
                        search = '"cat food" OR "dog food"')

"""
Explanation: Every rule must have a name, an action, and filters.
The first step to creating a rule through the API is to prepare filters by calling filters(). 
If your desired rules applies to a query (or queries), include queryName as a filter and pass in a list of the queries you want to apply it to.
There are over a hundred filters you can use to only download the mentions that qualify. See the full list in the file filters.py.  Here, different filters are used, which take different data types. filters.py details which data type is used with each filter. Some filters, like sentiment and xprofession below, have a limited number of settings to choose from.  You can filter many things by inclusion or exclusion. The x in xprofession stands for exclusion, for example.
If you include search terms, be sure to use nested quotes - passing in "cat food" will result in a search that says cat food (i.e. cat AND food)
End of explanation
"""


action = rules.rule_action(action = "addTag", 
                           setting = ["animal food"])

"""
Explanation: The second step is to prepare the rule action by calling rule_action().
For this function, you must pass in the action and setting.  Below I've used examples of adding categories and tags, but you can also set sentiment or workflow (as in the front end).
If you pass in a category or tag that does not yet exist, it will be automatically uploaded for you.
End of explanation
"""


rules.upload(name = "rule", 
             scope = "query", 
             filter = filters, 
             ruleAction = action,
             backfill = True)

"""
Explanation: The last step is to upload!
Pass in the name, filters, and action.   Scope is optional - it will default to query if queryName is in the filters and otherwise be set to project.  Backfill is also optional - it will default to False.
The upload() function will automatically check the validity of your search string and give a helpful error message if errors are found.
End of explanation
"""


filters1 = rules.filters(search = "caknvfoga;vnaei")
filters2 = rules.filters(queryName = ["Australian Animals"], search = "(bloop NEAR/10 blorp)")
filters3 = rules.filters(queryName = ["Australian Animals", "ice cream cake"], search = '"hello world"')

action1 = rules.rule_action(action = "addCategories", setting = {"Example": ["One"]})
action2 = rules.rule_action(action = "addTag", setting = ["My Example"])

"""
Explanation: You can also upload rules in bulk.  Below we prepare a bunch of filters and actions at once.
End of explanation
"""


rule1 = rules.rule(name = "rule1", 
                   filter = filters1, 
                   action = action1, 
                   scope = "project")

rule2 = rules.rule(name = "rule2", 
                   filter = filters2, 
                   action = action2)

rule3 = rules.rule(name = "rule3", 
                   filter = filters3, 
                   action = action1,
                   backfill = True)

rules.upload_all([rule1, rule2, rule3])

"""
Explanation: When uploading in bulk, it is helpful (but not necessary) to use the rules() function before uploading in order to keep the dictionaries organized.
End of explanation
"""


rules.delete(name = "rule")
rules.delete_all(names = ["rule1", "rule2", "rule3"])

rules.names

"""
Explanation: As with other resources, we can delete, delete_all or clear_all_in_project
End of explanation
"""


signals = BWSignals(project)

signals.names

"""
Explanation: Signals
Instantiate a BWSignals object by passing in your project as a parameter, which loads all of the signals in your project.
Print out ids to see which signals are currently in your project.
End of explanation
"""


signals.upload(name= "New Test",
               queries= ["ice cream cake"],
               parentCategory = ["Colors"],
               subscribers= [{"emailAddress": "test12345@brandwatch.com", "notificationThreshold": 1}])

signals.upload_all([{"name": "Signal Me",
                    "queries": ["ice cream cake"],
                    "category": {"Colors": ["Blue", "Yellow"]},
                    "subscribers": [{"emailAddress": "testaddress123@brandwatch.com", "notificationThreshold": 3}]},
                   {"name": "Signal Test",
                    "queries": ["ice cream cake"],
                    "tag": ["Tastes Good"],
                    "subscribers": [{"emailAddress": "exampleemail@brandwatch.com", "notificationThreshold": 2}]}])

signals.names

"""
Explanation: Again, we can upload signals individually or in batch.
You must pass at least a name, queries (list of queries you'd like the signal to apply to) and subscribers.  For each subscriber, you have to pass both an emailAddress and notificationThreshold.  The notificationThreshold will be a number 1, 2 or 3 - where 1 means send all notifications and 3 means send only high priority signals.
Optionally, you can also pass in categories or tags to filter by.  As before, you can filter by an entire category with the keyword parentCategory or just a subcategory (or list of subcategories) with the keyword category.  An example of how to pass in each filter is shown below.
End of explanation
"""


signals.delete("New Test")
signals.delete_all(["Signal Me", "Signal Test"])

signals.names

"""
Explanation: Signals can be deleted individually or in bulk.
End of explanation
"""


mentions = BWMentions(project)

filtered = queries.get_mentions(name = "ice cream cake", 
                                startDate = start, 
                                endDate = today,
                                parentCategory = ["Colors", "Days"],
                                category = {"Colors": ["Blue", "Yellow"], 
                                            "Days": ["Monday"]}, 
                                tag = ["Tastes Good"])

"""
Explanation: Patching Mentions
To patch the metadata on mentions, whether those mentions come from queries or from groups, you must first instantiate a BWMentions object and pass in your project as a parameter.
End of explanation
"""


mentions.patch_mentions(filtered[0:10], action = "addTag", setting = ["cold"])

mentions.patch_mentions(filtered[5:12], action = "starred", setting = True)

mentions.patch_mentions(filtered[6:8], action = "addCategories", setting = {"color":["green", "blue"]})

"""
Explanation: if you don't want to upload your tags and categories ahead of time, you don't have to! BWMentions will do that for you, but if there are a lot of differnet tags/categories, it's definitely more efficient to upload them in bulk ahead of time
For this example, i'm arbitrarily patching a few of the mentions, rather than all of them
End of explanation
"""


