├── CHANGELOG ├── vast ├── __init__.py ├── companionAd.py ├── trackingEvent.py ├── icon.py ├── ad.py ├── creative.py └── vast.py ├── .gitignore ├── setup.py ├── README.md └── LICENSE /CHANGELOG: -------------------------------------------------------------------------------- 1 | ## 2019-05-31 2 | ### Changed 3 | - LICENCE file changed from GPLV3 to APACHE v2 and fixed licence conflicts 4 | between licence file and headers of .py files 5 | - merged pr#2 this pr will fixed CDATA wrapping and ClickTracking for NonLinear ads. 6 | 7 | ### Added 8 | - added CHANGELOG file 9 | 10 | 11 | -------------------------------------------------------------------------------- /vast/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Timu Eren 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Timu Eren 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | from setuptools import setup 19 | 20 | setup( 21 | name='vast', 22 | version='0.1.dev1', 23 | packages=['vast'], 24 | url='https://github.com/selam/python-vast-xml-generator', 25 | license='Apache', 26 | author='Timu Eren', 27 | author_email='selamtux@gmail.com', 28 | description="Easy way to generate vast (3.0) xml" 29 | ) 30 | -------------------------------------------------------------------------------- /vast/companionAd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Timu Eren 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License") 7 | # you may not use self file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | from trackingEvent import TrackingEvent 19 | 20 | 21 | class CompanionAd(object): 22 | def __init__(self, resource, settings={}): 23 | self.resource = resource 24 | self.type = settings.get("type", None) 25 | self.url = settings.get("url", None) 26 | self.AdParameters = settings.get("AdParameters", None) 27 | self.AltText = settings.get("AltText", None) 28 | self.CompanionClickThrough = settings.get("CompanionClickThrough", None) 29 | self.CompanionClickTracking = settings.get("CompanionClickTracking", None) 30 | self.width = settings.get("width", None) 31 | self.height = settings.get("height", None) 32 | self.trackingEvents = [] 33 | 34 | def attachTrackingEvent(self, type, url): 35 | self.trackingEvents.append(TrackingEvent(type, url)) 36 | -------------------------------------------------------------------------------- /vast/trackingEvent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Timu Eren 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | VALID_TRACKING_EVENT_TYPES = [ 19 | 'creativeView', 20 | 'start', 21 | 'firstQuartile', 22 | 'midpoint', 23 | 'thirdQuartile', 24 | 'complete', 25 | 'mute', 26 | 'unmute', 27 | 'pause', 28 | 'rewind', 29 | 'resume', 30 | 'fullscreen', 31 | 'exitFullscreen', 32 | 'skip', 33 | 'progress', 34 | 35 | 'expand', 36 | 'collapse', 37 | 'acceptInvitationLinear', 38 | 'closeLinear' 39 | ] 40 | 41 | 42 | class TrackingEvent(object): 43 | def __init__(self, event, url, offset=None): 44 | self.offset = None 45 | self.event = None 46 | self.url = None 47 | 48 | if event not in VALID_TRACKING_EVENT_TYPES: 49 | raise Exception("""The supplied Tracking `event` {event} is not a valid Tracking event. 50 | Valid tracking events: {events}""".format( 51 | event=event, 52 | events=",".join(VALID_TRACKING_EVENT_TYPES) 53 | )) 54 | 55 | if event == "progress": 56 | if offset is None: 57 | raise Exception("Offset must be present for `progress` TrackingEvent.") 58 | self.offset = offset 59 | self.event = event 60 | self.url = url 61 | -------------------------------------------------------------------------------- /vast/icon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Timu Eren 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | REQURED_ATTRIBUTES = ["program", "width", "height", "xPosition", "yPosition"] 19 | 20 | 21 | class Icon(object): 22 | def __init__(self, settings=dict()): 23 | keys = settings.keys() 24 | for required in keys: 25 | if required not in keys: 26 | raise Exception("Missing required attribute '{attr}'".format(attr=required)) 27 | 28 | self.attributes = {} 29 | self.attributes.update(settings) 30 | self.resource = None 31 | self.clickThrough = None 32 | self.click = None 33 | self.view = None 34 | 35 | def setResource(self, _type, uri, creativeType=None): 36 | if _type not in ('StaticResource', "IFrameResource", "HTMLResource"): 37 | raise Exception("Invalid resource type") 38 | 39 | resource = {"type": _type, "uri": uri} 40 | if _type == 'HTMLResource': 41 | resource["html"] = uri 42 | if creativeType: 43 | resource["creativeType"] = creativeType 44 | self.resource = resource 45 | 46 | def setClickThrough(self, uri): 47 | self.clickThrough = uri 48 | return self 49 | 50 | def setClickTracking(self, uri): 51 | self.click = uri 52 | return self 53 | 54 | def setViewTracking(self, uri): 55 | self.view = uri 56 | return self 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-vast-xml-generator 2 | if you plan to write an video ad server with vast support you can generate xml response using this lib, 3 | 4 | ## Example 5 | #### Create instance 6 | ```python 7 | vast = VAST({"version":"3.0", "VASTErrorURI": "optional url if something went wrong in client side"}) # create instance of vast object, version is vast version, 8 | ``` 9 | 10 | 11 | #### Adding Ad 12 | ```python 13 | ad = vast.attachAd({ 14 | "id": "1", # ad id 15 | "structure": 'inline', # or "wrapper", 16 | "sequence": "1", # optional, not required 17 | "Error": 'http://error.err', # error url if something went wrong in client side, optional 18 | "AdTitle": 'Common name of the ad' # required for inline structure, 19 | "AdSystem": { "name": 'name of adserver or company', "version": "1.0" }, 20 | "Description": "optional description of ad", 21 | "Advertiser": "Optional name of advertiser", 22 | "Pricing": "Optional price (if you want to RTB on vast)", 23 | "Extensions": """xml extension for client side""", 24 | }) 25 | ``` 26 | 27 | 28 | #### Add Impression url 29 | ```python 30 | # you can add many servers if you need to 31 | ad.attachImpression({ 32 | "id": "servername" 33 | "url": "impression url" 34 | }) 35 | ``` 36 | #### Add Linear Creative 37 | ```python 38 | # you can give any valid VAST XmlTagName and value for Creative 39 | creative = ad.attachCreative('Linear', { 40 | "AdParameters" : """""", #Optional 41 | "Duration" : '00:00:30' # required for linear type 42 | }); 43 | # you can give any valid VAST XmlTagName and value for media file 44 | creative.attachMediaFile('file url', { 45 | "type": "video/mp4", 46 | "bitrate": "320", 47 | "minBitrate": "320", 48 | "maxBitrate": "320", 49 | "width": "640", 50 | "height": "360", 51 | "scalable": "true", 52 | "maintainAspectRatio": "true", 53 | "codec": "", 54 | "apiFramework": "VPAID", 55 | }); 56 | 57 | # You can add any valid tracking events: vast/trackingEvent.py#L18 58 | creative.attachTrackingEvent('creativeView', 'server url'); 59 | # many times 60 | creative.attachTrackingEvent('progress', 'server url', '00:00:01'); 61 | # 62 | creative.attachVideoClick('ClickThrough', 'click target url'); 63 | creative.attachClickThrough("Url of server") 64 | creative.attachClick("Url") # look at the vast 3.0 documentation 65 | 66 | vast.xml() // returns XMLBuilder object, to print str(vast.xml()) 67 | 68 | ``` 69 | -------------------------------------------------------------------------------- /vast/ad.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Timu Eren 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | from creative import Creative 19 | 20 | REQUIRED_INLINE = ['AdSystem', 'AdTitle'] 21 | REQUIRED_WRAPPER = ['AdSystem', 'VASTAdTagURI'] 22 | 23 | 24 | def validateSettings(settings, requireds): 25 | keys = settings.keys() 26 | for required in requireds: 27 | if required not in keys: 28 | raise Exception("Missing required settings: {required}".format(required=required)) 29 | 30 | 31 | def validateInLineSettings(settings): 32 | validateSettings(settings, REQUIRED_INLINE) 33 | 34 | 35 | def validateWrapperSettings(settings): 36 | validateSettings(settings, REQUIRED_WRAPPER) 37 | 38 | 39 | class Ad(object): 40 | def __init__(self, settings={}): 41 | self.errors = [] 42 | self.surveys = [] 43 | self.impressions = [] 44 | self.creatives = [] 45 | 46 | if settings["structure"].lower() == 'wrapper': 47 | validateWrapperSettings(settings) 48 | self.VASTAdTagURI = settings["VASTAdTagURI"] 49 | else: 50 | validateInLineSettings(settings) 51 | 52 | self.id = settings["id"] 53 | self.sequence = settings.get("sequence", None) 54 | self.structure = settings["structure"] 55 | self.AdSystem = settings["AdSystem"] 56 | self.AdTitle = settings["AdTitle"] 57 | 58 | # optional elements 59 | self.Error = settings.get("Error", None) 60 | self.Description = settings.get("Description", None) 61 | self.Advertiser = settings.get("Advertiser", None) 62 | 63 | self.Pricing = settings.get("Pricing", None) 64 | self.Extensions = settings.get("Extensions", None) 65 | 66 | def attachSurvey(self, settings): 67 | survey = {"url": settings.url} 68 | if "type" in settings: 69 | survey["type"] = settings["type"] 70 | self.surveys.append(survey) 71 | 72 | def attachImpression(self, settings): 73 | self.impressions.append(settings) 74 | return self 75 | 76 | def attachCreative(self, _type, options): 77 | creative = Creative(_type, options) 78 | self.creatives.append(creative) 79 | return creative 80 | -------------------------------------------------------------------------------- /vast/creative.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Timu Eren 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use self file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | from icon import Icon 19 | from trackingEvent import TrackingEvent 20 | 21 | VALID_VIDEO_CLICKS = ['ClickThrough', 'ClickTracking', 'CustomClick'] 22 | 23 | 24 | class Creative(object): 25 | def __init__(self, _type, settings=None): 26 | settings = {} if settings is None else settings 27 | self.type = _type 28 | self.mediaFiles = [] 29 | self.trackingEvents = [] 30 | self.videoClicks = [] 31 | self.clickThroughs = [] 32 | self.clicks = [] 33 | self.resources = [] 34 | self.icons = [] 35 | self.AdParameters = settings.get("AdParameters", None) 36 | self._adParameters = None 37 | self.attributes = {} 38 | self.duration = settings.get("Duration", None) 39 | self.skipoffset = settings.get("skipoffset", None) 40 | self.nonLinearClickThrough = None 41 | self.nonLinearClickTracking = None 42 | 43 | if _type == "Linear" and self.duration is None: 44 | raise Exception('A Duration is required for all creatives. Consider defaulting to "00:00:00"') 45 | 46 | if "id" in settings: 47 | self.attributes["id"] = settings["id"] 48 | 49 | if "width" in settings: 50 | self.attributes["width"] = settings["width"] 51 | if "height" in settings: 52 | self.attributes["height"] = settings["height"] 53 | if "expandedWidth" in settings: 54 | self.attributes["expandedWidth"] = settings["expandedWidth"] 55 | if "expandedHeight" in settings: 56 | self.attributes["expandedHeight"] = settings["expandedHeight"] 57 | if "scalable" in settings: 58 | self.attributes["scalable"] = settings["scalable"] 59 | if "maintainAspectRatio" in settings: 60 | self.attributes["maintainAspectRatio"] = settings["maintainAspectRatio"] 61 | if "minSuggestedDuration" in settings: 62 | self.attributes["minSuggestedDuration"] = settings["minSuggestedDuration"] 63 | if "apiFramework" in settings: 64 | self.attributes["apiFramework"] = settings["apiFramework"] 65 | 66 | def attachMediaFile(self, url, settings={}): 67 | media_file = {"attributes": {}} 68 | media_file["url"] = url 69 | media_file["attributes"]["type"] = settings.get("type", 'video/mp4') 70 | media_file["attributes"]["width"] = settings.get("width", '640') 71 | media_file["attributes"]["height"] = settings.get("height", '360') 72 | media_file["attributes"]["delivery"] = settings.get("delivery", 'progressive') 73 | if "id" not in settings: 74 | raise Exception('an `id` is required for all media files') 75 | 76 | media_file["attributes"]["id"] = settings["id"] 77 | if "bitrate" in settings: 78 | media_file["attributes"]["bitrate"] = settings["bitrate"] 79 | if "minBitrate" in settings: 80 | media_file["attributes"]["minBitrate"] = settings["minBitrate"] 81 | if "maxBitrate" in settings: 82 | media_file["attributes"]["maxBitrate"] = settings["maxBitrate"] 83 | if "scalable" in settings: 84 | media_file["attributes"]["scalable"] = settings["scalable"] 85 | if "codec" in settings: 86 | media_file["attributes"]["codec"] = settings["codec"] 87 | if "apiFramework" in settings: 88 | media_file["attributes"]["apiFramework"] = settings["apiFramework"] 89 | if "maintainAspectRatio" in settings: 90 | media_file["attributes"]["maintainAspectRatio"] = settings["maintainAspectRatio"] 91 | 92 | self.mediaFiles.append(media_file) 93 | return self 94 | 95 | def attachTrackingEvent(self, _type, url, offset=None): 96 | self.trackingEvents.append(TrackingEvent(_type, url, offset)) 97 | return self 98 | 99 | def attachVideoClick(self, _type, url, _id=''): 100 | if _type not in VALID_VIDEO_CLICKS: 101 | raise Exception('The supplied VideoClick `type` is not a valid VAST VideoClick type.') 102 | self.videoClicks.append({"type": _type, "url": url, "id": _id}) 103 | return self 104 | 105 | def attachClickThrough(self, url): 106 | self.clickThroughs.append(url) 107 | return self 108 | 109 | def attachClick(self, uri, _type=None): 110 | if isinstance(uri, basestring): 111 | _type = 'NonLinearClickThrough' 112 | self.clicks = [{"type": _type, "uri": uri}] 113 | return self 114 | 115 | def attachResource(self, _type, uri, creative_type=None): 116 | resource = {"type": _type, "uri": uri} 117 | if _type == 'HTMLResource': 118 | resource["html"] = uri 119 | if creative_type is not None: 120 | resource["creativeType"] = creative_type 121 | self.resources.append(resource) 122 | return self 123 | 124 | def attachIcon(self, settings): 125 | icon = Icon(settings) 126 | self.icons.append(icon) 127 | return icon 128 | 129 | def adParameters(self, data, xml_encoded): 130 | self._adParameters = {"data": data, "xmlEncoded": xml_encoded} 131 | return self 132 | 133 | def attachNonLinearClickThrough(self, url): 134 | self.nonLinearClickThrough = url 135 | 136 | def attachNonLinearClickTracking(self, url): 137 | self.nonLinearClickTracking = url 138 | -------------------------------------------------------------------------------- /vast/vast.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Timu Eren 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | from ad import Ad 19 | from xmlbuilder import XMLBuilder 20 | 21 | 22 | class VAST(object): 23 | def __init__(self, settings={}): 24 | self.ads = [] 25 | self.version = settings.get("version", "3.0") 26 | self.VASTErrorURI = settings.get("VASTErrorURI", None) 27 | 28 | def attachAd(self, settings): 29 | ad = Ad(settings) 30 | self.ads.append(ad) 31 | return ad 32 | 33 | def cdata(self, param): 34 | return "".format(param=param) 35 | 36 | def add_creatives(self, response, ad, track): 37 | linearCreatives = [c for c in ad.creatives if c.type == "Linear"] 38 | nonLinearCreatives = [c for c in ad.creatives if c.type == "NonLinear"] 39 | companionAdCreatives = [c for c in ad.creatives if c.type == "CompanionAd"] 40 | with response.Creatives: 41 | for creative in linearCreatives: 42 | creativeOpts = {} 43 | with response.Creative: 44 | if creative.skipoffset: 45 | creativeOpts["skipoffset"] = creative.skipoffset 46 | with response.Linear(**creativeOpts): 47 | if len(creative.icons) > 0: 48 | with response.Icons: 49 | for icon in creative.icons: 50 | with response.Icon(**icon.attributes): 51 | attributes = {} 52 | if "creativeType" in icon.resource: 53 | attributes["creativeType"] = icon.resource["creativeType"] 54 | attr = getattr(response, icon.resource["type"]) 55 | attr(self.cdata(icon.resource["uri"]), **attributes) 56 | if icon.click or icon.clickThrough: 57 | with response.IconClicks: 58 | if icon.clickThrough: 59 | response.IconClickThrough(self.cdata(icon.clickThrough)) 60 | if icon.click: 61 | response.IconClickTraking(self.cdata(icon.click)) 62 | if icon.view: 63 | response.IconViewTracking(self.cdata(icon.view)) 64 | response.Duration(creative.duration) 65 | with response.TrackingEvents: 66 | for event in creative.trackingEvents: 67 | if track: 68 | attrs = {"event": event.event} 69 | if event.offset: 70 | attrs["offset"] = event.offset 71 | response.Tracking(self.cdata(event.url), **attrs) 72 | if creative.AdParameters: 73 | response.AddParameters(creative.AdParameters) 74 | with response.VideoClicks: 75 | for click in creative.videoClicks: 76 | attr = getattr(response, click["type"]) 77 | attr(self.cdata(click["url"]), **{"id": click.get("id", "")}) 78 | 79 | with response.MediaFiles: 80 | for media in creative.mediaFiles: 81 | response.MediaFile(self.cdata(media["url"]), **media["attributes"]) 82 | 83 | if len(nonLinearCreatives) > 0: 84 | for creative in nonLinearCreatives: 85 | with response.Creative: 86 | with response.NonLinearAds: 87 | with response.NonLinear(**creative.attributes): 88 | for resource in creative.resources: 89 | attrs = {} 90 | if "creativeType" in resource: 91 | attrs["creativeType"] = resource["creativeType"] 92 | element = getattr(response, resource["type"]) 93 | element(self.cdata(resource["uri"]), **attrs) 94 | 95 | for click in creative.clicks: 96 | element = getattr(response, click["type"]) 97 | element(self.cdata(click["uri"])) 98 | 99 | if creative.AdParameters: 100 | response.AdParameters(creative.AdParameters["data"], **{ 101 | "xmlEncoded": creative.AdParameters["xmlEncoded"] 102 | }) 103 | if creative.nonLinearClickThrough: 104 | response.NonLinearClickThrough(self.cdata(creative.nonLinearClickThrough)) 105 | if creative.nonLinearClickTracking: 106 | response.NonLinearClickTracking(self.cdata(creative.nonLinearClickTracking)) 107 | 108 | if len(companionAdCreatives) > 0: 109 | with response.CompanionAds: 110 | for creative in companionAdCreatives: 111 | with response.Companion(**creative.attributes): 112 | for resource in creative.resources: 113 | attrs = {} 114 | element = getattr(response, resource["type"]) 115 | if "creativeType" in resource: 116 | attrs["creativeType"] = resource["creativeType"] 117 | element(self.cdata(resource["uri"]), **attrs) 118 | if "adParameters" in resource: 119 | response.AdParameters(resource["adParameters"]["data"], **{ 120 | "xmlEncoded": resource["adParameters"]["xmlEncoded"] 121 | }) 122 | with response.TrakingEvents: 123 | for event in creative.trackingEvents: 124 | if track: 125 | attrs = {"event": event.event} 126 | if event.offset: 127 | attrs["offset"] = event.offset 128 | response.Tracking(event.url, **attrs) 129 | 130 | for click in creative.clickThroughs: 131 | response.CompanionClickThrough(self.cdata(click)) 132 | 133 | if creative.nonLinearClickTracking: 134 | response.NonLinearClickTracking(self.cdata(creative.nonLinearClickTracking)) 135 | 136 | def xml(self, options={}): 137 | track = True if options.get("track", True) else options.get("track") 138 | response = XMLBuilder('VAST', version=self.version) 139 | if len(self.ads) == 0 and self.VASTErrorURI: 140 | response.Error(self.cdata(self.VASTErrorURI)) 141 | return response 142 | for ad in self.ads: 143 | adOptions = {"id": ad.id} 144 | if ad.sequence: 145 | adOptions["sequence"] = str(ad.sequence) 146 | 147 | with response.Ad(**adOptions): 148 | if ad.structure.lower() == 'wrapper': 149 | with response.Wrapper: 150 | response.AdSystem(ad.AdSystem["name"], **{"version": ad.AdSystem["version"]}) 151 | response.VASTAdTagURI(self.cdata(ad.VASTAdTagURI)) 152 | if ad.Error: 153 | response.Error(self.cdata(ad.Error)) 154 | for impression in ad.impressions: 155 | if track: 156 | response.Impression(self.cdata(impression["url"])) 157 | self.add_creatives(response, ad, track) 158 | else: 159 | with response.InLine: 160 | response.AdSystem(ad.AdSystem["name"], **{"version": ad.AdSystem["version"]}) 161 | response.AdTitle(self.cdata(ad.AdTitle)) 162 | response.Description(self.cdata(ad.Description or '')) 163 | 164 | with response.Survey: 165 | for survey in ad.surveys: 166 | attributes = {} 167 | if survey.type: 168 | attributes["type"] = survey.type 169 | response.Survey(self.cdata(survey.url), **attributes) 170 | 171 | if ad.Error: 172 | response.Error(self.cdata(ad.Error)) 173 | 174 | for impression in ad.impressions: 175 | if track: 176 | response.Impression(self.cdata(impression["url"])) 177 | 178 | self.add_creatives(response, ad, track) 179 | 180 | if ad.Extensions: 181 | for extension in ad.Extensions: 182 | response.Extension(extension) 183 | return response 184 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2015 Timu EREN 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------