criteo / criteo-python-marketing-sdk Goto Github PK
View Code? Open in Web Editor NEWOfficial Python SDK to access the Criteo Marketing API
License: Apache License 2.0
Official Python SDK to access the Criteo Marketing API
License: Apache License 2.0
Hello,
I've developed an ETL that consumes the API through this SDK and it works correctly in my development environment, in my Windows machine. Nevertheless, in my production environment, an EC2 instance with Ubuntu on AWS, it fails with the following message:
`Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/urllib3/contrib/pyopenssl.py", line 485, in wrap_socket
cnx.do_handshake()
File "/usr/lib/python3/dist-packages/OpenSSL/SSL.py", line 1915, in do_handshake
self._raise_ssl_error(self._ssl, result)
File "/usr/lib/python3/dist-packages/OpenSSL/SSL.py", line 1647, in _raise_ssl_error
_raise_current_error()
File "/usr/lib/python3/dist-packages/OpenSSL/_util.py", line 54, in exception_from_error_queue
raise exception_type(errors)
OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 665, in urlopen
httplib_response = self._make_request(
File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 376, in _make_request
self._validate_conn(conn)
File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 996, in validate_conn
conn.connect()
File "/usr/lib/python3/dist-packages/urllib3/connection.py", line 366, in connect
self.sock = ssl_wrap_socket(
File "/usr/lib/python3/dist-packages/urllib3/util/ssl.py", line 370, in ssl_wrap_socket
return context.wrap_socket(sock, server_hostname=server_hostname)
File "/usr/lib/python3/dist-packages/urllib3/contrib/pyopenssl.py", line 491, in wrap_socket
raise ssl.SSLError("bad handshake: %r" % e)
ssl.SSLError: ("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')])",)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "", line 1, in
File "/home/tiendamia/ETLs/datasources/digital_marketing/criteo/api/CriteoApi.py", line 44, in get_adset_report
response = self._analytics_api.get_adset_report(statistics_report_query_message=query_message)
File "/home/tiendamia/.local/lib/python3.8/site-packages/criteo_api_marketingsolutions_v2022_07/api_client.py", line 771, in call
return self.callable(self, *args, **kwargs)
File "/home/tiendamia/.local/lib/python3.8/site-packages/criteo_api_marketingsolutions_v2022_07/api/analytics_api.py", line 104, in __get_adset_report
return self.call_with_http_info(**kwargs)
File "/home/tiendamia/.local/lib/python3.8/site-packages/criteo_api_marketingsolutions_v2022_07/api_client.py", line 833, in call_with_http_info
return self.api_client.call_api(
File "/home/tiendamia/.local/lib/python3.8/site-packages/criteo_api_marketingsolutions_v2022_07/api_client.py", line 408, in call_api
return self.__call_api(resource_path, method,
File "/home/tiendamia/.local/lib/python3.8/site-packages/criteo_api_marketingsolutions_v2022_07/api_client.py", line 195, in __call_api
response_data = self.request(
File "/home/tiendamia/.local/lib/python3.8/site-packages/criteo_api_marketingsolutions_v2022_07/api_client.py", line 454, in request
return self.rest_client.POST(url,
File "/home/tiendamia/.local/lib/python3.8/site-packages/criteo_api_marketingsolutions_v2022_07/rest.py", line 268, in POST
return self.request("POST", url,
File "/home/tiendamia/.local/lib/python3.8/site-packages/criteo_api_marketingsolutions_v2022_07/rest.py", line 142, in request
self.refresh_token(headers)
File "/home/tiendamia/.local/lib/python3.8/site-packages/criteo_api_marketingsolutions_v2022_07/rest.py", line 302, in refresh_token
self.token = self.call_auth_endpoint(headers)
File "/home/tiendamia/.local/lib/python3.8/site-packages/criteo_api_marketingsolutions_v2022_07/rest.py", line 316, in call_auth_endpoint
response = self.request("POST", oauth_url,
File "/home/tiendamia/.local/lib/python3.8/site-packages/criteo_api_marketingsolutions_v2022_07/rest.py", line 173, in request
r = self.pool_manager.request(
File "/usr/lib/python3/dist-packages/urllib3/request.py", line 79, in request
return self.request_encode_body(
File "/usr/lib/python3/dist-packages/urllib3/request.py", line 171, in request_encode_body
return self.urlopen(method, url, **extra_kw)
File "/usr/lib/python3/dist-packages/urllib3/poolmanager.py", line 330, in urlopen
response = conn.urlopen(method, u.request_uri, **kw)
File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 747, in urlopen
return self.urlopen(
File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 747, in urlopen
return self.urlopen(
File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 747, in urlopen
return self.urlopen(
File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 719, in urlopen
retries = retries.increment(
File "/usr/lib/python3/dist-packages/urllib3/util/retry.py", line 436, in increment
raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='api.criteo.com', port=443): Max retries exceeded with url: /oauth2/token (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')])")))`
Also, my code for my API class where i set and pass the Configuration object:
`from helpers.Result import Result, Status
from datasources.digital_marketing.criteo.config.env_criteo import criteo_settings
from criteo_api_marketingsolutions_v2022_07 import Configuration, ApiClient
from criteo_api_marketingsolutions_v2022_07.api.analytics_api import AnalyticsApi
from criteo_api_marketingsolutions_v2022_07.model.statistics_report_query_message import StatisticsReportQueryMessage
from datetime import datetime
from typing import List, Dict
import json
import criteo_api_marketingsolutions_v2022_07
class CriteoApi:
def init(self):
self._client = ApiClient(Configuration(username=criteo_settings['CLIENT_ID'],
password=criteo_settings['CLIENT_SECRET'],
discard_unknown_keys=True))
self._analytics_api = AnalyticsApi(self._client)
def get_adset_report(self, start_date: str, end_date: str, metrics: List[str], dimensions: List[str], currency: str= 'USD') -> Result:
"""
Calls the statistics/report endpoint, constructing a StatisticsReportQueryMessage based on the args passed
Args:
start_date (str): Start Date of the report
end_date (str): End Date of the report
metrics (List[str]): List of metrics for the report to return
dimensions (List[str]): List of dimensions for the report to return
currency (str): Currency code to use for values in report
Returns:
result (Result): result from the API call
"""
result = Result(Status.error, '', [])
query_message = StatisticsReportQueryMessage(
dimensions=dimensions,
metrics=metrics,
start_date=datetime.strptime(start_date, '%Y-%m-%d'),
end_date=datetime.strptime(end_date, '%Y-%m-%d'),
currency=currency,
format='json'
)
try:
# [response_content, http_code, response_headers] = self._analytics_api.get_adset_report(query_message)
response = self._analytics_api.get_adset_report(statistics_report_query_message=query_message)
# decoded_data = response.encode().decode('utf-8-sig')
result.status = Status.success
# result.data = json.loads(decoded_data)['Rows']
result.data = response['Rows']
result.message = f'Fetched {len(result.data)} results from Criteo API!'
except criteo_api_marketingsolutions_v2022_07.ApiException as e:
print("Exception when calling AnalyticsApi->get_adset_report: %s\n" % e)
result.message = f'Exception getting campaign report from Criteo API: {str(e)}'
return result
`
Any help is much appreciated, i know this has something to do with certificates, but don't have much knowlege about it and i'm quite stuck.
As shown in the documentation the python code snippets to gather stats using stats API (https://developers.criteo.com/marketing-solutions/v2020/docs/statistics-v1) given parameter as start_date,end_date doesn't accept in the way it is shown in the docs respectively, I think the ( _ ) in between need to be removed either in docs or please do change it on python SDK given to accept the ( _ )
Hi,
Just started to use this api. Tried several of endpoints and they worked perfectly.
But this endpoint(in the subject) returns zero rows, both from web and from python sdk i do not get why. Tried both a lot of times, with a lot of different criteria set. Everytime I get 0 rows.
For example in the below code, when i run script, self.get_camp_data()
returns campaigns list which is expected. Also self.criteo_stats_2()
returns data. But self.criteo_stats()
returns this ("{'columns': ['campaignId', 'sellerId', 'sellerName', 'day', 'impressions', " "'clicks', 'cost', 'saleUnits', 'revenue', 'cr', 'cpo', 'cos', 'roas'], " "'data': [], 'rows': 0, 'links': {}}")
.
By the way, I have 2 active campaigns there should be some data. In addition response_code is 200.
Is there any way that my account cannot authorized to use this data/apis etc?
from __future__ import print_function
import time
import criteo_marketing
from criteo_marketing.rest import ApiException
from pprint import pprint
import json
import re
class CriteoApiHk(object):
def __init__(self):
# breakpoint()
with open('auth/criteo.json','r') as authfile:
self.auth = json.load(authfile)
self.get_token()
self.get_camp_data()
self.criteo_stats()
self.criteo_stats_2()
def get_token(self):
# breakpoint()
api_instance = criteo_marketing.AuthenticationApi()
try:
self.auth_api_response = api_instance.o_auth2_token_post(client_id=self.auth.get("me").get("client_id"),
client_secret= self.auth.get("me").get("client_secret"),
grant_type='client_credentials')
except ApiException as e:
print("Exception when calling AuthenticationApi->o_auth2_token_post: %s\n" % e)
def get_camp_data(self):
# breakpoint()
#config
configuration = criteo_marketing.Configuration()
configuration.host = self.auth.get("host_add")
#api
api_instance = criteo_marketing.AdvertisersApi(criteo_marketing.ApiClient(configuration))
advertiser_id = self.auth.get("advts_id")
authorization = 'Bearer {0}'.format(self.auth_api_response.to_dict().get("access_token"))
try:
api_response = api_instance.get_campaigns(advertiser_id, authorization)
except ApiException as e:
print("Exception when calling AdvertisersApi->get_campaigns: %s\n" % e)
breakpoint()
serialized = [camp.to_dict() for camp in api_response]
result = {"api":serialized}
with open('data.json', 'w') as output:
json.dump(result, output, indent=4)
def criteo_stats(self):
configuration = criteo_marketing.Configuration()
configuration.host = self.auth.get("host_add")
#api
api_instance = criteo_marketing.SellersV2StatsApi(criteo_marketing.ApiClient(configuration))
authorization = 'Bearer {0}'.format(self.auth_api_response.to_dict().get("access_token"))
#vars
start_date = '2020-05-18'
end_date = '2020-05-19'
count = 56
try:
# Get stats by seller-campaign.
api_response = api_instance.seller_campaigns(authorization,
start_date=start_date,
end_date=end_date,
count=count)
pprint(api_response)
except ApiException as e:
print("Exception when calling SellersV2StatsApi->seller_campaigns: %s\n" % e)
def criteo_stats_2(self):
# breakpoint()
token = 'Bearer ' + self.auth_api_response.to_dict().get("access_token")
configuration = criteo_marketing.Configuration()
configuration.host = self.auth.get("host_add")
stats_api = criteo_marketing.StatisticsApi(criteo_marketing.ApiClient(configuration))
stats_query_message = criteo_marketing.StatsQueryMessageEx(
report_type="CampaignPerformance",
dimensions=["CampaignId"],
metrics=["ConversionRate"],
start_date="2020-05-01",
end_date="2020-05-31",
format="json"
)
# Use the method with 'with_http_info' if you want to retrieve the filename
# Otherwise, you can directly call the get_stats method and write the return value to a file
[response_content, http_code, response_headers] = stats_api.get_stats_with_http_info(token, stats_query_message)
breakpoint()
if 200 == http_code:
content_disposition = response_headers["Content-Disposition"]
if content_disposition:
filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', content_disposition).group(1)
# $> file Extract_20190101_20190131.csv
# Extract_20190101_20190131.csv: UTF-8 Unicode (with BOM) text, with CRLF line terminators
with open(filename, "w+") as f:
f.write(response_content)
if __name__ == "__main__":
CriteoApiHk()
My code:
api_instance = criteo_marketing.AuthenticationApi()
api_response = api_instance.o_auth2_token_post(client_id=client_id, client_secret=client_secret, grant_type=grant_type)
authorization = "Bearer {}".format(api_response.access_token)
api_instance = criteo_marketing.StatisticsApi()
stats_query_message_ex = criteo_marketing.StatsQueryMessageEx(
report_type="CampaignPerformance"
, dimensions=["CampaignId"]
, metrics=["Clicks"]
, start_date="2019-01-01"
, end_date="2019-01-09"
, format="Csv")
api_instance.get_stats(authorization , stats_query_message_ex)
**in _refresh_token
raise ValueError('client_id (username) is missing')
ValueError: client_id (username) is missing**
Failed to call because of client_id, I guess the check in default get from configuration not from parsed headers (where client_id stored)
Hi,
I am trying get date and spend in an aggragated way via criteo-python-marketing-api so before get those, i just tried the script in here to test. I created client_id and client_secret from API users - REST API and i used those to get some metrics to test it but i got an error which i think is about GRANT_TYPE paramater. I am not exactly sure if i am doing it true and i am sorry if this problem sounds like a stackoverflow question but i though i may have a faster answer in here. I am not sure if i am doing something wrong and the documantation didn't help me.
The Code:
import re
import sys
import criteo_marketing as cm
from criteo_marketing import Configuration
GRANT_TYPE = {'client_id': '##############',
'client_secret': '#########'}
configuration = Configuration(username='mail_address', password='password')
client = cm.ApiClient(configuration)
auth_api = cm.AuthenticationApi(client)
auth_response = auth_api.o_auth2_token_post(client_id=client.configuration.username,
client_secret=client.configuration.password,
grant_type=GRANT_TYPE)
token = auth_response.token_type + " " + auth_response.access_token
stats_api = cm.StatisticsApi(client)
stats_query_message = cm.StatsQueryMessageEx(
report_type="CampaignPerformance",
dimensions=["CampaignId"],
metrics=["Clicks"],
start_date="2020-01-01",
end_date="2020-06-30",
format="Csv")
# Use the method with 'with_http_info' if you want to retrieve the filename
# Otherwise, you can directly call the get_stats method and write the return value to a file
[response_content, http_code, response_headers] = stats_api.get_stats_with_http_info(token, stats_query_message)
if 200 == http_code:
content_disposition = response_headers["Content-Disposition"]
if content_disposition:
filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', content_disposition).group(1)
# $> file Extract_20190101_20190131.csv
# Extract_20190101_20190131.csv: UTF-8 Unicode (with BOM) text, with CRLF line terminators
with open(filename, "w+") as f:
f.write(response_content)
This is the error:
---------------------------------------------------------------------------
ApiException Traceback (most recent call last)
in
14 auth_response = auth_api.o_auth2_token_post(client_id=client.configuration.username,
15 client_secret=client.configuration.password,
---> 16 grant_type=GRANT_TYPE)
17 token = auth_response.token_type + " " + auth_response.access_token
18
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/criteo_marketing/api/authentication_api.py in o_auth2_token_post(self, **kwargs)
62 """
63 kwargs['_return_http_data_only'] = True
---> 64 return self.o_auth2_token_post_with_http_info(**kwargs) # noqa: E501
65
66 def o_auth2_token_post_with_http_info(self, **kwargs): # noqa: E501
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/criteo_marketing/api/authentication_api.py in o_auth2_token_post_with_http_info(self, **kwargs)
151 _preload_content=local_var_params.get('_preload_content', True),
152 _request_timeout=local_var_params.get('_request_timeout'),
--> 153 collection_formats=collection_formats)
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/criteo_marketing/api_client.py in call_api(self, resource_path, method, path_params, query_params, header_params, body, post_params, files, response_type, auth_settings, async_req, _return_http_data_only, collection_formats, _preload_content, _request_timeout, _host)
339 response_type, auth_settings,
340 _return_http_data_only, collection_formats,
--> 341 _preload_content, _request_timeout, _host)
342 else:
343 thread = self.pool.apply_async(self.__call_api, (resource_path,
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/criteo_marketing/api_client.py in __call_api(self, resource_path, method, path_params, query_params, header_params, body, post_params, files, response_type, auth_settings, _return_http_data_only, collection_formats, _preload_content, _request_timeout, _host)
170 post_params=post_params, body=body,
171 _preload_content=_preload_content,
--> 172 _request_timeout=_request_timeout)
173
174 self.last_response = response_data
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/criteo_marketing/api_client.py in request(self, method, url, query_params, headers, post_params, body, _preload_content, _request_timeout)
384 _preload_content=_preload_content,
385 _request_timeout=_request_timeout,
--> 386 body=body)
387 elif method == "PUT":
388 return self.rest_client.PUT(url,
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/criteo_marketing/rest.py in POST(self, url, headers, query_params, post_params, body, _preload_content, _request_timeout)
284 _preload_content=_preload_content,
285 _request_timeout=_request_timeout,
--> 286 body=body)
287
288 def PUT(self, url, headers=None, query_params=None, post_params=None,
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/criteo_marketing/rest.py in request(self, method, url, query_params, headers, body, post_params, _preload_content, _request_timeout)
237
238 if not 200 <= r.status <= 299:
--> 239 raise ApiException(http_resp=r)
240
241 return r
ApiException: (400)
Reason: Bad Request
HTTP response headers: HTTPHeaderDict({'cache-control': 'no-cache', 'pragma': 'no-cache', 'content-length': '92', 'content-type': 'application/json;charset=UTF-8', 'expires': '-1', 'server': 'Microsoft-IIS/10.0', 'x-aspnet-version': '4.0.30319', 'x-powered-by': 'ASP.NET', 'date': 'Mon, 06 Jul 2020 13:06:09 GMT'})
HTTP response body: {"error":"invalid_grant","error_description":"The client_id or client_secret is incorrect."}
More of a question than a current issue. Curious about the plans if any that might exist to update portions of the current SDK to make them in line with expected end-points in the future. For instance. The following docs show that there is going to be a switch of Statistics endpoints in the switch from MAPI to stable release v2021-04. The alteration looking like the following.
new_endpoint = 'https://api.criteo.com/2021-04/statistics/report'
legacy_endpoint = 'https://api.criteo.com/marketing/v1/statistics'
Are there any plants to update portions like the current Statistics Module to be representative of the new endpoint?
Hello, I am trying get daily AdvertiserCost for my marketing reports with criteo and when i use day as dimension i can see all data except today but i want get the most current values for today so after some digging, i found out that there is also hour dimension but when i use it i got message":"The requested dimensions combination is not supported. error. Here is full code including error message:
import criteo_marketing as cm
from criteo_marketing import Configuration
import json
from datetime import date
configuration = Configuration(username=" user_name", password="pass_word")
client = cm.ApiClient(configuration)
auth_api = cm.AuthenticationApi(client)
auth_response = auth_api.o_auth2_token_post(client_id=client.configuration.username,client_secret=client.configuration.password,grant_type="client_credentials")
token = auth_response.token_type + " " + auth_response.access_token
today = date.today().strftime("%Y-%m-%d")
stats_api = cm.StatisticsApi(client)
stats_query_message = cm.StatsQueryMessageEx(dimensions=["Hour"],
metrics=["AdvertiserCost"],
start_date="2018-07-10",
end_date=today,
currency="TRY",
format="json")
[response_content, http_code, response_headers]=stats_api.get_stats_with_http_info(token, stats_query_message)
response_content=response_content.replace('\ufeff', '')
json1_data = json.loads(response_content)['Rows']
json1_data
---------------------------------------------------------------------------
ApiException Traceback (most recent call last)
<ipython-input-27-4024c70d078d> in <module>
19 currency="TRY",
20 format="json")
---> 21 [response_content, http_code, response_headers]=stats_api.get_stats_with_http_info(token, stats_query_message)
22 response_content=response_content.replace('\ufeff', '')
23 json1_data = json.loads(response_content)['Rows']
/usr/local/lib/python3.8/site-packages/criteo_marketing/api/statistics_api.py in get_stats_with_http_info(self, authorization, stats_query, **kwargs)
261 auth_settings = ['Authorization'] # noqa: E501
262
--> 263 return self.api_client.call_api(
264 '/v1/statistics', 'POST',
265 path_params,
/usr/local/lib/python3.8/site-packages/criteo_marketing/api_client.py in call_api(self, resource_path, method, path_params, query_params, header_params, body, post_params, files, response_type, auth_settings, async_req, _return_http_data_only, collection_formats, _preload_content, _request_timeout, _host)
334 """
335 if not async_req:
--> 336 return self.__call_api(resource_path, method,
337 path_params, query_params, header_params,
338 body, post_params, files,
/usr/local/lib/python3.8/site-packages/criteo_marketing/api_client.py in __call_api(self, resource_path, method, path_params, query_params, header_params, body, post_params, files, response_type, auth_settings, _return_http_data_only, collection_formats, _preload_content, _request_timeout, _host)
166
167 # perform request and return response
--> 168 response_data = self.request(
169 method, url, query_params=query_params, headers=header_params,
170 post_params=post_params, body=body,
/usr/local/lib/python3.8/site-packages/criteo_marketing/api_client.py in request(self, method, url, query_params, headers, post_params, body, _preload_content, _request_timeout)
378 body=body)
379 elif method == "POST":
--> 380 return self.rest_client.POST(url,
381 query_params=query_params,
382 headers=headers,
/usr/local/lib/python3.8/site-packages/criteo_marketing/rest.py in POST(self, url, headers, query_params, post_params, body, _preload_content, _request_timeout)
278 def POST(self, url, headers=None, query_params=None, post_params=None,
279 body=None, _preload_content=True, _request_timeout=None):
--> 280 return self.request("POST", url,
281 headers=headers,
282 query_params=query_params,
/usr/local/lib/python3.8/site-packages/criteo_marketing/rest.py in request(self, method, url, query_params, headers, body, post_params, _preload_content, _request_timeout)
237
238 if not 200 <= r.status <= 299:
--> 239 raise ApiException(http_resp=r)
240
241 return r
ApiException: (400)
Reason: Bad Request
HTTP response headers: HTTPHeaderDict({'cache-control': 'private', 'content-length': '68', 'content-type': 'application/json; charset=utf-8', 'x-aspnet-version': '4.0.30319', 'date': 'Tue, 18 Aug 2020 06:59:55 GMT'})
HTTP response body: {"message":"The requested dimensions combination is not supported."}
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.