Package: c2cwsgiutils
Camptocamp WSGI utilitiesThis is a Python 3 library (>=3.5) providing common tools for Camptocamp WSGI
applications:Provide prometheus metricsAllow to use a master/slave PostgresQL configurationLogging handler for CEE/UDP logsAn optional view to change runtime the log levelsSQL profiler to debug DB performance problems, disabled by default. Warning, it will slow down everything.A view to get the version information about the application and the installed packagesA framework for implementing a health_check serviceError handlers to send JSON messages to the client in case of errorA cornice service drop in replacement for setting up CORSAlso provide tools for writing acceptance tests:A class that can be used from a py.test fixture to control a compositionA class that can be used from a py.text fixture to test a REST APIAs an example on how to use it in an application provided by a Docker image, you can look at the
test application inacceptance_tests/app.
To see how to test such an application, look atacceptance_tests/tests.InstallCustom Docker image (from PYPI library)Here we didn't do a minimal install of c2cwsgiutils, but be put in place everything needed to
monitor the application in integration and production environment.The library is available in PYPI:https://pypi.python.org/pypi/c2cwsgiutilsCopy and adapt these template configuration file into your project:production.ini;gunicorn.conf.py.
Then replacec2cwsgiutils_appby your package name.You should installc2cwsgiutilswith the tool you use to manage your pip dependencies.In theDockerfileyou should add the following lines:# Generate the version file.RUNc2cwsgiutils-genversion$(gitrev-parseHEAD)CMD["gunicorn","--paste=/app/production.ini"]# Default values for the environment variablesENV\DEVELOPMENT=0\SQLALCHEMY_POOL_RECYCLE=30\SQLALCHEMY_POOL_SIZE=5\SQLALCHEMY_MAX_OVERFLOW=25\SQLALCHEMY_SLAVE_POOL_RECYCLE=30\SQLALCHEMY_SLAVE_POOL_SIZE=5\SQLALCHEMY_SLAVE_MAX_OVERFLOW=25\LOG_TYPE=console\OTHER_LOG_LEVEL=WARNING\GUNICORN_LOG_LEVEL=WARNING\SQL_LOG_LEVEL=WARNING\C2CWSGIUTILS_LOG_LEVEL=WARNING\LOG_LEVEL=INFOAdd in yourmainfunction.config.include("c2cwsgiutils.pyramid")dbsession=c2cwsgiutils.db.init(config,"sqlalchemy","sqlalchemy_slave")config.scan(...)# Initialize the health checkshealth_check=c2cwsgiutils.health_check.HealthCheck(config)health_check.add_db_session_check(dbsession)health_check.add_alembic_check(dbsession,"/app/alembic.ini",1)The related environment variables:DEVELOPMENT: set to1to enable the development mode, default is0.SQLALCHEMY_URL: SQL alchemy URL, likepostgresql://user:password@host:port/dbname.SQLALCHEMY_POOL_RECYCLE: The SQL alchemy pool recycle, default is30.SQLALCHEMY_POOL_SIZE: The SQL alchemy pool size, default is5.SQLALCHEMY_MAX_OVERFLOW: SQL alchemy max overflow, default is25.SQLALCHEMY_SLAVE_URL: The SQL alchemy slave (read only) URL, likepostgresql://user:password@host:port/dbname.SQLALCHEMY_SLAVE_POOL_RECYCLE: The SQL alchemy slave pool recycle, default is30.SQLALCHEMY_SLAVE_POOL_SIZE: The SQL alchemy slave pool size, default is5.SQLALCHEMY_SLAVE_MAX_OVERFLOW: The SQL alchemy slave max overflow, default is25.GUNICORN_WORKERS: The number of workers, default is2.GUNICORN_THREADS: The number of threads per worker, default is10.LOG_TYPE: The types of logs, default isconsole, should bejsonon kubernetes to work well withelk.LOG_LEVEL: The application log level, default isINFO.SQL_LOG_LEVEL: The SQL query log level,WARNING: no logs,INFO: logs the queries,DEBUGalso logs the results, default isWARNING.GUNICORN_ERROR_LOG_LEVEL: The Gunicorn error log level, default isWARNING.C2CWSGIUTILS_CONFIG: The fallback ini file to use by gunicorn, default isproduction.ini.C2CWSGIUTILS_LOG_LEVEL: The c2c WSGI utils log level, default isWARNING.OTHER_LOG_LEVEL: The log level for all the other logger, default isWARNING.Those environment variables can be useful for investigation on production environments.Docker (deprecated)Or (deprecated) as a base Docker image:camptocamp/c2cwsgiutils:release_5orghcr.io/camptocamp/c2cwsgiutils:release_5If you need an image with a smaller foot print, use the tags prefixed with-light. Those are without
GDAL and without the build tools.We deprecate the Docker image because:The project wants to choose the base image.The project pin different versions of the dependencies.General configIn general, configuration can be done both with environment variables (taken first) or with entries in theproduction.inifile.You can configure the base URL for accessing the views provided by c2cwsgiutils with an environment variable
namedC2C_BASE_PATHor in theproduction.inifile with a property namedc2c.base_path.A few REST APIs are added and can be seen with this URL:{C2C_BASE_PATH}.Some APIs are protected by a secret. This secret is specified in theC2C_SECRETvariable orc2c.secretproperty. It is either passed as thesecretquery parameter or theX-API-Keyheader. Once
accessed with a good secret, a cookie is stored and the secret can be omitted.An alternative of usingC2C_SECRETis to use an authentication on GitHub,create the GitHub application.Configure the json renderers with theC2C_JSON_PRETTY_PRINTandC2C_JSON_SORT_KEYSenvironment
variables orc2c.json.pretty_printandc2c.json.sort_keysproperties. Default isfalse.Then it will redirect the user to the github authentication form if not already authenticated
(usingC2C_AUTH_GITHUB_CLIENT_ID,C2C_AUTH_GITHUB_CLIENT_SECRETandC2C_AUTH_GITHUB_SCOPE).Then we will check if the user is allowed to access to the application, for that we check
if the user has enough right on a GitHub repository (usingC2C_AUTH_GITHUB_REPOSITORYandC2C_AUTH_GITHUB_REPOSITORY_ACCESS_TYPE).Finally we store the session information in an encrypted cookie (usingC2C_AUTH_SECRETandC2C_AUTH_COOKIE).Configuration details:Using the environment variableC2C_AUTH_GITHUB_REPOSITORYor the config keyc2c.auth.github.repositoryto define the related GitHub repository (required).Using the environment variableC2C_AUTH_GITHUB_ACCESS_TYPEor the config keyc2c.auth.github.access_typeto define the type of required access can bepull,pushoradmin(default ispush)Using the environment variableC2C_AUTH_GITHUB_CLIENT_IDor the config keyc2c.auth.github.client_idto
define the GitHub application ID (required)Using the environment variableC2C_AUTH_GITHUB_CLIENT_SECRETor the config keyc2c.auth.github.client_secretto define the GitHub application secret (required)Using the environment variableC2C_AUTH_GITHUB_SCOPEor the config keyc2c.auth.github.scopeto define
the GitHub scope (default isrepo), seeGitHub documentationUsing the environment variableC2C_AUTH_GITHUB_SECRETor the config keyc2c.auth.github.auth.secretto
define the used secret for JWD encryption (required, with a length at least of 16)Using the environment variableC2C_AUTH_GITHUB_COOKIEor the config keyc2c.auth.github.auth.cookieto
define the used cookie name (default isc2c-auth-jwt)Using the environment variableC2C_AUTH_GITHUB_AUTH_URLor the config keyc2c.auth.github.auth_urlto
define the GitHub auth URL (default ishttps://github.com/login/oauth/authorize)Using the environment variableC2C_AUTH_GITHUB_TOKEN_URLor the config keyc2c.auth.github.token_urlto
define the GitHub auth URL (default ishttps://github.com/login/oauth/access_token)Using the environment variableC2C_AUTH_GITHUB_USER_URLor the config keyc2c.auth.github.user_urlto
define the GitHub auth URL (default ishttps://api.github.com/user)Using the environment variableC2C_AUTH_GITHUB_REPO_URLor the config keyc2c.auth.github.repo_urlto
define the GitHub auth URL (default ishttps://api.github.com/repo)Using the environment variableC2C_AUTH_GITHUB_PROXY_URLor the config keyc2c.auth.github.auth.proxy_urlto
define a redirect proxy between GitHub and our application to be able to share an OAuth2 application on GitHub (default is no proxy).
Made to work withthis proxy.Using the environment variableC2C_USE_SESSIONor the config keyc2c.use_sessionto
define if we use a session. Currently, we can use the session to store a state, used to prevent CSRF, during OAuth2 login (default isfalse)PyramidAll the environment variables are usable in the configuration file using stuff like%(ENV_NAME)s.To enable most of the features of c2cwsgiutils, you need to add this line to your WSGI main:importc2cwsgiutils.pyramidconfig.include(c2cwsgiutils.pyramid.includeme)Error catching views will be put in place to return errors as JSON.A custom loader is provided to run pyramid scripts against configuration files containing environment variables:proutesc2c://production.ini# relative pathproutesc2c:///app/production.ini# absolute pathA filter is automatically installed to handle the HTTP headers set by common proxies and have correct values
in the request object (request.client_addr, for example). This filter is equivalent to what thePasteDeploy#prefix(minus the prefix part) does, but supports newer headers as well (Forwarded).
If you need to prefix your routes, you can use theroute_prefixparameter of theConfiguratorconstructor.LoggingTwo new logging backends are provided:c2cwsgiutils.pyramid_logging.PyramidCeeSysLogHandler: to send @cee formatted logs to syslog through UDP.c2cwsgiutils.pyramid_logging.JsonLogHandler: to output (on stdout or stderr) JSON formatted logs.Look at the logging configuration part ofacceptance_tests/app/production.inifor paste and commands line.The logging configuration is imported automatically by gunicorn, it is possible to visualize the dict config by setting the environment variableDEBUG_LOGCONFIG=1.You can enable a view to configure the logging level on a live system using theC2C_LOG_VIEW_ENABLEDenvironment
variable. Then, the current status of a logger can be queried with a GET on{C2C_BASE_PATH}/logging/level?secret={C2C_SECRET}&name={logger_name}and can be changed with{C2C_BASE_PATH}/logging/level?secret={C2C_SECRET}&name={logger_name}&level={level}. Overrides are stored in
Redis, ifC2C_REDIS_URL(c2c.redis_url) orC2C_REDIS_SENTINELSis configured.Database maintenanceYou can enable a view to force usage of the slave engine using theC2C_DB_MAINTENANCE_VIEW_ENABLEDenvironment
variable. Then, the database can be made "readonly" with{C2C_BASE_PATH}/db/maintenance?secret={C2C_SECRET}&readonly=true.
The current state is stored in Redis, ifC2C_REDIS_URL(c2c.redis_url) orC2C_REDIS_SENTINELSis configured.Request trackingIn order to follow the logs generated by a request across all the services (think separate processes),
c2cwsgiutils tries to flag averything with a request ID. This field can come from the input as request headers
(X-Request-ID,X-Correlation-ID,Request-IDorX-Varnish) or will default to a UUID. You can add an
additional request header as source for that by defining theC2C_REQUEST_ID_HEADERenvironment variable
(c2c.request_id_header).In JSON logging formats, arequest_idfield is automatically added.You can enable (disabled by default since it can have a cost) the flagging of the SQL requests as well by
setting the C2C_SQL_REQUEST_ID environment variable (or c2c.sql_request_id in the .ini file). This will use
the application name to pass along the request id. If you do that, you must include the application name in
the PostgreSQL logs by settinglog_line_prefixto something like"%a "(don't forget the space).Then, in your application, it is recommended to transmit the request ID to the external REST APIs. Use
theX-Request-IDHTTP header, for example. The value of the request ID is accessible through an addedc2c_request_idattribute on the Pyramid Request objects. Therequestsmodule is patched to automatically
add this header.The requests module is also patched to monitor requests done without timeout. In that case, you can
configure a default timeout with theC2C_REQUESTS_DEFAULT_TIMEOUTenvironment variable
(c2c.requests_default_timeout). If no timeout and no default is specified, a warning is issued.SQL profilerThe SQL profiler must be configured with theC2C_SQL_PROFILER_ENABLEDenvironment variable. That enables a view
to query the status of the profiler ({C2C_BASE_PATH}/sql_profiler?secret={C2C_SECRET}) or to
enable/disable it ({C2C_BASE_PATH}/sql_profiler?secret={C2C_SECRET}&enable={1|0}).If enabled, for eachSELECTquery sent by SQLAlchemy, another query it done withEXPLAIN ANALYZEprepended to it. The results are sent to thec2cwsgiutils.sql_profilerlogger.Don't enable that on a busy production system. It will kill your performances.ProfilerC2cwsgiutils provide an easy way to profile an application:With a decorator:fromc2cwsgiutils.profileimportProfiler@Profile('/my_file.prof')my_function():...Or with thewithstatement:fromc2cwsgiutils.profileimportProfilerwithProfile('/my_file.prof'):...Then open your file with SnakeViz:dockercpcontainer_name:/my_file.prof.
pipinstall--usersnakeviz
snakevizmy_file.profDB sessionsThec2cwsgiutils.db.initallows you to setup a DB session that has two engines for accessing a
master/slave PostgresQL setup. The slave engine (read only) will be used automatically forGETandOPTIONSrequests and the master engine (read write) will be used for the other queries.To use that, yourproduction.inimust look like that:sqlalchemy.url=%(SQLALCHEMY_URL)ssqlalchemy.pool_recycle=%(SQLALCHEMY_POOL_RECYCLE)ssqlalchemy.pool_size=%(SQLALCHEMY_POOL_SIZE)ssqlalchemy.max_overflow=%(SQLALCHEMY_MAX_OVERFLOW)ssqlalchemy_slave.url=%(SQLALCHEMY_SLAVE_URL)ssqlalchemy_slave.pool_recycle=%(SQLALCHEMY_SLAVE_POOL_RECYCLE)ssqlalchemy_slave.pool_size=%(SQLALCHEMY_SLAVE_POOL_SIZE)ssqlalchemy_slave.max_overflow=%(SQLALCHEMY_SLAVE_MAX_OVERFLOW)sAnd your code that initializes the DB connection must look like that:importc2cwsgiutils.dbdefmain(config):c2cwsgiutils.db.init(config,'sqlalchemy','sqlalchemy_slave',force_slave=["POST /api/hello"])[0]You can use theforce_slaveandforce_masterparameters to override the defaults and force a route to use
the master or the slave engine.Health checksTo enable health checks, you must add some setup in your WSGI main (usually after the DB connections are
setup). For example:fromc2cwsgiutils.health_checkimportHealthCheckdefcustom_check(request):globalnot_happyifnot_happy:raiseException("I'm not happy")return"happy"health_check=HealthCheck(config)health_check.add_db_session_check(models.DBSession,at_least_one_model=models.Hello)health_check.add_url_check('http://localhost:8080/api/hello')health_check.add_custom_check('custom',custom_check,2)health_check.add_alembic_check(models.DBSession,'/app/alembic.ini',3)Then, the URL{C2C_BASE_PATH}/health_check?max_level=3can be used to run the health checks and get a report
looking like that (in case of error):{"status":500,"successes":{"db_engine_sqlalchemy":{"timing":0.002},"db_engine_sqlalchemy_slave":{"timing":0.003},"http://localhost/api/hello":{"timing":0.01},"alembic_app_alembic.ini_alembic":{"timing":0.005,"result":"4a8c1bb4e775"}},"failures":{"custom":{"message":"I'm not happy","timing":0.001}}}The levels are:0: Don't add checks at this level. This max_level is used for doing a simple ping.1: Checks for anything vital for the usefulness of the service (DB, redis, ...). This is the max_level set
by default and used by load balancers to determine if the service is alive.>=2: Use those at your convenience. Pingdom and CO are usually setup at max_level=100. So stay below.The URL{C2C_BASE_PATH}/health_check?checks=<check_name>can be used to run the health checks on some
checks, coma separated list.When you instantiate theHealthCheckclass, two checks may be automatically enabled:If redis is configured, check that redis is reachable.If redis is configured and the version information is available, check that the version matches
across all instances.Look at the documentation of thec2cwsgiutils.health_check.HealthCheckclass for more information.SQLAlchemy models graphA command is provided that can generate Doxygen graphs of an SQLAlchemy ORM model.
Seeacceptance_tests/app/models_graph.pyhow it's used.Version informationIf the/app/versions.jsonexists, a view is added ({C2C_BASE_PATH}/versions.json) to query the current
version of a app. This file is generated by calling thec2cwsgiutils-genversion [$GIT_TAG] $GIT_HASHcommand line. Usually done in theDockerfileof the WSGI application.PrometheusPrometheus clientis integrated in c2cwsgiutils.It will work in multi process mode with the limitation listed in theprometheus_clientdocumentation.To enable it you should provide theC2C_PROMETHEUS_PORTenvironment variable.
For security reason, this port should not be exposed.We can customize it with the following environment variables:C2C_PROMETHEUS_PREFIX: to customize the prefix, default isc2cwsggiutils-.C2C_PROMETHEUS_PACKAGESthe packages that will be present in the version information, default isc2cwsgiutils,pyramid,gunicorn,sqlalchemy.C2C_PROMETHEUS_APPLICATION_PACKAGEthe packages that will be present in the version information as application.And you should add in yourgunicorn.conf.py:fromprometheus_clientimportmultiprocessdefon_starting(server):fromc2cwsgiutilsimportprometheusdelserverprometheus.start()defpost_fork(server,worker):fromc2cwsgiutilsimportprometheusdelserver,workerprometheus.cleanup()defchild_exit(server,worker):delservermultiprocess.mark_process_dead(worker.pid)In yourDockerfileyou should add:RUNmkdir-p/prometheus-metrics\&&chmoda+rwx/prometheus-metricsENVPROMETHEUS_MULTIPROC_DIR=/prometheus-metricsAdd custom metric collectorSeeofficial documentation.Related to the Unix process.fromc2cwsgiutilsimportbroadcast,prometheusprometheus.MULTI_PROCESS_COLLECTOR_BROADCAST_CHANNELS.append("prometheus_collector_custom")broadcast.subscribe("c2cwsgiutils_prometheus_collect_gc",_broadcast_collector_custom)my_custom_collector_instance=MyCustomCollector()def_broadcast_collector_custom()->List[prometheus.SerializedGauge]:"""Get the collected GC gauges."""returnprometheus.serialize_collected_data(my_custom_collector_instance)Related to the host, use that in thegunicorn.conf.py:defon_starting(server):fromc2cwsgiutilsimportprometheusdelserverregistry=CollectorRegistry()registry.register(MyCollector())prometheus.start(registry)Database metricsLook at thec2cwsgiutils-stats-dbutility if you want to generate statistics (gauges) about the
row counts.Usage of metricsWith c2cwsgiutils each instance (Pod) has its own metrics, so we need to aggregate them to have the metrics for the service you probably need to usesum by (<fields>) (<metric>)to get the metric (especially for counters).Custom scriptsTo have the application initialized in a script you should use thec2cwsgiutils.setup_process.bootstrap_application_from_optionsfunction.Example ofmainfunction:defmain()->None:parser=argparse.ArgumentParser(description="My scrypt.")# Add your argument herec2cwsgiutils.setup_process.fill_arguments(parser)args=parser.parse_args()env=c2cwsgiutils.setup_process.bootstrap_application_from_options(args)settings=env["registry"].settings# Add your code hereIf you need an access to the database you should add:engine=c2cwsgiutils.db.get_engine(settings)session_factory=c2cwsgiutils.db.get_session_factory(engine)withtransaction.manager:# Add your code hereIf you need the database connection without the application context, you can replace:env=c2cwsgiutils.setup_process.bootstrap_application_from_options(args)settings=env["registry"].settingsby:loader=pyramid.scripts.common.get_config_loader(args.config_uri)loader.setup_logging(parse_vars(args.config_vars)ifargs.config_varselseNone)settings=loader.get_settings()DebuggingTo enable the debugging interface, you must set theC2C_DEBUG_VIEW_ENABLEDenvironment variable. Then you can
have dumps of a few things:every threads' stacktrace:{C2C_BASE_PATH}/debug/stacks?secret={C2C_SECRET}memory usage:{C2C_BASE_PATH}/debug/memory?secret={C2C_SECRET}&limit=30&analyze_type=builtins.dict&python_internals_map=falseobject ref:{C2C_BASE_PATH}/debug/show_refs.dot?secret={C2C_SECRET}&analyze_type=gunicorn.app.wsgiapp.WSGIApplication&analyze_id=12345&max_depth=3&too_many=10&filter=1024&no_extra_info&backrefsanalyze_typeandanalyze_idshould not ve used toogether, you can use it like:curl"<URL>">/tmp/show_refs.dot
dot-Lg-Tpng/tmp/show_refs.dot>/tmp/show_refs.pngmemory increase when calling another API:{C2C_BASE_PATH}/debug/memory_diff?path={path_info}&secret={C2C_SECRET}&limit=30&no_warmupsleep the given number of seconds (to test load balancer timeouts):{C2C_BASE_PATH}/debug/sleep?secret={C2C_SECRET}&time=60.2see the HTTP headers received by WSGI:{C2C_BASE_PATH}/debug/headers?secret={C2C_SECRET}&status=500return an HTTP error:{C2C_BASE_PATH}/debug/error?secret={C2C_SECRET}&status=500To ease local development, the views are automatically reloaded when files change. In addition, the filesystem is mounted by thedocker-compose.override.yamlfile. Make sure not to use such file / mechanism in production.BroadcastSome c2cwsgiutils APIs effect or query the state of the WSGI server. Since only one process out of the 5
(by default) time the number of servers gets a query, only this one will be affected. To avoid that, you
can configure c2cwsgiutils to use Redis pub/sub to broadcast those requests and collect the answers.The impacted APIs are:{C2C_BASE_PATH}/debug/stacks{C2C_BASE_PATH}/debug/memory{C2C_BASE_PATH}/logging/level{C2C_BASE_PATH}/sql_profilerThe configuration parameters are:C2C_REDIS_URL(c2c.redis_url): The URL to the Redis single instance to useC2C_REDIS_OPTIONS: The Redis options, comma separated list of =, the value is parsed as YAMLC2C_REDIS_SENTINELS: The coma separated list of Redis host:port sentinel instances to useC2C_REDIS_SERVICENAME: The redis service name in case of using sentinelsC2C_REDIS_DB: The redis database number in case of using sentinelsC2C_BROADCAST_PREFIX(c2c.broadcast_prefix): The prefix to add to the channels being used (must be
different for 2 different services)If not configured, only the process receiving the request is impacted.CORSTo have CORS compliant views, define your views like that:fromc2cwsgiutilsimportserviceshello_service=services.create("hello","/hello",cors_credentials=True)@hello_service.get()defhello_get(request):return{'hello':True}Exception handlingc2cwsgiutils can install exception handling views that will catch any exception raised by the
application views and will transform it into a JSON response with a HTTP status corresponding to the error.You can enable this by settingC2C_ENABLE_EXCEPTION_HANDLING(c2c.enable_exception_handling) to "1".In development mode (DEVELOPMENT=1), all the details (SQL statement, stacktrace, ...) are sent to the
client. In production mode, you can still get them by sending the secret defined inC2C_SECRETin the query.If you want to use pyramid_debugtoolbar, you need to disable exception handling and configure it like that:pyramid.includes=pyramid_debugtoolbardebugtoolbar.enabled=truedebugtoolbar.hosts=0.0.0.0/0debugtoolbar.intercept_exc=debugdebugtoolbar.show_on_exc_only=truec2c.enable_exception_handling=0JSON pretty printSome JSON renderers are available:json: the normal JSON renderer (default).fast_json: a faster JSON renderer using ujson.cornice_json: the normal JSON renderer wrapped around cornice CorniceRenderer.cornice_fast_json: a faster JSON renderer wrapped around cornice CorniceRenderer.Both pretty prints the rendered JSON. While this adds significant amount of whitespace, the difference in
bytes transmitted on the network is negligible thanks to gzip compression.Thefast_jsonrenderer is using ujson which is faster, but doesn't offer the ability to change the rendering
of some types (thedefaultparameter of json.dumps). This will interact badly withpapyrusand such.The cornice versions should be used to avoid the "'JSON' object has no attribute 'render_errors'" error.Sentry integrationThe stacktraces can be sent to a sentry.io service for collection. To enable it, you must set theSENTRY_URL(c2c.sentry_url) to point the the project's public DSN.A few other environment variables can be used to tune the info sent with each report:SENTRY_EXCLUDES(c2c.sentry.excludes): list of loggers (colon separated, without spaces) to exclude for sentryGIT_HASH(c2c.git_hash): will be used for the releaseSENTRY_CLIENT_RELEASE: If not equal to "latest", will be taken for the release instead of the GIT_HASHSENTRY_CLIENT_ENVIRONMENT: the environment (dev, int, prod, ...)SENTRY_CLIENT_IGNORE_EXCEPTIONS: list (coma separated) of exceptions to ignore (defaults to SystemExit)SENTRY_TAG_...: to add other custom tagsSENTRY_LEVEL: starting from what logging level to send events to Sentry (defaults to ERROR)SENTRY_TRACES_SAMPLE_RATE: The percentage of events to send to sentry in order to compute the performance. Value between 0 and 1, default is 0.Developer infoYou will needdocker(>=1.12.0),docker-compose(>=1.10.0) andmakeinstalled on the machine to play with this project.
Check available versions ofdocker-enginewithapt-get policy docker-engineand eventually force install the
up-to-date version using a command similar toapt-get install docker-engine=1.12.3-0~xenial.To lint and test everything, run the following command:makeMake sure you are strict with the version numbers:bug fix version change: Nothing added, removed or changed in the API and only bug fix
version number changes in the dependenciesminor version change: The API must remain backward compatible and only minor version
number changes in the dependenciesmajor version change: The API and the dependencies are not backward compatibleTo make a release:Change the the version insetup.py.Commit and push to master.Tag the GIT commit.Add the new branch name in the.github/workflows/rebuild.yamland.github/workflows/audit.yamlfiles.TestingScreenshotsTo test the screenshots, you need to installnodewithnpm, to do that add the following lines in yourDockerfile:RUN--mount=type=cache,target=/var/lib/apt/lists\--mount=type=cache,target=/var/cache,sharing=locked\apt-getinstall--yes--no-install-recommendsgnupg\&&./etc/os-release\&&echo"deb https://deb.nodesource.com/node_18.x${VERSION_CODENAME}main">/etc/apt/sources.list.d/nodesource.list\&&curl--silenthttps://deb.nodesource.com/gpgkey/nodesource.gpg.key|apt-keyadd-\&&apt-getupdate\&&apt-getinstall--assume-yes--no-install-recommends'nodejs=18.*'\libx11-6libx11-xcb1libxcomposite1libxcursor1\libxdamage1libxext6libxi6libxtst6libnss3libcups2libxss1libxrandr2libasound2libatk1.0-0\libatk-bridge2.0-0libpangocairo-1.0-0libgtk-3.0libxcb-dri3-0libgbm1libxshmfence1To do the image test callcheck_screenshote.g.:fromc2cwsgiutils.acceptanceimportimagedeftest_screenshot(app_connection):image.check_screenshot(app_connection.base_url+"my-path",width=800,height=600,result_folder="results",expected_filename=os.path.join(os.path.dirname(__file__),"my-check.expected.png"),)ContributingInstall the pre-commit hooks:pipinstallpre-commit
pre-commitinstall--allow-missing-config