GithubHelp home page GithubHelp logo

tableau / server-client-python Goto Github PK

View Code? Open in Web Editor NEW
633.0 58.0 413.0 5.39 MB

A Python library for the Tableau Server REST API

Home Page: https://tableau.github.io/server-client-python/

License: MIT License

Python 99.98% Shell 0.02%
python rest-api tableau tableau-api

server-client-python's Introduction

Tableau Server Client (Python)

Tableau Supported Build Status FOSSA Status

Use the Tableau Server Client (TSC) library to increase your productivity as you interact with the Tableau Server REST API. With the TSC library you can do almost everything that you can do with the REST API, including:

  • Publish workbooks and data sources.
  • Create users and groups.
  • Query projects, sites, and more.

This repository contains Python source code for the library and sample files showing how to use it. As of May 2022, Python versions 3.7 and up are supported.

To see sample code that works directly with the REST API (in Java, Python, or Postman), visit the REST API Samples repo.

For more information on installing and using TSC, see the documentation: https://tableau.github.io/server-client-python/docs/

License

FOSSA Status

server-client-python's People

Contributors

a-torres-2 avatar bcantoni avatar benlower avatar d45 avatar dependabot[bot] avatar dzucker-tab avatar ffmmm avatar gaoang2148 avatar graysonarts avatar guodah avatar jacalata avatar jdomingu avatar jorwoods avatar jz-huang avatar kykrueger avatar lbrendanl avatar lgraber avatar martinbpeters avatar mbren avatar mmuttreja-tableau avatar nnevalainen avatar opus-42 avatar ovinis avatar preguraman avatar scuml avatar shinchris avatar sotnich avatar t8y8 avatar talvalin avatar vogelsgesang 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

server-client-python's Issues

All Requests Return 401 from real servers

The refactoring in #87 has a bug in our header handling.

    @staticmethod
    def _make_common_headers(auth_token, content_type):
        retval = {}
        if auth_token is not None:
            retval['x-tableau-auth'] = auth_token
        if content_type is not None:
            retval['content-type'] = content_type

There's no return -- so the x-tableau-auth never gets updated, and all calls are missing the token.

I can whip up a patch.

/cc @RussTheAerialist

AuthEndpoint should not construct baseurl in constructor

All other endpoints were updated to use a pattern where the url is built when it was used. This endpoint was not. The reason we build it at the time of use is so that the version number can technically be changed at different times and automatically be reflected in the next request. We should follow the same pattern everywhere.

How to contribute changes?

Would it help new contributors if we stated the the code contribution model is based on a Fork and Pull model? I am new to contributing so this might be redundant information for more experienced developers but I notice that there is another approach (Shared Repo) described here: [https://help.github.com/articles/about-collaborative-development-models/]

Convert dates to py datetime

Is there any reason why this module doesn't convert dates to python datetime objects? Things like last logged in / last updated etc.

Refreshing get() lists

I couldn't find this in the docs so apologies for raising here.

Let's say I've queried the list of projects from my server, then added a new project. Then I want to refresh the list, but re-running get() doesn't update the list.

How do I make this work?

` with server.auth.sign_in(tableau_auth):
## get the list of projects from Tableau
projects, pagination_info = server.projects.get()

    # Find local projects which do not exist on Tableau
    for p in (x for x in local_projects if x not in (p.name for p in projects)):
            # Add Project
            new_project = TSC.ProjectItem(name=p)
            try:
                project_id = server.projects.create(new_project).id
            except TSC.ServerResponseError as e:
                #TODO
                pass

    projects, pagination_info = server.projects.get()
    for p in projects:
        print(p.name, p.id)`

This last iteration is returning the original list of projects, not the new one.

TravisCI runs have PEP440 Warning

/home/travis/virtualenv/python3.3.5/lib/python3.3/site-packages/pkg_resources/__init__.py:2510:
PEP440Warning: 'requests (mock-0.2.0)' is being parsed as a legacy, non PEP 440, version.
You may find odd behavior and sort order. In particular it will be sorted as less than 0.0. 
It is recommend to migrate to PEP 440 compatible versions.

Shows for Py2 and P3 runs.

Don't know what it means yet

setup.py test doesn't pass on clean repo clone

Python 3.5:

[1/1] test
test test crashed -- Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/test/regrtest.py", line 1292, in runtest_inner
    the_module = importlib.import_module(abstest)
  File "/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 986, in _gcd_import
  File "<frozen importlib._bootstrap>", line 969, in _find_and_load
  File "<frozen importlib._bootstrap>", line 956, in _find_and_load_unlocked
ImportError: No module named 'test.test'

1 test failed:
    test

Python 2.7:

running build_ext
Traceback (most recent call last):
  File "setup.py", line 19, in <module>
    'requests-mock>=1.0,<1.1a0'
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/distutils/core.py", line 151, in setup
    dist.run_commands()
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/distutils/dist.py", line 953, in run_commands
    self.run_command(cmd)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/distutils/dist.py", line 972, in run_command
    cmd_obj.run()
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/setuptools/command/test.py", line 138, in run
    self.with_project_on_sys_path(self.run_tests)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/setuptools/command/test.py", line 118, in with_project_on_sys_path
    func()
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/setuptools/command/test.py", line 164, in run_tests
    testLoader = cks
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/main.py", line 94, in __init__
    self.parseArgs(argv)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/main.py", line 149, in parseArgs
    self.createTests()
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/main.py", line 158, in createTests
    self.module)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/loader.py", line 130, in loadTestsFromNames
    suites = [self.loadTestsFromName(name, module) for name in names]
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/loader.py", line 103, in loadTestsFromName
    return self.loadTestsFromModule(obj)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/setuptools/command/test.py", line 35, in loadTestsFromModule
    tests.append(self.loadTestsFromName(submodule))
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/loader.py", line 91, in loadTestsFromName
    module = __import__('.'.join(parts_copy))
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/test/autotest.py", line 5, in <module>
    from test import regrtest
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/test/regrtest.py", line 183, in <module>
    for module in sys.modules.itervalues():
RuntimeError: dictionary changed size during iteration

Deleting a site results in a signout error

I'm deleting a site using code like this:

tableau_auth = TSC.TableauAuth(args.username, password, site=args.contentUrl)
with server.auth.sign_in(tableau_auth):
    server.sites.delete(site_id)
    print("\nSite deleted.")

Note that in order to delete a site, I have to sign into that site by including the contentUrl. (The site_id value was fetched earlier in the program from which I take this snippet.) This code works--the site is deleted.

However, at the termination of the with block, the code implicitly attempts to sign out from the site. But the site doesn't exist any more, so the signout fails and throws all sorts of errors.

Workbook update does too much if all I did was change the tag set

I like our model of simplifying the model so that tags are just a set on the workbook and you can modify the set and then simply call "update". However, we are not tracking whether anything else changed on the workbook and so are calling "update" on the general workbook properties always which makes changing the tags unnecessarily expensive. We need to have some _dirty boolean that is set to false after parsing the results and flipped when any property is set. Then we can avoid unnecessary calls when nothing has changed.

"from_response" should not be on TableauAuth object

You must have missed this from an earlier review. "from_response" implies that it is constructing the class object. This code is simply parsing the response of "signin" and is not constructing a TableauAuth object. Please just move this to the "signin" method.

Site creation fails when using Japanese characters for site name

Trying to create a site with the following:

    sites = "遊び場"
    new_site = TSC.SiteItem(name=site, content_url=site.replace(" ", ""), admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers)
    server.sites.create(new_site)

This errors with:

409002: Resource Conflict
A site already exists with the content URL '遊び場'

The site doesn't exist on my server.

How to Add Connection Credentials to Publish Workbook

I have a workbook with an embedded live connection. On publish I need to add the credential password otherwise the publish fails. This implies that Embed is defaulting to true but I don't want to prompt anyway.

Clarify what TableauAuth site really is

Tableau Server sites have a site name and site id under site settings.

You can have a site name like "My Cool Site" and the side ID will have spaces removed (i.e. "MyCoolSite").

It's not clear what should be used when signing in with the client library. When I try to use tableau_auth.site = "My Cool Site" I get a 401001 Signin Error. However, when I use "MyCoolSite" things work.

We should clarify this via docs and/or having property names match what's in Tableau Server.

How can we query workbooks from a non-default project?

server.workbooks.get() will get me all 121 workbooks in our default project. How can we specify to return all of the workbooks from a non-default project? I see that we can filter the returned results with TSC.RequestOptions() but I dont see how that could be used to specify the project to pull from.

Can't set password for new user?

Sorry if this is an old issue. (I searched but didn't find it.) I can create a new user this way:

new_user = server.users.add(name, siteRole)

The add function has this signature:

def add(self, user_item)

The UserItem constructor explicitly sets the password property like this:

def __init__(self, name, site_role, auth_setting=None):
        self._auth_setting = None
        self._domain_name = None
        self._external_auth_user_id = None
        self._id = None
        self._last_login = None
        self._name = None
        self._site_role = None
        self._workbooks = None
        self.email = None
        self.fullname = None
        self.password = None

And as near as I can tell (??) there is no setter in the UserItem class for a password property.

I tried doing this, which does compile and run:

new_user.password = args.newpassword
print("\nPassword changed to {0}".format(new_user.password))
new_user = server.users.update(new_user)

The server.users.update(new_user) call did result in a PUT request, but per Fiddler, the PUT request had only this in it (no password):

<tsRequest><user authSetting="ServerDefault" siteRole="SiteAdministrator" /></tsRequest>

In the UserRequest class (request_factory.py), there's a definition for an update_req method that would seem to take a password:

def update_req(self, user_item, password=''):
...
    if password:
        user_element.attrib['password'] = password

But I can't tell how to reach this method directly. So I'm stuck with being able to create a user but not assign a password. (And can't sign in as that user.)

Can I keep a session alive and use an auth token for subsequent requests?

I started toying with creating a portal and I couldn't find a way to keep a session alive.

As an example:
I send a request to get a list of my workbooks.
I send a follow-up request with a particular workbook id (on a different web page) to get the list of views for that workbook.

Do I have to call the auth.sign_in function each time I send a new request? It's not ideal if I have to store the username and password somewhere server-side to do this--I would prefer to store the auth token and just use that with subsequent requests.

Memory Error downloading large extract data sources

Hi,

I am using the module to download data sources, and publish them to another server instance. Most data sources download fine, but larger extracts give the error below. Have you seen this before?
I can provide the code if you need it.
There is no memory spike on either the Tableau Server, or the machine I am running the code.

Thanks.

Traceback (most recent call last):
File "tableauserver.py", line 44, in
prod.datasources.download(datasource.id)
File "C:\Python27\lib\site-packages\tableauserverclient\server\endpoint\dataso
urces_endpoint.py", line 67, in download
server_response = self.get_request(url)
File "C:\Python27\lib\site-packages\tableauserverclient\server\endpoint\endpoi
nt.py", line 54, in get_request
request_object=request_object)
File "C:\Python27\lib\site-packages\tableauserverclient\server\endpoint\endpoi
nt.py", line 34, in _make_request
server_response = method(url, **parameters)
File "C:\Python27\lib\site-packages\requests\sessions.py", line 488, in get
return self.request('GET', url, **kwargs)
File "C:\Python27\lib\site-packages\requests\sessions.py", line 475, in reques
t
resp = self.send(prep, **send_kwargs)
File "C:\Python27\lib\site-packages\requests\sessions.py", line 628, in send
r.content
File "C:\Python27\lib\site-packages\requests\models.py", line 755, in content
self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes
()
MemoryError

Avoid common keywords as parameters

'input' and 'file' are both keywords and it's usually better to avoid them, especially in 'with' statements

They're used all over the codebase

Design Proposal: Change pagination_items in returns from lists

All of the model.get() calls return PaginationItem, List[<model>] which means there's a lot of

_, all_workbooks  = server.workbooks.get()

I propose that we either:

  1. Don't return the PaginationItem because you, the caller, already know your pagination parameters, since you applied them when you issued the 'get' AND you can't do anything interesting with them after the fact (the most interesting item, the total number, is available idiomatically as len(all_workbooks)
  2. Switch the order of the items in the return to that it's (List[], PaginationItem)

Design Proposal: Move 'populate methods' to the parent objects from the Endpoints

Today there are several properties that are unpopulated (views on workbooks, connections in workbooks) until you call a populate method.

That makes sense, it requires a new API call.

However, the logic is hidden up on the endpoint and not on the object itself. I think it's a little unintuitive.

Example:

_, wbs = server.workbooks.get()
wb = wbs.pop()
wb.name
>>> '1234-5678-123456'
wb.views
>>> UnpopulatedPropertyError

To get that populated you would call

server.workbooks.populate_views(wb)

But it feels a bit weird, because the method sort of implies I'm populating on the workbooks endpoint.

More naturally, it could be

wb.populate_views()
wb.views
>>> [<view obj>, <view obj>]

Or even call that automatically on access of wb.views for the first time

Pattern for checking if value is member of enum should be abstracted in a decorator

We have the same ~4ish lines of code in a number of places which attempt to make sure that some value being assigning to a class property is a member of some enum. And if not, we construct an error message and throw. This should be hidden in a decorator to make the code a bit more readable and also gaurantee some consistency in our error handling.

does populate_connections work for 2.2 version of the REST API?

I'm having trouble getting the connection information for a data source. I'm using Tableau Server 9.3, and specifying the 2.2 version of the REST API when working with the python module.

I'm getting the following error when trying to use populate_connections, and reading the 2.2 REST API documentation, it seems like you can update a data sources connection, but only query a workbooks connection details. Can you confirm this is correct?

404003: Resource Not Found

What i'm trying to achieve is a migration of data sources from server A to server B, but I need to query the data source's connection information in order for me to set the correct connection information before re-publishing to the destination server.

Thanks.

Paul

Redundant code in most __init__ functions

A lot of our models have

# Invoke setter
        self.name = name
        self.site_role = site_role

        if auth_setting:
            # In order to invoke the setter method for auth_setting,
            # _auth_setting must be initialized first
            self.auth_setting = auth_setting

So they set the _var to None and then set it again a few lines later to the item passed in to __init__ -- but in python the class is fully constructed by the time __init__ gets called, so we can just call the setter directly from there, no need to set to None

http://stackoverflow.com/questions/3941919/how-to-set-a-python-property-in-init

@RussTheAerialist if you don't object I want to make a PR that cleans this up -- less code makes the classes easier to read to me, so I can distinguish between what actually needs to be None vs what's getting set when you instantiate the class.

PEP8 Compliance

We're actually pretty close!

./setup.py:23:2: W292 no newline at end of file
./samples/explore_datasource.py:34:1: E266 too many leading '#' for block comment
./samples/explore_datasource.py:76:1: W391 blank line at end of file
./samples/explore_workbook.py:35:1: E266 too many leading '#' for block comment
./tableauserverclient/namespace.py:1:44: W292 no newline at end of file
./tableauserverclient/models/datasource_item.py:116:29: E128 continuation line under-indented for visual indent
./tableauserverclient/models/exceptions.py:2:9: W292 no newline at end of file
./tableauserverclient/models/user_item.py:147:29: E128 continuation line under-indented for visual indent
./tableauserverclient/models/workbook_item.py:166:29: E128 continuation line under-indented for visual indent
./tableauserverclient/server/exceptions.py:2:9: W292 no newline at end of file
./tableauserverclient/server/endpoint/projects_endpoint.py:55:1: W391 blank line at end of file
./test/test_request_option.py:99:1: W391 blank line at end of file
./test/test_view.py:65:112: W292 no newline at end of file

Test Coverage for unit tests

Not too bad all things considered, but there are some notable holes we should probably add extra coverage to.

Name                                                          Stmts   Miss  Cover
---------------------------------------------------------------------------------
tableauserverclient/__init__.py                                   5      0   100%
tableauserverclient/models/__init__.py                           15      0   100%
tableauserverclient/models/connection_credentials.py             11      5    55%
tableauserverclient/models/connection_item.py                    38      0   100%
tableauserverclient/models/datasource_item.py                   104      5    95%
tableauserverclient/models/exceptions.py                          2      0   100%
tableauserverclient/models/fileupload_item.py                    17     10    41%
tableauserverclient/models/group_item.py                         39      0   100%
tableauserverclient/models/interval_item.py                     112      9    92%
tableauserverclient/models/pagination_item.py                    22      0   100%
tableauserverclient/models/project_item.py                       59      1    98%
tableauserverclient/models/property_decorators.py                63      6    90%
tableauserverclient/models/schedule_item.py                     145      5    97%
tableauserverclient/models/server_info_item.py                   21      0   100%
tableauserverclient/models/site_item.py                         147     14    90%
tableauserverclient/models/tableau_auth.py                       14      6    57%
tableauserverclient/models/tag_item.py                           13      0   100%
tableauserverclient/models/user_item.py                         111      4    96%
tableauserverclient/models/view_item.py                          56      6    89%
tableauserverclient/models/workbook_item.py                     146      8    95%
tableauserverclient/namespace.py                                  1      0   100%
tableauserverclient/server/__init__.py                            9      0   100%
tableauserverclient/server/endpoint/__init__.py                  12      0   100%
tableauserverclient/server/endpoint/auth_endpoint.py             34      3    91%
tableauserverclient/server/endpoint/datasources_endpoint.py     100     21    79%
tableauserverclient/server/endpoint/endpoint.py                  40      0   100%
tableauserverclient/server/endpoint/exceptions.py                16      0   100%
tableauserverclient/server/endpoint/fileuploads_endpoint.py      46     31    33%
tableauserverclient/server/endpoint/groups_endpoint.py           78      4    95%
tableauserverclient/server/endpoint/projects_endpoint.py         40      0   100%
tableauserverclient/server/endpoint/schedules_endpoint.py        46      8    83%
tableauserverclient/server/endpoint/server_info_endpoint.py      11      0   100%
tableauserverclient/server/endpoint/sites_endpoint.py            59      4    93%
tableauserverclient/server/endpoint/users_endpoint.py            60      1    98%
tableauserverclient/server/endpoint/views_endpoint.py            23      0   100%
tableauserverclient/server/endpoint/workbooks_endpoint.py       137     12    91%
tableauserverclient/server/exceptions.py                          2      0   100%
tableauserverclient/server/filter.py                             19      3    84%
tableauserverclient/server/pager.py                              25      2    92%
tableauserverclient/server/request_factory.py                   273     42    85%
tableauserverclient/server/request_options.py                    41      1    98%
tableauserverclient/server/server.py                             64      6    91%
tableauserverclient/server/sort.py                                6      3    50%
---------------------------------------------------------------------------------
TOTAL                                                          2282    220    90%

Clarify that adding tags requires a list

When calling tags.update("foobar") the result is the workbook being tagged with five tags: 'f', 'o', 'b', 'a', 'r'. To get the expected behavior you have to use tags.update(["foobar"]) because it expects a list. Clarify this.

How can we Query Workbook for a given filter

Hello,

Is there a way to download all the views (as png or pdf) for a given workbook, filtered by a given dimension value?

For instance: If the workbook has a filter on dimension device type (values = smartphone, desktop and tablet), then query this workbook for a given dimension (say smartphone) and download the png. ?

Thanks
Sid

Why do I need to populate a Group with Users to add a new user?

After about an hour of work, I am finally able to successfully add a new user and then add them to an existing group. However, I was perplexed by receiving an error about the Group not being populated:

My initial code:

with server.auth.sign_in(auth):
user = TSC.UserItem('stone_cold', 'Publisher')
new_user = server.users.add(user)
gs, pi = server.groups.get()
for g in gs:
if g.name == 'Ballers':
server.groups.add_user(g, new_user.id)

Working code:
with server.auth.sign_in(auth):
user = TSC.UserItem('stone_cold', 'Publisher')
new_user = server.users.add(user)
gs, pi = server.groups.get()
for g in gs:
if g.name == 'Ballers':
server.groups.populate_users(g)
server.groups.add_user(g, new_user.id)

Why do I need to use the populate_users() method on the Group just to add a user to it? As a second issue, why is populate_users() a Groups endpoint method and not a method of the GroupItem itself, where it could be called automatically prior to the attempt to add?

server.users.get() returns error if you have Guest user enabled

If you have guest user enabled, it's site_role is "Guest", if you call server.users.get(), you get an error "ValueError: Invalid value: Guest. site_role must be of type Roles."
Presumably, this is because tableauserverclient.Roles does not include "Guest", but not sure if the solution is to ignore guest in get() or to include "Guest" in Roles.

samples should not have two logging options

It was nice to expose this but it should be a single option, ie "loglevel" with values of [info, debug, ... whatever else is supported]. Giving two options where the user could set both of them is odd.

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.