photo / openphoto-python Goto Github PK
View Code? Open in Web Editor NEWA Python OAuth client for OpenPhoto
Home Page: http://theopenphotoproject.org
License: Apache License 2.0
A Python OAuth client for OpenPhoto
Home Page: http://theopenphotoproject.org
License: Apache License 2.0
This endpoint is not used and has been removed from the frontend development branch. See #34 comments for more details.
The tag/album/photo delete methods really ought to return the "True/False" return value from the API call.
I'm not sure if the API would ever return False without also raising an error code (which would raise an exception), but it's worth making this consistent anyway.
setuptools and distribute allow you to python setup.py develop
as well as to install dependencies; i.e. we require oauth2 (https://github.com/openphoto/openphoto-python/blob/master/setup.py#L9) but it is not actually installed with python setup.py install. I would recommend just requiring setuptools or distribute, but you can also optionally support them in the same setup.py; see https://github.com/pypa/virtualenv/blob/develop/setup.py for example.
I'm happy to do this if there is interest (or at least no dissent)
in my config file I wrote the host without "https" and I get this error :
Traceback (most recent call last):
File "./import.py", line 23, in <module>
resp = client.get("/photos/list.json")
File "/usr/local/lib/python2.6/dist-packages/trovebox/http.py", line 92, in get
return self._process_response(response)
File "/usr/local/lib/python2.6/dist-packages/trovebox/http.py", line 188, in _process_response
(response.status_code, response.reason))
trovebox.errors.Trovebox404Error: HTTP Error 404: Not Found
I can correct this by replacing "http" with "https" at line 69 in http.py : url = urlunparse(('http', self.host, endpoint, '', '', ''))
The problem is this urlunparse.
We probably need a scheme parsing in http.init to avoid the use of "http" hard written in http.py
.
so we perhaps need a
The library doesn't seem to contain any copyright or license information.
Can you make sure to assert your rights and clarify licensing so that others can safely use it (or not) ?
Thanks in advance.
Best regards
Let's add TravisCI to automatically run the unit tests for commits and merges.
Reported here: https://groups.google.com/forum/#!search/openphoto-python/openphoto/haSmoFWxgqM/tfKJs3sgxqkJ
Specifically, uploading photos with unicode tags doesn't work. It looks like if a list is provided, the individual parameters don't get utf-8 encoded.
Hi,
I'm not sure it's an issue due to trovebox or to my OS, but in auth.py it can't read my config file anymore.
I changed it to :
with open(config_path, "r") as conf:
res = conf.read()
buf.write(unicode(res))
and it works now.
It seems to me it is some kind of issue with having python3 and python2.7 on the same system. With StringIO I can't use the "open" method.
Ideally this follows the same pattern(s) as the php client. It used https://github.com/jrconlin/oauthsimple/ which has a python component as well. Something to look into.
https://github.com/openphoto/openphoto-php for reference.
As mentioned in #37, the majority of the current tests are integration tests, communicating via the API with a photo/frontend server. Multiple test servers can be used to validate against different server/API versions.
While this is a useful strategy for flushing out issues and generally making sure the API doesn't accidentally get broken, it would also be useful to have a wider variety of unit tests that don't require a server connection.
In particular:
The goal should be to mock at the socket level (eg. Httpretty).
Once we have good unit test coverage, we can use Travis to run unit tests, and run the integration tests manually as required.
trovebox -c test -X POST -e /photo/upload.json -F photo=@~/Pictures/opmeqnqXBX-28cbef_960x180.jpg
Traceback (most recent call last):
File "/usr/local/bin/trovebox", line 9, in <module>
load_entry_point('trovebox==0.5', 'console_scripts', 'trovebox')()
File "/usr/local/lib/python2.7/dist-packages/trovebox/main.py", line 90, in main
params, files = extract_files(params)
File "/usr/local/lib/python2.7/dist-packages/trovebox/main.py", line 126, in extract_files
files[name] = open(params[name][1:], 'rb')
IOError: [Errno 2] No such file or directory: '~/Pictures/opmeqnqXBX-28cbef_960x180.jpg'
But it works if /home/pete/file.jpg is used instead of ~/file.jpg
Hi,
When I try to connect to my self hosted trovebox site, I get this error.
Traceback (most recent call last): File "./import.py", line 24, in <module> resp = client.get("/photos/list.json") File "/usr/local/lib/python2.6/dist-packages/trovebox/http.py", line 81, in get response = session.get(url, params=params, auth=auth, verify=False) File "/usr/local/lib/python2.6/dist-packages/requests/sessions.py", line 254, in get return self.request('get', url, **kwargs) File "/usr/local/lib/python2.6/dist-packages/requests/sessions.py", line 241, in request r.send(prefetch=prefetch) File "/usr/local/lib/python2.6/dist-packages/requests/models.py", line 518, in send r = self.auth(self) File "/usr/local/lib/python2.6/dist-packages/requests_oauthlib/core.py", line 58, in __call__ if is_form_encoded or extract_params(r.body): AttributeError: 'Request' object has no attribute 'body'
I can't understand why but I suspect that it is because of https + self signed certificate, and I can't find a way to disable certificate checking.
I started using this (excellent!) Python library, and pretty quickly found myself wrapping the raw GET/POST calls up into Python objects.
So instead of:
resp = client.get("/photos/list.json")
resp = client.post("/photo/62/update.json", {'tags': 'tag1,tag2'})
I can:
photos = client.photos_list()
photos[0].update(tags=["tag1", "tag2"])
This would hopefully make the library easier to use, since it abstracts the API URL constructions and responses.
@jmathai, @walkah: what do you think? Is this useful, or beyond what we should be doing here?
Hello,
when I try to upload a photo by command line I have this error from the server:
{
"code":500,
"message":"Invalid mime type",
"result":false
}
I exec this command line:
openphoto -p -X POST -H akirasan.trovebox.com -e /photo/upload.json -F 'photo=/mnt/ksi2t/scripts/DSC_8422.JPG' -F 'tags=test'
The file is a jpeg image. Others request to the server trovebox.com are succesfull.
Regards,
akirasan
From: https://gist.github.com/1732810
Processing 906 of 3975 2768847584.json ...
Traceback (most recent call last):
File "import.py", line 66, in <module>
main()
File "import.py", line 44, in main
resp = client.post('/photo/upload.json', params)
File "/Library/Python/2.7/site-packages/openphoto-0.1-py2.7.egg/openphoto/__init__.py", line 41, in post
body = urllib.urlencode(params)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 1294, in urlencode
v = quote_plus(str(v))
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe7' in position 77: ordinal not in range(128)
Currently the openphoto script gets the credentials from rather generically-named environment variables:
https://github.com/openphoto/openphoto-python/blob/master/README.markdown
If you're very often running this program, it is probably better to have these secrets live in a dotfile instead of having a two-step process to run the script (source a script then run openphoto). I would advise looking for a dotfile, e.g. $HOME/.openphoto, which contains the credentials and overriding from the environment if the environment variables exist
I've had a crack at this, with the aim of supporting both Python 2 and 3 from the same codebase. This involved:
As a result of the above, we can support Python 2.6, 2.7 and 3.3. Maintaining support for 2.5 at the same time as 3.x is difficult (and messy).
To avoid conflicts, I'd like to wait until the last few PRs are merged before doing any more.
Any comments?
I tried to upload a file using the command line tool:
trovebox -p -X POST -e /photo/upload.json -F 'photo=@/Users/me/Dropbox/Images/Älgkalv/ÄLGKALV.jpg'
But if the filename contains non ascii characters it fails with the following error message:
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 10: ordinal not in range(128)
Non ascii characters in the path is no problem:
trovebox -p -X POST -e /photo/upload.json -F 'photo=@/Users/me/Dropbox/Images/Älgkalv/ALGKALV.jpg'
I edited main.py line 126:
files[name] = open(os.path.expanduser(params[name][1:].decode('utf-8')), 'rb')
This solves the UnicodeDecodeError, but then the authention fails:
Traceback (most recent call last):
File "/usr/local/bin/trovebox", line 9, in
load_entry_point('trovebox==0.6.1', 'console_scripts', 'trovebox')()
File "/Library/Python/2.7/site-packages/trovebox/main.py", line 93, in main
files=files, **params)
File "/Library/Python/2.7/site-packages/trovebox/http.py", line 180, in post
(response.status_code, response.reason))
trovebox.errors.TroveboxError: HTTP Error 401: Unauthorized
if i check the apache error log, this is what i see when the authentication fails:
[Mon Dec 09 19:30:40.403222 2013] [:error] [pid 4995] [client 192.168.5.106:57220] {severity:warn, description:"oauth_problem=signature_invalid&debug_sbs=POST&http%3A%2F%2Fphoto.majsajt.com%2Fphoto%2Fupload.json&oauth_consumer_key%3F73048bee39a55e5db2a4a97fe421f5%26oauth_nonce%3D2567566948544325531386613844%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1386613844%26oauth_token%3D4f8f8d5a960af9e64fc2529075ffac%26oauth_version%3D1.0%26photo%3D%25FF%25D8%25FF%25E1Q%25CCExif%2500%2500II%252A%2500%2508%2500%2500%2500%250A%2500%250F%2501%2502%2500%2506%2500%2500%2500%2586%2500%2500%2500%2510%2501%2502%2500%250E%2500%2500%2500%258C%2500%2500%2500%2512%2501%2503%2500%2501%2500%2500%2500%2501%2500%2500%2500%251A%2501%2505%2500%2501%2500%2500%2500%25AC%2500%2500%2500%251B%2501%2505%2500%2501%2500%2500%2500%25B4%2500%2500%2500%2528%2501%2503%2500%2501%2500%2500%2500%2502%2500%2500%25002%2501%2502%2500%2514%2500%2500%2500%25BC%2500%2500%2500%2513%2502%2503%2500%2501%2500%2500%2500%2502%2500%2500%2500i%2587%2504%2500%2501%2500%2500%2500%25D0%2500%2500%2500%2525%2588%2504%2500%2501%2500%2500%2500%25DE%2521%2500%2500%25E0%2528%2500%2500Canon%2500Canon%2520EOS%252040D%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500H%2500%2500%2500%2501%2500%2500%2500H%2500%2500%2500%2501%2500%2500%25002008%253A05%253A24%252014%253A15%253A01%2500%251F%2500%259A%2582%2505%2500%2501%2500%2500%2500J%2502%2500%2500%259D%2582%2505%2500%2501%2500%2500%2500R%2502%2500%2500%2522%2588%2503%2500%2501%2500%2500%2500%2502%2500%2500%2500%2527%2588%2503%2500%2501%2500%2500%2500%2590%2501%2500%2500%2500%2590%2507%2500%2504%2500%2500%25000221%2503%2590%2502%2500%2514%2500%2500%2500Z%2502%2500%2500%2504%2590%2502%2500%2514%2500%2500%2500n%2502%2500%2500%2501%2591%2507%2500%2504%2500%2500%2500%2501%2502%2503%2500%2501%2592%250A%2500%2501%2500%2500%2500%2582%2502%2500%2500%2502%2592%2505%2500%2501%2500%2500%2500%258A%2502%2500%2500%2504%2592%250A%2500%2501%2500%2500%2500%2592%2502%2500%2500%2507%2592%2503%2500%2501%2500%2500%2500%2505%2500%2500%2500%2509%2592%2503%2500%2501%2500%2500%2500%2510%2500%2500%2500%250A%2592%2505%2500%2501%2500%2500%2500%259A%2502%2500%2500%257C%2592%2507%2500%2506%251E%2500%2500%25A2%2502%2500%2500%2586%2592%2507%2500%2508%2501%2500%2500%25A8%2520%2500%2500%2590%2592%2502%2500%2503%2500%2500%250000%2500%2500%2591%2592%2502%2500%2503%2500%2500%250000%2500%2500%2592%2592%2502%2500%2503%2500%2500%250000%2500%2500%2500%25A0%2507%2500%2504%2500%2500%25000100%2501%25A0%2503%2500%2501%2500%2500%2500%2501%2500%2500%2500%2502%25A0%2503%2500%2501%2500%2500%25000%250F%2500%2500%2503%25A0%2503%2500%2501%2500%2500%2500%2520%250A%2500%2500%2505%25A0%2504%2500%2501%2500%2500%2500%25B0%2521%2500%2500%250E%25A2%2505%2500%2501%2500%2500%2500%25CE%2521%2500%2500%250F%25A2%2505%2500%2501%2500%2500%2500%25D6%2521%2500%2500%2510%25A2%2503%2500%2501%2500%2500%2500%2502%2500%2500%2500%2501%25A4%2503%2500%2501%2500%2500%2500%2500%2500%2500%2500%2502%25A4%2503%2500%2501%2500%2500%2500%2500%2500%2500%2500%2503%25A4%2503%2500%2501%2500%2500%2500%2500%2500%2500%2500%2506%25A4%2503%2500%2501%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2501%2500%2500%2500%25FA%2500%2500%2500G%2500%2500%2500%250A%2500%2500%25002008%253A05%253A24%252014%253A15%253A01%25002008%253A05%253A24%252014%253A15%253A01%2500%2500%2500%2508%2500%2500%2500%2501%2500%2500%25A0%2505%2500%2500%2500%2501%2500%2500%2500%2500%2500%2501%2500%2500%2500%25C2%2500%2500%2500%2501%2500%2500%2500%2521%2500%2501%2500%2503%2500%252F%2500%2500%25004%2504%2500%2500%2502%2500%2503%2500%2504%2500%2500%2500%2592%2504%2500%2500%2503%2500%2503%2500%2504%2500%2500%2500%259A%2504%2500%2500%2504%2500%2503%2500%2522%2500%2500%2500%25A2%2504%2500%2500%2506%2500%2502%2500%250E%2500%2500%2500%25E6%2504%2500%2500%2507%2500%2502%2500%2518%2500%2500%2500%2506%2505%2500%2500%2509%2500%2502%2500%2520%2500%2500%2500%251E%2505%2500%2500%250C%2500%2504%2500%2501%2500%2500%2500O%2588%25FD%2584%250D%2500%2507%2500%2500%250C%2500%2500%253E%2505%2500%2500%2510%2500%2504%2500%2501%2500%2500%2500%2590%2501%2500%2580%2513%2500%2503%2500%2504%2500%2500%2500%253E%2511%2500%2500%2515%2500%2504%2500%2501%2500%2500%2500%2500%2500%2500%25A0%2519%2500%2503%2500%2501%2500%2500%2500%2501%2500%2500%2500%2526%2500%2503%25000%2500%2500%2500F%2511%2500%2500%2583%2500%2504%2500%2501%2500%2500%2500%2500%2500%2500%2500%2593%2500%2503%2500%2518%2500%2500%2500%25A6%2513%2500%2500%2595%2500%2502%2500%2540%2500%2500%2500%25D6%2513%2500%2500%2596%2500%2502%2500%2510%2500%2500%2500%2516%2514%2500%2500%2597%2500%2507%2500%2500%2504%2500%2500%2526%2514%2500%2500%2598%2500%2503%2500%2504%2500%2500%2500%2526%2518%2500%2500%2599%2500%2504%2500Y%2500%2500%2500.%2518%2500%2500%259A%2500%2504%2500%2505%2500%2500%2500%2592%2519%2500%2500%25A0%2500%2503%2500%250E%2500%2500%2500%25A6%2519%2500%2500%25AA%2500%2503%2500%2506%2500%2500%2500%25C2%2519%2500%2500%25B4%2500%2503%2500%2501%2500%2500%2500%2501%2500%2500%2500%25D0%2500%2504%2500%2501%2500%2500%2500%2500%2500%2500%2500%25E0%2500%2503%2500%2511%2500%2500%2500%25CE%2519%2500%2500%2501%2540%2503%2500%25B4%2502%2500%2500%25F0%2519%2500%2500%2508%2540%2503%2500%2503%2500%2500%2500X%251F%2500%2500%2509%2540%2503%2500%2503%2500%2500%2500%255E%251F%2500%2500%2510%2540%2502%2500%2520%2500%2500%2500d%251F%2500%2500%2511%2540%2507%2500%25FC%2500%2500%2500%2584%251F%2500%2500%2512%2540%2502%2500%2520%2500%2500%2500%2580%2520%2500%2500%2500%2500%2500%2500%255E%2500%2502%2500%2500%2500%2503%2500%2500%2500%2500%2500%2500%2500%2502%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%25FF%257F%25FF%257F%2503%2500%2502%2500%2500%2500%2500%2500%25FF%25FF1%2500%25FA%25007%2500%2501%2500%25A0%2500%2540%2501%2500%2500%2500%2500%2500%2500%2500%2500%25FF%25FF%25FF%25FF%25FF%25FF%2500%2500%2500%2500%2500%2500%2500%2500%25FF%25FF%25FF%25FF%2500%2500%2500%2500%25FF%257F%25FF%25FF%25FF%25FF%25FF%25FF%2500%2500%25C2%2500a%25B0%2510%251C%2500%2500%2500%2500%2500%2500%2500%2500D%2500%2500%2500%25E0%2500%25D8%2500%25B4%2500%2500%2501%2500%2500%2500%2500%2503%2500%2500%2500%2508%2500%2508%2500%2599%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2501%2500%2500%2500%2500%2500%25B4%2500%2504%2501%258E%2500%2500%2500%2500%2500%25F8%2500%25FF%25FF%25FF%25FF%25FF%25FF%25FF%25FF%2500%2500%2500%2500%2500%2500Canon%2520EOS%252040D%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500Firmware%2520Version%25201.1.1%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%25AA%25AAy5x5X%2500%258E%258E%2500%2503%2500%2500%2500%2500%2500%2500%2501%2500%2500%2506%2500%2500%2599%25BA%255Bc%2500%2500%25C2%2500%2502%2500%2500%2500%2500%2501%25BB%25BB%2501%2501%2500%2500%2500%2500%2502%2501%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2504%25E3%2504-%250C%250C%2509%25F9%2511j%2515%2588%251C%2595%2523F%2527%2524%25CC%25CC%2509%2500%2500%2500%2500%2500%2500%2500%2503%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%25FF%2501%2500%2500%2500%2500%2500%2500P%2514%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2501%2500%2500%2500%2501%2500%2500%2500%2501%2500%2500%2500%2503%2500%2500%2500%2503%2500%2500%2500%2503%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2581%2500%2500%2500%2501%2500%2500%2500%2507%2500%2500%2500%2500%2500%2500%2500%2503%2500%2500%2500%2507%2500%2500%2500%2500%2500%2500%2500%2508%2500%2500%2500%2500%2500%2500%2500%2500%25FF%25FB%25FF%2500%2500%2500%2500%2500%2500%2500%2500%25010X%25001%25007%2500%25FA%2591u%25BA%251F%2500%2500%2500%2500%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%25001.1.1%25006C%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500d%2500%2500%2500d%2500%2500%2500d%2500%2500%2500%2524%2509%2500%2500%2500%2500%2500%2500%2500%2500%250
Full error message:
$ trovebox -p -X POST -e /photo/upload.json -F 'photo=@/Users/me/Dropbox/Images/Älgkalv/ÄLGKALV.jpg'
Traceback (most recent call last):
File "/usr/local/bin/trovebox", line 9, in
load_entry_point('trovebox==0.6.1', 'console_scripts', 'trovebox')()
File "/Library/Python/2.7/site-packages/trovebox/main.py", line 93, in main
files=files, *_params)
File "/Library/Python/2.7/site-packages/trovebox/http.py", line 152, in post
files=files, auth=auth)
File "/Library/Python/2.7/site-packages/requests/sessions.py", line 403, in post
return self.request('POST', url, data=data, *_kwargs)
File "/Library/Python/2.7/site-packages/requests/sessions.py", line 324, in request
prep = self.prepare_request(req)
File "/Library/Python/2.7/site-packages/requests/sessions.py", line 265, in prepare_request
hooks=merge_setting(request.hooks, self.hooks),
File "/Library/Python/2.7/site-packages/requests/models.py", line 286, in prepare
self.prepare_body(data, files)
File "/Library/Python/2.7/site-packages/requests/models.py", line 416, in prepare_body
(body, content_type) = self._encode_files(files, data)
File "/Library/Python/2.7/site-packages/requests/models.py", line 141, in _encode_files
rf.make_multipart(content_type=ft)
File "/Library/Python/2.7/site-packages/requests/packages/urllib3/fields.py", line 175, in make_multipart
self.headers['Content-Disposition'] += '; '.join(['', self._render_parts((('name', self._name), ('filename', self._filename)))])
File "/Library/Python/2.7/site-packages/requests/packages/urllib3/fields.py", line 138, in _render_parts
parts.append(self._render_part(name, value))
File "/Library/Python/2.7/site-packages/requests/packages/urllib3/fields.py", line 118, in _render_part
return format_header_param(name, value)
File "/Library/Python/2.7/site-packages/requests/packages/urllib3/fields.py", line 43, in format_header_param
result.encode('ascii')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 10: ordinal not in range(128)
Very occasionally API requests to the hosted site fail:
When deleting a photo (https://travis-ci.org/photo/openphoto-python/builds/7605994):
OpenPhotoError: Code 500: cURL resource: Resource id #15; cURL error: Unknown SSL protocol error in connection to awesomeness.openphoto.me.s3.amazonaws.com:443 (35)
When uploading a photo (https://travis-ci.org/photo/openphoto-python/builds/7607189):
OpenPhotoError: Code 500: cURL resource: Resource id #28; cURL error: SSL read: error:00000000:lib(0):func(0):reason(0), errno 104 (56)
Perhaps I should raise a photo/frontend issue? I never see this when running on a self-hosted server, so I can't look at the logs.
It would be nice if the Python library automatically retried these failed requests a couple of times. I'm not sure if we'd want to support this for all failures, or just a subset (eg. 500 errors).
If we do this, the number of retries to be configurable (or set to 0 to disable).
See docs for the php library for an example.
https://github.com/openphoto/openphoto-php
Uploading to PyPI will make this more straightforward to install, and means it can be specified as an auto-installed dependency.
Depends on #22 (rename to Trovebox).
At the moment, it is very hard to find the details on how to connect via oauth IMHO.
The script openphoto should IMHO be able to prompt the user and direct him/her in what needs to be done to connect with OAuth.
It should probably do things like print "go to your browser at http:///... and click ok" in the flow of validating token requests and such.
Hope this helps.
Provide a better error message if a config file is invalid or incomplete
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.