Skip to main content

Source Factories

Source factories are used to simplify the creation of sources for certain standard cases.

Sources split up the process of providing input fields with choices for users into several components: a context binder, a source class, a terms class, and a term class.

This is the correct abstraction and will fit many complex cases very well. To reduce the amount of work to do for some standard cases, the source factories allow users to define only the business relevant code for getting a list of values, getting a token and a title to display.

Simple case

In the most simple case, you only have to provide a method that returns a list of values and derive from BasicSourceFactory:

>>> import zc.sourcefactory.basic
>>> class MyStaticSource(zc.sourcefactory.basic.BasicSourceFactory):
...     def getValues(self):
...         return ['a', 'b', 'c']

When calling the source factory, we get a source:

>>> source = MyStaticSource()
>>> import zope.schema.interfaces
>>> zope.schema.interfaces.ISource.providedBy(source)
True

The values match our getValues-method of the factory:

>>> list(source)
['a', 'b', 'c']
>>> 'a' in source
True
>>> len(source)
3

Contextual sources

Sometimes we need context to determine the values. In this case, the getValues-method gets a parameter context.

Let’s assume we have a small object containing data to be used by the source:

>>> class Context(object):
...      values = []
>>> import zc.sourcefactory.contextual
>>> class MyDynamicSource(
...     zc.sourcefactory.contextual.BasicContextualSourceFactory):
...     def getValues(self, context):
...         return context.values

When instanciating, we get a ContextSourceBinder:

>>> binder = MyDynamicSource()
>>> zope.schema.interfaces.IContextSourceBinder.providedBy(binder)
True

Binding it to a context, we get a source:

>>> context = Context()
>>> source = binder(context)
>>> zope.schema.interfaces.ISource.providedBy(source)
True
>>> list(source)
[]

Modifying the context also modifies the data in the source:

>>> context.values = [1,2,3,4]
>>> list(source)
[1, 2, 3, 4]
>>> 1 in source
True
>>> len(source)
4

It’s possible to have the default machinery return different sources, by providing a source_class argument when calling the binder. One can also provide arguments to the source.

>>> class MultiplierSource(zc.sourcefactory.source.FactoredContextualSource):
...     def __init__(self, factory, context, multiplier):
...         super(MultiplierSource, self).__init__(factory, context)
...         self.multiplier = multiplier
...
...     def _get_filtered_values(self):
...         for value in self.factory.getValues(self.context):
...             yield self.multiplier * value
>>> class MultiplierSourceFactory(MyDynamicSource):
...     source_class = MultiplierSource
>>> binder = MultiplierSourceFactory()
>>> source = binder(context, multiplier=5)
>>> list(source)
[5, 10, 15, 20]
>>> 5 in source
True
>>> len(source)
4

Filtering

Additional to providing the getValues-method you can also provide a filterValue-method that will allow you to reduce the items from the list, piece by piece.

This is useful if you want to have more specific sources (by subclassing) that share the same basic origin of the data but have different filters applied to it:

>>> class FilteringSource(zc.sourcefactory.basic.BasicSourceFactory):
...     def getValues(self):
...         return iter(range(1,20))
...     def filterValue(self, value):
...         return value % 2
>>> source = FilteringSource()
>>> list(source)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

Subclassing modifies the filter, not the original data:

>>> class OtherFilteringSource(FilteringSource):
...     def filterValue(self, value):
...         return not value % 2
>>> source = OtherFilteringSource()
>>> list(source)
[2, 4, 6, 8, 10, 12, 14, 16, 18]

The “in” operator gets applied also to filtered values:

>>> 2 in source
True
>>> 3 in source
False

The “len” also gets applied to filtered values:

>>> len(source)
9

Scaling

Sometimes the number of items available through a source is very large. So large that you only want to access them if absolutely neccesary. One such occasion is with truth-testing a source. By default Python will call __nonzero__ to get the boolean value of an object, but if that isn’t available __len__ is called to see what it returns. That might be very expensive, so we want to make sure it isn’t called.

>>> class MyExpensiveSource(zc.sourcefactory.basic.BasicSourceFactory):
...     def getValues(self):
...         yield 'a'
...         raise RuntimeError('oops, iterated too far')
>>> source = MyExpensiveSource()
>>> bool(source)
True

Simple case

In the most simple case, you only have to provide a method that returns a list of values and derive from BasicSourceFactory:

>>> import zc.sourcefactory.basic
>>> class MyStaticSource(zc.sourcefactory.basic.BasicSourceFactory):
...     def getValues(self):
...         return ['a', 'b', 'c']

When calling the source factory, we get a source:

>>> source = MyStaticSource()
>>> import zope.schema.interfaces
>>> zope.schema.interfaces.ISource.providedBy(source)
True

The values match our getValues-method of the factory:

>>> list(source)
['a', 'b', 'c']
>>> 'a' in source
True
>>> len(source)
3

WARNING about the standard adapters for ITerms

The standard adapters for ITerms are only suitable if the value types returned by your getValues function are homogenous. Mixing integers, persistent objects, strings, and unicode within one source may create non-unique tokens. In this case, you have to provide a custom getToken-method to provide unique and unambigous tokens.

Mapping source values

Sometimes a source provides the right choice of objects, but the actual values we want to talk about are properties or computed views on those objects. The mapping proxy source helps us to map a source to a different value space.

We start out with a source:

>>> source = [1,2,3,4,5]

and we provide a method that maps the values of the original source to the values we want to see (we map the numbers to the characters in the english alphabet):

>>> map = lambda x: chr(x+96)

Now we can create a mapped source:

>>> from zc.sourcefactory.mapping import ValueMappingSource
>>> mapped_source = ValueMappingSource(source, map)
>>> list(mapped_source)
['a', 'b', 'c', 'd', 'e']
>>> len(mapped_source)
5
>>> 'a' in mapped_source
True
>>> 1 in mapped_source
False

You can also use context-dependent sources:

>>> def bindSource(context):
...     return [1,2,3,4,5]
>>> from zc.sourcefactory.mapping import ValueMappingSourceContextBinder
>>> binder = ValueMappingSourceContextBinder(bindSource, map)
>>> bound_source = binder(object())
>>> list(bound_source)
['a', 'b', 'c', 'd', 'e']
>>> len(bound_source)
5
>>> 'a' in bound_source
True
>>> 1 in bound_source
False

Scaling

Sometimes the number of items available through a source is very large. So large that you only want to access them if absolutely neccesary. One such occasion is with truth-testing a source. By default Python will call __nonzero__ to get the boolean value of an object, but if that isn’t available __len__ is called to see what it returns. That might be very expensive, so we want to make sure it isn’t called.

>>> class ExpensiveSource(object):
...     def __len__(self):
...         raise RuntimeError("oops, don't want to call __len__")
...
...     def __iter__(self):
...         return iter(range(999999))
>>> expensive_source = ExpensiveSource()
>>> mapped_source = ValueMappingSource(expensive_source, map)
>>> bool(mapped_source)
True

Custom constructors

Source factories are intended to behave as natural as possible. A side-effect of using a custom factory method (__new__) on the base class is that sub-classes may have a hard time if their constructor (__init__) has a different signature.

zc.sourcefactory takes extra measures to allow using a custom constructor with a different signature.

>>> import zc.sourcefactory.basic
>>> class Source(zc.sourcefactory.basic.BasicSourceFactory):
...
...     def __init__(self, values):
...         super(Source, self).__init__()
...         self.values = values
...
...     def getValues(self):
...         return self.values
>>> source = Source([1, 2, 3])
>>> list(source)
[1, 2, 3]

This is also true for contextual sources. The example is a bit silly but it shows that it works in principal:

>>> import zc.sourcefactory.contextual
>>> default_values = (4, 5, 6)
>>> context_values = (6, 7, 8)
>>> class ContextualSource(
...     zc.sourcefactory.contextual.BasicContextualSourceFactory):
...
...     def __init__(self, defaults):
...         super(ContextualSource, self).__init__()
...         self.defaults = defaults
...
...     def getValues(self, context):
...         return self.defaults + context
>>> contextual_source = ContextualSource(default_values)(context_values)
>>> list(contextual_source)
[4, 5, 6, 6, 7, 8]

Common adapters for sources

To allow adapting factored sources specific to the factory, a couple of standard interfaces that can be adapters are re-adapted as using a multi-adapter for (FactoredSource, SourceFactory).

ISourceQueriables

>>> from zc.sourcefactory.basic import BasicSourceFactory
>>> class Factory(BasicSourceFactory):
...     def getValues(self):
...         return [1,2,3]
>>> source = Factory()
>>> from zope.schema.interfaces import ISourceQueriables
>>> import zope.interface
>>> @zope.interface.implementer(ISourceQueriables)
... class SourceQueriables(object):
...     def __init__(self, source, factory):
...         self.source = source
...         self.factory = factory
...     def getQueriables(self):
...         return [('test', None)]
>>> from zc.sourcefactory.source import FactoredSource
>>> zope.component.provideAdapter(factory=SourceQueriables,
...                               provides=ISourceQueriables,
...                               adapts=(FactoredSource, Factory))
>>> queriables = ISourceQueriables(source)
>>> queriables.factory
<Factory object at 0x...>
>>> queriables.source
<zc.sourcefactory.source.FactoredSource object at 0x...>
>>> queriables.getQueriables()
[('test', None)]

Cleanup

>>> zope.component.getSiteManager().unregisterAdapter(factory=SourceQueriables,
...     provided=ISourceQueriables, required=(FactoredSource, Factory))
True

Browser views for sources created by source factories

Sources that were created using source factories already come with ready-made terms and term objects.

Simple use

Let’s start with a simple source factory:

>>> import zc.sourcefactory.basic
>>> class DemoSource(zc.sourcefactory.basic.BasicSourceFactory):
...     def getValues(self):
...         return [b'a', b'b', b'c', b'd']
>>> source = DemoSource()
>>> list(source)
[b'a', b'b', b'c', b'd']

We need a request first, then we can adapt the source to ITerms:

>>> from zope.publisher.browser import TestRequest
>>> import zope.browser.interfaces
>>> import zope.component
>>> request = TestRequest()
>>> terms = zope.component.getMultiAdapter(
...     (source, request), zope.browser.interfaces.ITerms)
>>> terms
<zc.sourcefactory.browser.source.FactoredTerms object at 0x...>

For each value we get a factored term:

>>> terms.getTerm(b'a')
<zc.sourcefactory.browser.source.FactoredTerm object at 0x...>
>>> terms.getTerm(b'b')
<zc.sourcefactory.browser.source.FactoredTerm object at 0x...>
>>> terms.getTerm(b'c')
<zc.sourcefactory.browser.source.FactoredTerm object at 0x...>
>>> terms.getTerm(b'd')
<zc.sourcefactory.browser.source.FactoredTerm object at 0x...>

Unicode values are allowed as well:

>>> terms.getTerm('\xd3')
<zc.sourcefactory.browser.source.FactoredTerm object at 0x...>

Our terms are ITitledTokenizedTerm-compatible:

>>> import zope.schema.interfaces
>>> zope.schema.interfaces.ITitledTokenizedTerm.providedBy(
...     terms.getTerm('a'))
True

In the most simple case, the title of a term is the string representation of the object:

>>> terms.getTerm('a').title
'a'

If an adapter from the value to IDCDescriptiveProperties exists, the title will be retrieved from this adapter:

>>> import persistent
>>> class MyObject(persistent.Persistent):
...    custom_title = 'My custom title'
...    _p_oid = 12
>>> class DCDescriptivePropertiesAdapter(object):
...    def __init__(self, context):
...        self.title = context.custom_title
...        self.description = u""
>>> from zope.component import provideAdapter
>>> from zope.dublincore.interfaces import IDCDescriptiveProperties
>>> provideAdapter(DCDescriptivePropertiesAdapter, [MyObject],
...     IDCDescriptiveProperties)
>>> terms.getTerm(MyObject()).title
'My custom title'

Extended use: provide your own titles

Instead of relying on string representation or IDCDescriptiveProperties adapters you can specify the getTitle method on the source factory to determine the title for a value:

>>> class DemoSourceWithTitles(DemoSource):
...     def getTitle(self, value):
...         return 'Custom title ' + value.custom_title
>>> source2 = DemoSourceWithTitles()
>>> terms2 = zope.component.getMultiAdapter(
...     (source2, request), zope.browser.interfaces.ITerms)
>>> o1 = MyObject()
>>> o1.custom_title = u"Object one"
>>> o2 = MyObject()
>>> o2.custom_title = u"Object two"
>>> terms2.getTerm(o1).title
'Custom title Object one'
>>> terms2.getTerm(o2).title
'Custom title Object two'

Extended use: provide your own tokens

Instead of relying on default adapters to generate tokens for your values, you can override the getToken method on the source factory to determine the token for a value:

>>> class DemoObjectWithToken(object):
...     token = None
>>> o1 = DemoObjectWithToken()
>>> o1.token = "one"
>>> o2 = DemoObjectWithToken()
>>> o2.token = "two"
>>> class DemoSourceWithTokens(DemoSource):
...     values = [o1, o2]
...     def getValues(self):
...         return self.values
...     def getToken(self, value):
...         return value.token
>>> source3 = DemoSourceWithTokens()
>>> terms3 = zope.component.getMultiAdapter(
...     (source3, request), zope.browser.interfaces.ITerms)
>>> terms3.getTerm(o1).token
'one'
>>> terms3.getTerm(o2).token
'two'

Looking up by the custom tokens works as well:

>>> terms3.getValue("one") is o1
True
>>> terms3.getValue("two") is o2
True
>>> terms3.getValue("three")
Traceback (most recent call last):
KeyError: "No value with token 'three'"

Value mapping sources

XXX to come

Contextual sources

Let’s start with an object that we can use as the context:

>>> zip_to_city = {'06112': 'Halle',
...                '06844': 'Dessa'}
>>> import zc.sourcefactory.contextual
>>> class DemoContextualSource(
...     zc.sourcefactory.contextual.BasicContextualSourceFactory):
...     def getValues(self, context):
...         return context.keys()
...     def getTitle(self, context, value):
...         return context[value]
...     def getToken(self, context, value):
...         return 'token-%s' % value
>>> source = DemoContextualSource()(zip_to_city)
>>> sorted(list(source))
['06112', '06844']

Let’s look at the terms:

>>> terms = zope.component.getMultiAdapter(
...     (source, request), zope.browser.interfaces.ITerms)
>>> terms
<zc.sourcefactory.browser.source.FactoredContextualTerms object at 0x...>

For each value we get a factored term with the right title from the context:

>>> terms.getTerm('06112')
<zc.sourcefactory.browser.source.FactoredTerm object at 0x...>
>>> terms.getTerm('06112').title
'Halle'
>>> terms.getTerm('06844')
<zc.sourcefactory.browser.source.FactoredTerm object at 0x...>
>>> terms.getTerm('06844').title
'Dessa'
>>> terms.getTerm('06844').token
'token-06844'

And in reverse we can get the value for a given token as well:

>>> terms.getValue('token-06844')
'06844'

Interfaces

Both the FactoredSource and FactoredContextualSource have associated interfaces.

>>> from zc.sourcefactory import interfaces
>>> from zc.sourcefactory import source
>>> from zope import interface
>>> interface.classImplements(
...     source.FactoredSource, interfaces.IFactoredSource)
>>> interface.classImplements(
...     source.FactoredContextualSource, interfaces.IContextualSource)

Tokens

Tokens are an identifying representation of an object, suitable for transmission amongs URL-encoded data.

The sourcefactory package provides a few standard generators for tokens:

>>> import zc.sourcefactory.browser.token

We have generators for strings:

>>> zc.sourcefactory.browser.token.fromString('somestring')
'1f129c42de5e4f043cbd88ff6360486f'

Unicode

Argh, I have to write the umlauts as unicode escapes otherwise distutils will have a encoding error in preparing upload to pypi:

>>> zc.sourcefactory.browser.token.fromUnicode(
...     'somestring with umlauts \u00F6\u00E4\u00FC')
'45dadc304e0d6ae7f4864368bad74951'

Integer

>>> zc.sourcefactory.browser.token.fromInteger(12)
'12'

Persistent

>>> import persistent
>>> class PersistentDummy(persistent.Persistent):
...     pass
>>> p = PersistentDummy()
>>> p._p_oid = 1234
>>> zc.sourcefactory.browser.token.fromPersistent(p)
'1234'

If an object is persistent but has not been added to a database yet, it will be added to the database of it’s __parent__:

>>> root = rootFolder
>>> p1 = PersistentDummy()
>>> p1.__parent__ = root
>>> zc.sourcefactory.browser.token.fromPersistent(p1)
'0x01'

If an object has no parent, we fail:

>>> p2 = PersistentDummy()
>>> zc.sourcefactory.browser.token.fromPersistent(p2)
Traceback (most recent call last):
...
ValueError: Can not determine OID for <builtins.PersistentDummy object at 0x...>

Security proxied objects are unwrapped to get to their oid or connection attribute:

>>> from zope.security.proxy import ProxyFactory
>>> p3 = PersistentDummy()
>>> root['p3'] = p3
>>> p3.__parent__ = root
>>> p3p = ProxyFactory(p3)
>>> p3p._p_jar
Traceback (most recent call last):
  ...
zope.security.interfaces.ForbiddenAttribute: ('_p_jar', <builtins.PersistentDummy object at 0x...>)
>>> zc.sourcefactory.browser.token.fromPersistent(p3p)
'0x02'

As a side-effect p3 now has an _p_oid assigned. When an object already has an OID the connection is not queried, so a __parent__ would not be necessary:

>>> del p3.__parent__
>>> zc.sourcefactory.browser.token.fromPersistent(p3p)
'0x02'

Interfaces

>>> from zope.interface import Interface
>>> class I(Interface):
...     pass
>>> zc.sourcefactory.browser.token.fromInterface(I)
'builtins.I'

Changes

3.1 (2026-11-05)

  • Move package metadata from setup.py to pyproject.toml.

  • Add support for Python 3.14, 3.15.

  • Drop support for Python 3.10, 3.9.

3.0 (2025-09-18)

  • Replace pkg_resources namespace with PEP 420 native namespace.

  • Add support for Python 3.12, 3.13.

  • Drop support for Python 3.7, 3.8.

2.0 (2023-02-23)

  • Add support for Python 3.8, 3.9, 3.10, 3.11.

  • Drop support for Python 2.7, 3.5, 3.6.

1.1 (2018-11-07)

  • Add support for Python 3.6 and 3.7.

  • Drop support for Python 3.3 and 3.4.

1.0.0 (2016-08-02)

  • Claim support for Python 3.4 and 3.5.

  • Drop support for Python 2.6.

1.0.0a1 (2013-02-23)

  • Added support for Python 3.3.

  • Drastically reduce testing dependencies to make porting easier.

  • Replaced deprecated zope.interface.implements usage with equivalent zope.interface.implementer decorator.

  • Dropped support for Python 2.4 and 2.5.

0.8.0 (2013-10-04)

  • BasicSourceFactory now uses a class variable to tell what kind of source to make. (Same mechanism as it was added for ContextualSourceFactory in version 0.5.0).

0.7.0 (2010-09-17)

  • Using Python’s doctest instead of deprecated zope.testing.doctest.

  • Using zope.keyreference as test dependency instead of zope.app.keyreference.

0.6.0 (2009-08-15)

  • Change package homepage to PyPI instead of Subversion.

  • Dropped Support for Zope 3.2 by removing a conditional import.

  • Use hashlib for Python 2.5 and later to avoid deprecation warnings.

0.5.0 (2009-02-03)

  • FactoredContextualSourceBinder.__call__ now accepts arguments giving the args to pass to source class. ContextualSourceFactory now uses a class variable to tell what kind of Source to make.

  • Use zope.intid instead of zope.app.intid.

  • Corrected e-mail address as zope3-dev@zope.org has been retired.

0.4.0 (2008-12-11)

  • Removed zope.app.form dependency. Changed ITerms import from zope.app.form.browser.interfaces to zope.browser.interfaces. [projekt01]

0.3.5 (2008-12-08)

  • Fixed bug in __new__ of contexual factories that would disallow subclasses to use constructors that expect a different signature. [icemac]

0.3.4 (2008-08-27)

  • Added all documents in package to long description, so they are readable in pypi. [icemac]

0.3.3 (2008-06-10)

  • Fixed bug in __new__ of factories that would disallow subclasses to use constructors that expect a different signature. (Thanks to Sebastian Wehrmann for the patch.)

0.3.2 (2008-04-09)

  • Fixed scalability bug caused by missing __nonzero__ on ValueMappingSource

0.3.1 (2008-02-12)

  • Fixed scalability bug caused by missing __nonzero__ on BasicSourceFactory

0.3.0 (??????????)

  • Added class-level defaults for attributes that are declared in the interfaces to not have the Zope 2 security machinery complain about them.

0.2.1 (2007-07-10)

  • Fixed a bug in the contextual token policy that was handling the resolution of values for a given token incorrectly.

0.2.0 (2007-07-10)

  • Added a contextual token policy interface that allows getToken and getValue to access the cotext for contextual sources.

  • Added a contextual term policy interface that allows createTerm and getTitle to access the context for contextual sources.

  • Added compatibility for Zope 3.2 and Zope 2.9 (via Five 1.3)

Metadata

Release files for zc.sourcefactory 3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zc.sourcefactory 3.1
File Size Uploaded Provenance Actions
zc_sourcefactory-3.1.tar.gz 30.7 kB With provenance Details for zc_sourcefactory-3.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for zc.sourcefactory 3.1
File Interpreter ABI Platform Actions
zc_sourcefactory-3.1-py3-none-any.whl Python 3 none any Details for zc_sourcefactory-3.1-py3-none-any.whl

Total release size: 63.1 kB

Release files / zc_sourcefactory-3.1.tar.gz

Download URL zc_sourcefactory-3.1.tar.gz
Size 30.7 kB
Tags Source
SHA-256 checksum
How to use checksums
0d298015b18f40100462f4bae9425ea5ddad0850c40f7b749184fb9fe86055c7
BLAKE2b-256 checksum
How to use checksums
e5eed5a6b156e1271523f1210d670e97cfa9af5a3703ffc27711cdde54f2c94d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

Release files / zc_sourcefactory-3.1-py3-none-any.whl

Download URL zc_sourcefactory-3.1-py3-none-any.whl
Size 32.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e75e9ddefed4d07330c8c0a580e5265270fdb1ac73af0899596214431315e2bd
BLAKE2b-256 checksum
How to use checksums
c1e97d7b9e7f1a19df2e245a64431de2ae928d148c8d4a49d70755f36609fa91
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

Security for zc.sourcefactory 3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for zc_sourcefactory-3.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page