Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix long lines due to strings and comments #698

Merged
merged 8 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ reformat:
ruff check --select F401,I --fix
# reformat
ruff format
# make an extended check with rules that might be triggered by reformat
ruff check --config ruff-extended.toml

lint:
ruff check
Expand Down
6 changes: 6 additions & 0 deletions ruff-extended.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
extend = "./ruff.toml"

[lint]
# Some rules are marked as incompatible with the formatter so use them in an extended check instead.
# See: https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
extend-select = ["E501", "ISC"]
1 change: 0 additions & 1 deletion ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@ target-version = "py310"
[lint]
select = ["E", "F", "W", "I", "ASYNC", "UP", "FLY", "PERF", "FURB"]

# For now, ignore E501 (line too long) errors. They are in strings and comments.
ignore = ["E501"]
10 changes: 5 additions & 5 deletions src/eduid/common/config/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,12 @@ class FlaskConfig(CORSMixin):
# per-file basis using the get_send_file_max_age() hook on Flask or Blueprint,
# respectively. Defaults to 43200 (12 hours).
send_file_max_age_default: int = 43200 # 12 hours
# the name and port number of the server. Required for subdomain support (e.g.: 'myapp.dev:5000') Note that localhost
# does not support subdomains so setting this to “localhost” does not help. Setting a SERVER_NAME also by default
# enables URL generation without a request context but with an application context.
# the name and port number of the server. Required for subdomain support (e.g.: 'myapp.dev:5000') Note that
# localhost does not support subdomains so setting this to “localhost” does not help. Setting a SERVER_NAME also by
# default enables URL generation without a request context but with an application context.
server_name: str | None = None
# If the application does not occupy a whole domain or subdomain this can be set to the path where the application is
# configured to live. This is for session cookie as path value. If domains are used, this should be None.
# If the application does not occupy a whole domain or subdomain this can be set to the path where the application
# is configured to live. This is for session cookie as path value. If domains are used, this should be None.
application_root: str = "/"
# The URL scheme that should be used for URL generation if no URL scheme is
# available. This defaults to http
Expand Down
4 changes: 2 additions & 2 deletions src/eduid/graphdb/tests/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def test_create_db(self):
with self.neo4jdb.driver.session() as session:
session.run("CREATE (n:Test $props)", props={"name": "test node", "testing": True})
with self.neo4jdb.driver.session() as session:
result = session.run("MATCH (n {name: $name})" "RETURN n.testing", name="test node")
result = session.run("MATCH (n {name: $name})RETURN n.testing", name="test node")
self.assertTrue(result.single().value())


Expand All @@ -33,5 +33,5 @@ def test_base_db(self):
with test_db._db.driver.session() as session:
session.run("CREATE (n:Test $props)", props={"name": "test node", "testing": True})
with test_db._db.driver.session() as session:
result = session.run("MATCH (n {name: $name})" "RETURN n.testing", name="test node")
result = session.run("MATCH (n {name: $name})RETURN n.testing", name="test node")
self.assertTrue(result.single().value())
3 changes: 2 additions & 1 deletion src/eduid/satosa/scimapi/accr.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ def process(self, context: satosa.context.Context, data: satosa.internal.Interna
supported_accr = self.supported_accr_sorted_by_prio
required_accr_by_virtual_idp = supported_accr[: supported_accr.index(minimum_accr) + 1]
logger.info(
f"Replacing requested ACCR: {requested_accr}, with what {virtual_idp} requires: {required_accr_by_virtual_idp}."
f"Replacing requested ACCR: {requested_accr}, "
f"with what {virtual_idp} requires: {required_accr_by_virtual_idp}."
)
accr_to_forward = required_accr_by_virtual_idp

Expand Down
15 changes: 11 additions & 4 deletions src/eduid/satosa/scimapi/stepup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@
from satosa.context import Context
from satosa.exception import SATOSAAuthenticationError, SATOSAError
from satosa.internal import InternalData
from satosa.micro_services.base import ( # TODO: Enable when https://github.com/IdentityPython/SATOSA/pull/435 has been accepted; CallbackCallSignature,; CallbackReturnType,; ProcessReturnType,
from satosa.micro_services.base import (
# TODO: Enable when https://github.com/IdentityPython/SATOSA/pull/435 has been accepted;
# CallbackCallSignature,
# CallbackReturnType,
# ProcessReturnType,
RequestMicroService,
ResponseMicroService,
)
Expand Down Expand Up @@ -94,10 +98,13 @@ class StepupPluginConfig(BaseModel):

class StepupParams(BaseModel):
issuer: str
issuer_loa: str | None = None # LoA that the IdP released - as requested through the acr_mapping configuration
# LoA that the IdP released - as requested through the acr_mapping configuration
issuer_loa: str | None = None
requester: EntityId
requester_loas: list[str] # (original) LoAs required by the requester
loa_settings: LoaSettings # LoA settings to use. Either from the configuration or derived using entity attributes in the metadata.
# (original) LoAs required by the requester
requester_loas: list[str]
# LoA settings to use. Either from the configuration or derived using entity attributes in the metadata.
loa_settings: LoaSettings


# Applied to response from IDP
Expand Down
2 changes: 1 addition & 1 deletion src/eduid/scimapi/routers/invites.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ async def on_put(
# req.app.context.logger.info(f"Profile {this}/{update_request.nutid_user_v1.profiles[this]} updated")
# profiles_changed = True
# else:
# req.app.context.logger.info(f"Profile {this}/{update_request.nutid_user_v1.profiles[this]} not changed")
# req.app.context.logger.info(f"Profile {this}/{update_request.nutid_user_v1.profiles[this]} not changed") # noqa: E501
# for this in db_invite.profiles.keys():
# if this not in update_request.nutid_user_v1.profiles:
# req.app.context.logger.info(f"Profile {this}/{db_invite.profiles[this]} removed")
Expand Down
3 changes: 2 additions & 1 deletion src/eduid/scimapi/tests/test_scimgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ def _perform_search(
self.assertEqual(
expected_group.display_name,
resources[0].get("displayName"),
f"Search parsed_response group does not have the expected displayName: {str(expected_group.display_name)}",
"Search parsed_response group does not have the expected displayName: "
f"{str(expected_group.display_name)}",
)

return resources
Expand Down
2 changes: 1 addition & 1 deletion src/eduid/userdb/tests/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_uri_with_replicaset(self):
self.assertEqual(mdb.sanitized_uri, "mongodb://john:[email protected]/testdb?replicaset=rs9")
self.assertEqual(
mdb._db_uri,
"mongodb://john:[email protected],db2.example.com,db3.example.com:1234" "/testdb?replicaset=rs9",
"mongodb://john:[email protected],db2.example.com,db3.example.com:1234/testdb?replicaset=rs9",
)

def test_uri_with_options(self):
Expand Down
4 changes: 3 additions & 1 deletion src/eduid/userdb/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def dst(self, dt):
return datetime.timedelta(0)


# NOTE: This function is copied from eduid.webapp.common.misc.timeutil because eduid-userdb can't import eduid.webapp.common
# NOTE: This function is copied from eduid.webapp.common.misc.timeutil
# because eduid-userdb can't import eduid.webapp.common
# TODO: check this as it is in eduid.common.misc.timeutil
def utc_now() -> datetime.datetime:
"""Return current time with tz=UTC"""
return datetime.datetime.now(tz=datetime.timezone.utc)
Expand Down
2 changes: 1 addition & 1 deletion src/eduid/vccs/server/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def save(self, credential: Credential) -> bool:
logger.debug(f"Updated credential {credential} in the db (to revision {credential.revision}): {result}")
return True
logger.warning(
f"Could not update credential {credential} (to revision {credential.revision}): " f"{result.raw_result}"
f"Could not update credential {credential} (to revision {credential.revision}): {result.raw_result}"
)
credential.revision -= 1
return False
Expand Down
2 changes: 1 addition & 1 deletion src/eduid/vccs/server/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ async def startup_event():
if _name == "uvicorn.access":
_logger.propagate = False
app.logger.info(
f"Updated logger {_name} handlers {_old_handlers} -> {_logger.handlers} " f"(prop: {_logger.propagate})"
f"Updated logger {_name} handlers {_old_handlers} -> {_logger.handlers} (prop: {_logger.propagate})"
)


Expand Down
2 changes: 1 addition & 1 deletion src/eduid/webapp/authn/tests/test_authn.py
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ def test_no_cookie(self):
self.assertTrue(resp.location.startswith(self.app.conf.authn_service_url))

def test_cookie(self):
sessid = "fb1f42420b0109020203325d750185673df252de388932a3957f522a6c43a" "a47"
sessid = "fb1f42420b0109020203325d750185673df252de388932a3957f522a6c43aa47"
self.redis_instance.conn.set(sessid, json.dumps({"v1": {"id": "0"}}))

with self.session_cookie(self.browser, self.test_user.eppn) as c:
Expand Down
8 changes: 4 additions & 4 deletions src/eduid/webapp/bankid/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def setUp(self, *args: Any, **kwargs: Any) -> None:
{extra_attributes}
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>"""
</samlp:Response>""" # noqa: E501
self.saml_response_tpl_success = """<?xml version="1.0"?>
<samlp:Response xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Destination="{sp_url}saml2-acs" ID="id-88b9f586a2a3a639f9327485cc37c40a" InResponseTo="{session_id}" IssueInstant="{timestamp}" Version="2.0">
<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer>
Expand Down Expand Up @@ -138,7 +138,7 @@ def setUp(self, *args: Any, **kwargs: Any) -> None:
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>
</samlp:Response>"""
</samlp:Response>""" # noqa: E501
self.saml_response_tpl_fail = """<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="{sp_url}saml2-acs" ID="_ebad01e547857fa54927b020dba1edb1" InResponseTo="{session_id}" IssueInstant="{timestamp}" Version="2.0">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml2:Issuer>
Expand All @@ -148,7 +148,7 @@ def setUp(self, *args: Any, **kwargs: Any) -> None:
</saml2p:StatusCode>
<saml2p:StatusMessage>User login was not successful or could not meet the requirements of the requesting application.</saml2p:StatusMessage>
</saml2p:Status>
</saml2p:Response>"""
</saml2p:Response>""" # noqa: E501
self.saml_response_tpl_cancel = """
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="{sp_url}saml2-acs" ID="_ebad01e547857fa54927b020dba1edb1" InResponseTo="{session_id}" IssueInstant="{timestamp}" Version="2.0">
Expand All @@ -159,7 +159,7 @@ def setUp(self, *args: Any, **kwargs: Any) -> None:
</saml2p:StatusCode>
<saml2p:StatusMessage>The login attempt was cancelled</saml2p:StatusMessage>
</saml2p:Status>
</saml2p:Response>"""
</saml2p:Response>""" # noqa: E501

super().setUp(users=["hubba-bubba", "hubba-baar"])

Expand Down
5 changes: 4 additions & 1 deletion src/eduid/webapp/common/api/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ def set_user_names_from_official_address(
"""
user.given_name = proofing_log_entry.user_postal_address.name.given_name
user.surname = proofing_log_entry.user_postal_address.name.surname
user.legal_name = f"{proofing_log_entry.user_postal_address.name.given_name} {proofing_log_entry.user_postal_address.name.surname}"
user.legal_name = (
f"{proofing_log_entry.user_postal_address.name.given_name} "
f"{proofing_log_entry.user_postal_address.name.surname}"
)

# please mypy
if user.given_name is None or user.surname is None:
Expand Down
7 changes: 4 additions & 3 deletions src/eduid/webapp/common/api/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,9 +510,10 @@ def _assure_not_in_dict(d: Mapping[str, Any], unwanted_key: str):
assert (
k in _json["payload"]
), f"The Flux response payload {_json['payload']} does not contain {repr(k)}"
assert (
v == _json["payload"][k]
), f"The Flux response payload item {repr(k)} should be {repr(v)} but is {repr(_json['payload'][k])}"
assert v == _json["payload"][k], (
f"The Flux response payload item {repr(k)} should be {repr(v)} "
f"but is {repr(_json['payload'][k])}"
)
if assure_not_in_payload is not None:
for key in assure_not_in_payload:
_assure_not_in_dict(_json["payload"], key)
Expand Down
2 changes: 1 addition & 1 deletion src/eduid/webapp/common/api/views/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def cached_json_response(key: str, data: dict[str, Any] | None = None) -> Respon
if SIMPLE_CACHE.get(key) is not None:
if now < SIMPLE_CACHE[key].expire_time:
if get_from_current_app("debug", bool):
logger.debug(f"Returned cached response for {key}" f" {now} < {SIMPLE_CACHE[key].expire_time}")
logger.debug(f"Returned cached response for {key} {now} < {SIMPLE_CACHE[key].expire_time}")
response = jsonify(SIMPLE_CACHE[key].data)
response.headers.add("Expires", SIMPLE_CACHE[key].expire_time.strftime("%a, %d %b %Y %H:%M:%S UTC"))
response.headers.add("Cache-Control", f"public,max-age={cache_for_seconds}")
Expand Down
2 changes: 1 addition & 1 deletion src/eduid/webapp/common/authn/tests/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def auth_response(session_id: str, eppn: str, accr: EduidAuthnContextClass | Non
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>"""
</samlp:Response>""" # noqa: E501

return saml_response_tpl.format(
**{
Expand Down
10 changes: 8 additions & 2 deletions src/eduid/webapp/common/authn/tests/test_fido_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,14 @@ def __init__(self, config: MockFidoConfig):
SAMPLE_WEBAUTHN_REQUEST = {
"credentialId": "i3KjBT0t5TPm693T9O0f4zyiwvdu9cY8BegCjiVvq_FS-ZmPcvXipFvHvD5CH6ZVRR3nsVsOla0Cad3fbtUA_Q",
"authenticatorData": "3PcEcSYqagziJNECYxSBKMR01J4pmySHIPPDM-42YdMBAAAGNw",
# {"type":"webauthn.get","challenge":"saoY-78kzDgV6mX5R2ixraC699jEU1cJTu7I9twUfJQ","origin":"https://idp.eduid.docker","crossOrigin":false}
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoic2FvWS03OGt6RGdWNm1YNVIyaXhyYUM2OTlqRVUxY0pUdTdJOXR3VWZKUSIsIm9yaWdpbiI6Imh0dHBzOi8vaWRwLmVkdWlkLmRvY2tlciIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
# {
# "type":"webauthn.get",
# "challenge":"saoY-78kzDgV6mX5R2ixraC699jEU1cJTu7I9twUfJQ",
# "origin":"https://idp.eduid.docker",
# "crossOrigin":false
# }
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoic2FvWS03OGt6RGdWNm1YNVIyaXhyYUM2OTlqRVUxY0pU"
"dTdJOXR3VWZKUSIsIm9yaWdpbiI6Imh0dHBzOi8vaWRwLmVkdWlkLmRvY2tlciIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
# This is a fake signature, we mock its verification below
"signature": "MEUCICVPIQ5fO6gXtu3nXD9ff5ILcmWc54m6AxvK9vcS8IjkAiEAoFAKblpl29UHK6AhnOf6r7hezTZeQdK5lB4J3F-cguY",
}
Expand Down
3 changes: 2 additions & 1 deletion src/eduid/webapp/common/proofing/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ def get_proofing_method(
framework=TrustFramework.EIDAS,
idp=config.foreign_identity_idp,
method=method,
required_loa=config.foreign_required_loa, # TODO: True Required LOA is likely higher here when verifying credentials
# TODO: True Required LOA is likely higher here when verifying credentials
required_loa=config.foreign_required_loa,
)
if method == "svipe_id":
return ProofingMethodSvipe(
Expand Down
8 changes: 4 additions & 4 deletions src/eduid/webapp/eidas/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def setUp(self, *args: Any, **kwargs: Any) -> None:
{extra_attributes}
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>"""
</samlp:Response>""" # noqa: E501
self.saml_response_tpl_fail = """<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="{sp_url}saml2-acs" ID="_ebad01e547857fa54927b020dba1edb1" InResponseTo="{session_id}" IssueInstant="{timestamp}" Version="2.0">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml2:Issuer>
Expand All @@ -116,7 +116,7 @@ def setUp(self, *args: Any, **kwargs: Any) -> None:
</saml2p:StatusCode>
<saml2p:StatusMessage>User login was not successful or could not meet the requirements of the requesting application.</saml2p:StatusMessage>
</saml2p:Status>
</saml2p:Response>"""
</saml2p:Response>""" # noqa: E501
self.saml_response_tpl_cancel = """
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="{sp_url}saml2-acs" ID="_ebad01e547857fa54927b020dba1edb1" InResponseTo="{session_id}" IssueInstant="{timestamp}" Version="2.0">
Expand All @@ -127,7 +127,7 @@ def setUp(self, *args: Any, **kwargs: Any) -> None:
</saml2p:StatusCode>
<saml2p:StatusMessage>The login attempt was cancelled</saml2p:StatusMessage>
</saml2p:Status>
</saml2p:Response>"""
</saml2p:Response>""" # noqa: E501
self.saml_response_foreign_eid_tpl_success = """
<?xml version='1.0' encoding='UTF-8'?>
<samlp:Response xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Destination="{sp_url}saml2-acs" ID="id-88b9f586a2a3a639f9327485cc37c40a" InResponseTo="{session_id}" IssueInstant="{timestamp}" Version="2.0">
Expand Down Expand Up @@ -182,7 +182,7 @@ def setUp(self, *args: Any, **kwargs: Any) -> None:
{extra_attributes}
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>"""
</samlp:Response>""" # noqa: E501

super().setUp(users=["hubba-bubba", "hubba-baar"])

Expand Down
2 changes: 1 addition & 1 deletion src/eduid/webapp/email/verifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def send_verification_code(email: str, user: User) -> bool:
# Debug-log the code and message in development environment
current_app.logger.debug(f"code: {state.verification.verification_code}")
current_app.logger.debug(f"Generating verification e-mail with context:\n{payload}")
current_app.logger.info(f"Sent email address verification mail to user {user}" f" about address {email!s}.")
current_app.logger.info(f"Sent email address verification mail to user {user} about address {email!s}.")
return True


Expand Down
8 changes: 4 additions & 4 deletions src/eduid/webapp/email/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def verify(user: User, code: str, email: str) -> FluxData:
@require_user
def post_remove(user, email):
proofing_user = ProofingUser.from_user(user, current_app.private_userdb)
current_app.logger.debug(f"Trying to remove email address {email!r} " f"from user {proofing_user}")
current_app.logger.debug(f"Trying to remove email address {email!r} from user {proofing_user}")

# Do not let the user remove all mail addresses
if proofing_user.mail_addresses.count == 1:
Expand Down Expand Up @@ -184,17 +184,17 @@ def post_remove(user, email):
@MarshalWith(EmailResponseSchema)
@require_user
def resend_code(user: User, email: str) -> FluxData:
current_app.logger.debug("Trying to send new verification code for email " f"address {email} for user {user}")
current_app.logger.debug(f"Trying to send new verification code for email address {email} for user {user}")

if not user.mail_addresses.find(email):
current_app.logger.debug(f"Unknown email {email!r} in resend_code_action," f" user {user}")
current_app.logger.debug(f"Unknown email {email!r} in resend_code_action, user {user}")
return error_response(message=CommonMsg.out_of_sync)

sent = send_verification_code(email, user)
if not sent:
return error_response(message=EmailMsg.still_valid_code)

current_app.logger.debug("New verification code sent to " f"address {email} for user {user}")
current_app.logger.debug(f"New verification code sent to address {email} for user {user}")
current_app.stats.count(name="email_resend_code", value=1)

emails = {"emails": user.mail_addresses.to_list_of_dicts()}
Expand Down
Loading
Loading