Repository navigation
Expand file tree
/
Copy pathcopier.yml
More file actions
527 lines (447 loc) · 23.2 KB
/
Copy pathcopier.yml
File metadata and controls
527 lines (447 loc) · 23.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# ============== WARNING ==============================================================================
# File is managed by copier template: gh:LabAutomationAndScreening/copier-base-template.git
# See .config/.copier-managed-files.json for details.
#
# You are welcome to make changes to this file in your repo if they are custom to your project,
# but if the change should be shared with other projects, please backport it to the template repo.
# =====================================================================================================
# Questions specific to this template
has_backend:
type: bool
help: Does this project have a backend?
default: yes
is_circuit_python_driver:
type: bool
help: Is this backend a CircuitPython device driver?
default: no
when: "{{ has_backend }}"
has_circuit_python_backend_template_been_instantiated:
type: bool
help: Has the CircuitPython backend template been instantiated in this repository yet?
default: no
when: "{{ is_circuit_python_driver }}"
# Questions managed by upstream template
repo_name:
type: str
help: What is the name of the repository?
repo_org_name:
type: str
help: What's the organization or username that owns this repository (look in the URL)?
description:
type: str
help: What is the description of this repository?
install_claude_cli:
type: bool
help: Should the Claude CLI be installed in the devcontainer?
default: no
is_open_source:
type: bool
help: Is this library open source?
default: no
ssh_port_number:
type: int
help: What port should the devcontainer bind SSH to?
# Pick a random port, but ensure it's not in the excluded port range on Windows (powershell: `netsh int ipv4 show excludedportrange protocol=tcp`)
default: "{{ ( (range(49152, 49752) | list) + (range(49852, 50000) | list) + (range(50060, 50160) | list) + (range(50160, 50260) | list) + (range(50260, 50360) | list) + (range(50914, 51014) | list) + (range(51114, 51214) | list) + (range(51214, 51314) | list) + (range(51314, 51414) | list) + (range(51623, 51723) | list) + (range(51723, 51823) | list) + (range(65269, 65369) | list) + (range(65369, 65469) | list) ) | random }}"
pull_from_ecr:
type: bool
help: Will you need to pull images from a central AWS Elastic Container Registry?
default: no
use_windows_in_ci:
type: bool
help: Should CI in the instantiated template also use Windows runners?
default: no
node_version:
type: str
help: What version of NodeJS is used for development?
default: "{{ default_node_version }}"
install_aws_ssm_port_forwarding_plugin:
type: bool
help: Should the AWS SSM Port Forwarding Plugin be installed?
default: no
configure_vcrpy:
type: bool
help: Should VCRpy be configured for use during unit testing in Python?
default: "{{ is_circuit_python_driver }}"
when: "{{ not is_circuit_python_driver }}"
configure_python_asyncio:
type: bool
help: Will Python code be using asyncio?
default: "{{ is_circuit_python_driver }}"
when: "{{ not is_circuit_python_driver }}"
python_version:
type: str
help: What version of Python is used for development?
default: "3.12.7"
python_package_registry:
type: str
help: What registry should Python Packgaes be installed from?
choices:
- PyPI
- AWS CodeArtifact
default: PyPI
aws_identity_center_id:
type: str
help: What's the ID of your Organization's AWS Identity center, e.g. d-9145c20053?
when: "{{ python_package_registry == 'AWS CodeArtifact' or install_aws_ssm_port_forwarding_plugin or pull_from_ecr }}"
aws_org_home_region:
type: str
help: What is the home region of the AWS Organization (where all of the central infrastructure is deployed)?
default: us-east-1
when: "{{ python_package_registry == 'AWS CodeArtifact' or install_aws_ssm_port_forwarding_plugin or pull_from_ecr }}"
aws_central_infrastructure_account_id:
type: str
help: What's the ID of your Organization's AWS Account containing Central Infrastructure (e.g. CodeArtifact)?
when: "{{ python_package_registry == 'AWS CodeArtifact' or pull_from_ecr }}"
default: "000000000000"
core_infra_base_access_profile_name:
type: str
help: What's the AWS Identity Center Profile name for base access to the Central Infrastructure account (i.e. to read from CodeArtifact)?
when: "{{ python_package_registry == 'AWS CodeArtifact' or pull_from_ecr }}"
default: CoreInfraBaseAccess
# Questions specific to this template
human_friendly_app_name:
type: str
help: What's the human-friendly name of this application? (e.g. for documentation)
default: "{{ repo_name.replace('-', ' ').replace('_', ' ') }}"
backend_rest_api_description:
type: str
help: What's the description of the backend REST API? (e.g. for documentation)
when: "{{ has_backend }}"
backend_makes_api_calls:
type: bool
help: Will the backend make calls to an external API?
default: "{{ is_circuit_python_driver }}"
when: "{{ has_backend and not is_circuit_python_driver }}"
backend_source_uses_kiota:
type: bool
help: Will the backend make calls to an external API using Kiota-generated client code?
default: "{{ is_circuit_python_driver }}"
when: "{{ has_backend and backend_makes_api_calls and not is_circuit_python_driver }}"
backend_deployed_port_number:
type: int
help: What port should the backend be deployed to?
default: 4000
when: "{{ has_backend }}"
backend_uses_graphql:
type: bool
help: Is the backend GraphQL?
default: "{{ not is_circuit_python_driver }}"
when: "{{ has_backend and not is_circuit_python_driver }}"
deploy_as_executable:
type: bool
help: Should this application be deployed as a single standalone executable?
default: no
when: "{{ has_backend and not is_circuit_python_driver }}"
use_upx_for_executable:
type: bool
help: Should UPX be used to compress the executable? On Windows this can make the compiled program smaller, but increases the risk of being flagged by antivirus software.
default: yes
when: "{{ deploy_as_executable }}"
install_as_windows_service:
type: bool
help: Should this application be installable as a Windows service?
default: "{{ deploy_as_executable and use_windows_in_ci }}"
when: "{{ deploy_as_executable and use_windows_in_ci }}"
windows_service_org_prefix:
type: str
help: What prefix should the Windows service name use? The service registers as <prefix><app-name> (e.g. "lab-sync-" gives "lab-sync-my-app"). Leave blank for no prefix.
default: "{{ repo_org_name }}-"
when: "{{ install_as_windows_service }}"
validator: |-
{% if '/' in windows_service_org_prefix or '\\' in windows_service_org_prefix %}The service-name prefix must not contain / or \ (they are illegal in a Windows service name).{% elif "'" in windows_service_org_prefix %}The service-name prefix must not contain a single quote (it breaks the generated PowerShell $ServiceName literal).{% elif (windows_service_org_prefix ~ repo_name) | length > 256 %}The resulting service name "{{ windows_service_org_prefix }}{{ repo_name }}" exceeds the 256-character Windows service-name limit.{% endif %}
extra_windows_service_rights:
type: yaml
help: Extra LSA account privileges to grant the service account, on top of the always-granted SeServiceLogonRight. Provide a YAML list, e.g. ["SeCreateSymbolicLinkPrivilege"]. Leave empty for none.
default: []
when: "{{ install_as_windows_service }}"
windows_service_bind_policy:
type: str
help: >-
Which network address should the service listen on, and may the operator change it at install time?
loopback (127.0.0.1) is safe but only reachable from the same PC; all-interfaces (0.0.0.0) is reachable
from other computers on the network. "configurable_*" adds an installer page (and an ALLOW_REMOTE silent
property) letting the operator choose, defaulting to the named side; "*_only" forces the address with no choice.
choices:
- loopback_only
- configurable_loopback_default
- configurable_all_default
- all_interfaces_only
default: configurable_loopback_default
when: "{{ install_as_windows_service }}"
installer_manufacturer:
type: str
help: "What is the human-friendly manufacturer/company name to show in the MSI installer and Add/Remove Programs (e.g. Acme Corp)?"
when: "{{ install_as_windows_service }}"
validator: |-
{% if not installer_manufacturer %}A manufacturer name is required.{% elif installer_manufacturer | regex_search('["\\\\<>&]') %}The manufacturer name must not contain " \ < > or & (they break the MSI XML attribute, the generated Python f-string, or the license RTF).{% endif %}
installer_branding_dir:
type: str
help: "Directory (relative to the repo root) holding the installer icon favicon.ico. Frontend apps can use frontend/public; apps whose frontend has no favicon should point this at a dir they own (e.g. installer)."
default: "frontend/public"
when: "{{ install_as_windows_service }}"
installer_upgrade_code:
type: str
help: "A fixed-for-life UpgradeCode GUID for this app's MSI (generate once, NEVER change it - it identifies upgrades across versions). Format: 8-4-4-4-12 hex, e.g. 3b9d1f6a-2c84-4e7b-9a1f-6d5c4b3a2e10."
when: "{{ install_as_windows_service }}"
validator: "{% if not (installer_upgrade_code | regex_search('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')) %}Must be a GUID like 3b9d1f6a-2c84-4e7b-9a1f-6d5c4b3a2e10{% endif %}"
sign_installer:
type: bool
help: Should the MSI be code-signed via Azure Trusted Signing during CI?
default: no
when: "{{ install_as_windows_service }}"
signing_account:
type: str
help: Azure Trusted Signing account name.
when: "{{ sign_installer }}"
signing_profile:
type: str
help: Azure Trusted Signing certificate profile name.
when: "{{ sign_installer }}"
signing_endpoint:
type: str
help: Azure Trusted Signing endpoint URL (e.g. https://eus.codesigning.azure.net/).
when: "{{ sign_installer }}"
install_gcc_in_backend_container_build_phase:
type: bool
help: Is GCC required to build any Python dependencies in the backend container?
default: "{{ is_circuit_python_driver }}"
when: "{{ not deploy_as_executable and has_backend and not is_circuit_python_driver }}"
create_docker_image_tar_artifact:
type: bool
help: During CI, should the docker images be bundled up into a tar file for manual deployment?
default: no
when: "{{ not deploy_as_executable }}"
push_to_ecr:
type: bool
help: During CI, should the docker images be pushed to an ECR?
default: no
when: "{{ not deploy_as_executable }}"
frontend_ecr_base_url:
type: str
help: What is the base URL of the ECR repository to push the frontend image to? (e.g. 123456789012.dkr.ecr.us-east-1.amazonaws.com)
when: "{{ push_to_ecr }}"
default: "{{ aws_central_infrastructure_account_id }}.dkr.ecr.{{ aws_org_home_region }}.amazonaws.com"
frontend_ecr_push_role_name:
type: str
help: What is the name of the role to assume for pushing the frontend image to ECR?
when: "{{ push_to_ecr }}"
default: "GHA-ECR-Push-{{ repo_name }}-frontend"
frontend_image_name:
type: str
help: What is the name of the frontend image?
default: "{{ repo_name }}/frontend"
when: "{{ not deploy_as_executable }}"
backend_ecr_base_url:
type: str
help: What is the base URL of the ECR repository to push the backend image to? (e.g. 123456789012.dkr.ecr.us-east-1.amazonaws.com)
when: "{{ push_to_ecr and has_backend }}"
default: "{{ frontend_ecr_base_url }}"
backend_ecr_push_role_name:
type: str
help: What is the name of the role to assume for pushing the backend image to ECR?
when: "{{ push_to_ecr and has_backend }}"
default: "GHA-ECR-Push-{{ repo_name }}-backend"
backend_image_name:
type: str
help: What is the name of the backend image?
default: "{{ repo_name }}/backend"
when: "{{ has_backend and not deploy_as_executable }}"
auth_to_ecr_for_e2e_in_ci:
type: bool
help: Will the E2E job in CI need to authenticate into an ECR to pull images?
default: false
auth_to_ecr_for_e2e_in_ci_region:
type: str
help: What AWS region should the E2E job in CI authenticate to ECR in?
default: "{{ aws_org_home_region }}"
when: "{{ auth_to_ecr_for_e2e_in_ci }}"
auth_to_ecr_for_e2e_in_ci_role_arn:
type: str
help: What is the ARN of the role to assume for authenticating to ECR in the E2E job in CI?
default: "arn:aws:iam::{{ aws_central_infrastructure_account_id }}:role/{{ core_infra_base_access_profile_name }}"
when: "{{ auth_to_ecr_for_e2e_in_ci }}"
run_backend_e2e_in_ci:
type: bool
help: Should a separate job in CI be created to run E2E tests just against the backend (Note - this does not actually work for executables yet, still TODO)?
default: "{{ has_circuit_python_backend_template_been_instantiated }}"
when: "{{ has_backend and not is_circuit_python_driver }}"
auth_to_ecr_for_backend_e2e_in_ci:
type: bool
help: Will the Backend E2E job in CI need to authenticate into an ECR to pull images?
default: "{{ auth_to_ecr_for_e2e_in_ci and run_backend_e2e_in_ci }}"
when: "{{ run_backend_e2e_in_ci }}"
auth_to_ecr_for_backend_e2e_in_ci_region:
type: str
help: What AWS region should the Backend E2E job in CI authenticate to ECR in?
default: "{{ aws_org_home_region }}"
when: "{{ auth_to_ecr_for_backend_e2e_in_ci }}"
auth_to_ecr_for_backend_e2e_in_ci_role_arn:
type: str
help: What is the ARN of the role to assume for authenticating to ECR in the Backend E2E job in CI?
default: "arn:aws:iam::{{ aws_central_infrastructure_account_id }}:role/{{ core_infra_base_access_profile_name }}"
when: "{{ auth_to_ecr_for_backend_e2e_in_ci }}"
frontend_uses_graphql:
type: bool
help: Do you want the frontend to make GraphQL calls?
default: |-
{% if has_backend == false -%}
no
{%- else -%}
{#- This way if backend is already in use, then we'll automatically include graphql for this variable -#}
{{ backend_uses_graphql }}
{%- endif %}
frontend_deployed_port_number:
type: int
help: What port should the frontend be deployed to?
default: 3000
when: "{{ not deploy_as_executable }}"
frontend_uses_zod:
type: bool
help: Will the frontend use the zod library for form validation?
default: "{{ is_circuit_python_driver }}"
when: "{{ not is_circuit_python_driver }}"
frontend_nodeport_number:
type: int
help: What nodeport should the frontend be deployed to in the Helm Chart?
when: "{{ not deploy_as_executable and is_circuit_python_driver }}"
validator: "{% if frontend_nodeport_number < 30000 or frontend_nodeport_number > 32767 %}NodePort must be in range 30000-32767{% endif %}"
backend_nodeport_number:
type: int
help: What nodeport should the backend be deployed to in the Helm Chart?
when: "{{ not deploy_as_executable and has_backend and is_circuit_python_driver }}"
validator: "{% if backend_nodeport_number < 30000 or backend_nodeport_number > 32767 %}NodePort must be in range 30000-32767{% endif %}"
# Questions managed by upstream template
repo_names_for_codespaces_read_permissions:
type: str
help: Provide a comma-separated list of repository names only within this GitHub organization that you would like the codespace to have read access to (for example, private-template, not my-org/private-template)
default: |-
{% if is_circuit_python_driver -%}
copier-circuit-python-device-driver-backend,copier-circuit-python-device-driver-frontend
{%- else -%}
{%- endif %}
pnpm_minimum_release_age_exclude:
type: str
help: "Comma-delimited list of package name patterns to exclude from pnpm's minimumReleaseAge constraint (leave empty for none)"
default: ""
_skip_if_exists:
- backend/src/static/favicon.ico # TODO: figure out if there's another way to stop the template from overriding a changed icon
- backend/tests/unit/__snapshots__/test_basic_server_functionality/test_openapi_schema.json # snapshot file of the OpenAPI schema that will be overwritten by the repo as the schema evolves
- backend/tests/e2e/generated/open_api/** # when the repo regenerates the client code, we don't want Copier to overwrite it. the template is just dropping in a basic spec to support a healthcheck E2E test
_migrations:
# PNPM12-MIGRATION -- this whole block is transient: it carries repos across the pnpm 11 -> 12 boundary and
# nothing else. Delete both entries (and with them `_migrations` entirely, if nothing else has been added)
# once every downstream repo has taken an update that ran them. Grep PNPM12-MIGRATION for the rest.
# Both of these run in the `before` stage, while the repo's own pnpm-workspace.yaml is still on disk and
# before anything in the update reaches for pnpm. pnpm 12 cannot load a manifest whose
# minimumReleaseAgeExclude is a scalar -- it fails every pnpm command, `pnpm --version` included -- so the
# value has to be a list before the pnpm 12 install below, and `pnpm config` cannot do the rewrite itself.
- command: python3 '{{ _copier_conf.src_path }}/src/copier_tasks/normalize_pnpm_minimum_release_age_exclude.py'
when: "{{ _stage == 'before' }}"
# The devcontainer's pnpmVersion only takes effect on a rebuild, so an update applied from an existing
# container still has the old pnpm on PATH while the tasks that read pnpm 12 settings run. The version is
# hardcoded rather than templated because this migration exists to cross the pnpm 11 -> 12 boundary, and
# the check keeps it from downgrading a container that is already on 12 or newer.
- command: |
installed_major="$(pnpm --version 2>/dev/null | cut -d. -f1)"
if [ -z "$installed_major" ] || [ "$installed_major" -lt 12 ]; then
echo "Installing pnpm 12.4.2 (found '${installed_major:-none}') so pnpm 12 workspace settings can be read before a devcontainer rebuild..."
npm install -g pnpm@12.4.2
fi
when: "{{ _stage == 'before' }}"
_tasks:
- command: |
python3 '{{ _copier_conf.src_path }}/src/copier_tasks/remove_precommit_hooks.py' \
--hook-id-regex '^\s*-\s+id:\s+merge-claude-settings\s*$' \
--target-file .pre-commit-config.yaml
when: "{{ not install_claude_cli }}"
# Copier does not preserve executable permissions when instantiating the template, so we set them explicitly here.
# git update-index is also needed because with core.fileMode=false (common on WSL2) git ignores the on-disk exec bit,
# so a plain `git add` would not capture it. It is guarded on the file being tracked, since on an initial `copier copy`
# into a fresh repo the file is not in the index yet (the chmod alone covers that case).
- command: |
if [ -f scripts/delete_false_positive_rej.py ]; then
chmod +x scripts/delete_false_positive_rej.py
if git ls-files --error-unmatch scripts/delete_false_positive_rej.py > /dev/null 2>&1; then
git update-index --chmod=+x scripts/delete_false_positive_rej.py
fi
fi
- command: |
for script in .claude/skills/address-pr-comments/check-footer.py .claude/skills/address-pr-comments/commit-link.py .claude/skills/address-pr-comments/fetch-pr-comments.py .claude/skills/address-pr-comments/post-reply.py .claude/skills/address-pr-comments/post-review-comment.py .claude/skills/address-pr-comments/verify-env.py; do
if [ -f "$script" ]; then
chmod +x "$script"
if git ls-files --error-unmatch "$script" > /dev/null 2>&1; then
git update-index --chmod=+x "$script"
fi
fi
done
- command: |
for script in .claude/skills/fix-mutants/check-results.py .claude/skills/fix-mutants/group-by-line.py .claude/skills/fix-mutants/list-survived.py .claude/skills/fix-mutants/run-mutmut.py .claude/skills/fix-mutants/show-mutant.py .claude/skills/fix-mutants/verify-mutant.py; do
if [ -f "$script" ]; then
chmod +x "$script"
if git ls-files --error-unmatch "$script" > /dev/null 2>&1; then
git update-index --chmod=+x "$script"
fi
fi
done
- command: |
export PNPM_PATTERNS='{{ pnpm_minimum_release_age_exclude }}'
python3 '{{ _copier_conf.src_path }}/src/copier_tasks/ensure_pnpm_minimum_release_age_exclude.py' --patterns "$PNPM_PATTERNS"
when: "{{ pnpm_minimum_release_age_exclude }}"
- command: |
if [ -f .config/ruff.toml ]; then
echo "Updating ruff target-version from python_version..."
py_major_minor="$(printf '%s' '{{ python_version }}' | cut -d. -f1,2)"
py_tag="py$(printf '%s' "$py_major_minor" | tr -d '.')"
sed -i -E 's/^target-version = "py[0-9]+"/target-version = "'"$py_tag"'"/' .config/ruff.toml
else
echo ".config/ruff.toml not found; skipping Ruff target-version update."
fi
- command: |
echo "Checking pyrefly.toml search-path for circuit python firmware..."
if [ -f .config/pyrefly.toml ]; then
# circuit python cannot do relative imports, so firmware uses absolute imports rooted at firmware/src; tell pyrefly about them
if ! grep -q 'firmware/src' .config/pyrefly.toml; then
sed -i 's#^search-path = \["../backend/src", "../backend"\]#search-path = ["../backend/src", "../backend", "../backend/src/backend_api/firmware/src"]#' .config/pyrefly.toml
fi
else
echo ".config/pyrefly.toml not found"
fi
when: "{{ is_circuit_python_driver }}"
- command: |
echo "Checking .config/pyrefly.toml..."
if [ -f .config/pyrefly.toml ]; then
if ! grep -q 'firmware/src' .config/pyrefly.toml; then
sed -i 's#search-path = \["../backend/src", "../backend"\]#search-path = ["../backend/src", "../backend", "../backend/src/backend_api/firmware/src"]#' .config/pyrefly.toml
fi
else
echo ".config/pyrefly.toml not found"
fi
when: "{{ is_circuit_python_driver }}"
- command: |
echo "Checking for .coveragerc in repo root config..."
if [ -f .config/.coveragerc ]; then
echo "Moving .coveragerc to backend folder..."
mkdir -p backend
mv .config/.coveragerc backend/.coveragerc
echo ".coveragerc moved successfully"
else
echo ".coveragerc not found in repo root config"
fi
when: "{{ has_backend }}"
- command: |
python3 '{{ _copier_conf.src_path }}/src/copier_tasks/copier_provenance.py' \
'{{ _copier_conf.src_path }}/template' \
'.' \
--template-src '{{ _src_path }}'
# Additional Settings
_min_copier_version: "9.8"
_answers_file: ".config/.copier-answers.yml"
_subdirectory: template
_templates_suffix: .jinja
_exclude:
- "copier.yml"
# adapted from https://github.com/copier-org/copier-template-extensions#context-hook-extension
_jinja_extensions:
- copier_template_extensions.TemplateExtensionLoader
- extensions/context.py:ContextUpdater