Fix pyright type errors across codebase

- Add pyrightconfig.json for pyright 1.1.408 TOML parsing workaround
- Add NoReturn annotations to fatal() functions for proper type narrowing
- Add None checks and assertions after require=True get_record() calls
- Fix AttrDict class with __getattr__ for dynamic attribute access
- Add type annotations and casts for Kubernetes client objects
- Store compose config as DockerDeployer instance attributes
- Filter None values from dotenv and environment mappings
- Use hasattr/getattr patterns for optional container attributes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
A. F. Dudley
2026-01-22 01:10:36 -05:00
co-authored by Claude Opus 4.5
parent cd3d908d0d
commit dd856af2d3
29 changed files with 512 additions and 267 deletions
@@ -73,6 +73,7 @@ def process_app_deployment_request(
app = laconic.get_record(
app_deployment_request.attributes.application, require=True
)
assert app is not None # require=True ensures this
logger.log(f"Retrieved app record {app_deployment_request.attributes.application}")
# 2. determine dns
@@ -483,6 +484,8 @@ def command( # noqa: C901
laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file
)
webapp_deployer_record = laconic.get_record(lrn, require=True)
assert webapp_deployer_record is not None # require=True ensures this
assert webapp_deployer_record.attributes is not None
payment_address = webapp_deployer_record.attributes.paymentAddress
main_logger.log(f"Payment address: {payment_address}")
@@ -495,6 +498,7 @@ def command( # noqa: C901
sys.exit(2)
# Find deployment requests.
requests = []
# single request
if request_id:
main_logger.log(f"Retrieving request {request_id}...")
@@ -518,25 +522,35 @@ def command( # noqa: C901
previous_requests = load_known_requests(state_file)
# Collapse related requests.
requests.sort(key=lambda r: r.createTime)
requests.reverse()
# Filter out None values and sort
valid_requests = [r for r in requests if r is not None]
valid_requests.sort(key=lambda r: r.createTime if r else "")
valid_requests.reverse()
requests_by_name = {}
skipped_by_name = {}
for r in requests:
main_logger.log(f"BEGIN: Examining request {r.id}")
for r in valid_requests:
if not r:
continue
r_id = r.id if r else "unknown"
main_logger.log(f"BEGIN: Examining request {r_id}")
result = "PENDING"
try:
if (
r.id in previous_requests
and previous_requests[r.id].get("status", "") != "RETRY"
r_id in previous_requests
and previous_requests[r_id].get("status", "") != "RETRY"
):
main_logger.log(f"Skipping request {r.id}, we've already seen it.")
main_logger.log(f"Skipping request {r_id}, we've already seen it.")
result = "SKIP"
continue
if not r.attributes:
main_logger.log(f"Skipping request {r_id}, no attributes.")
result = "ERROR"
continue
app = laconic.get_record(r.attributes.application)
if not app:
main_logger.log(f"Skipping request {r.id}, cannot locate app.")
main_logger.log(f"Skipping request {r_id}, cannot locate app.")
result = "ERROR"
continue
@@ -544,7 +558,7 @@ def command( # noqa: C901
if not requested_name:
requested_name = generate_hostname_for_app(app)
main_logger.log(
"Generating name %s for request %s." % (requested_name, r.id)
"Generating name %s for request %s." % (requested_name, r_id)
)
if (
@@ -552,31 +566,33 @@ def command( # noqa: C901
or requested_name in requests_by_name
):
main_logger.log(
"Ignoring request %s, it has been superseded." % r.id
"Ignoring request %s, it has been superseded." % r_id
)
result = "SKIP"
continue
if skip_by_tag(r, include_tags, exclude_tags):
r_tags = r.attributes.tags if r.attributes else None
main_logger.log(
"Skipping request %s, filtered by tag "
"(include %s, exclude %s, present %s)"
% (r.id, include_tags, exclude_tags, r.attributes.tags)
% (r_id, include_tags, exclude_tags, r_tags)
)
skipped_by_name[requested_name] = r
result = "SKIP"
continue
r_app = r.attributes.application if r.attributes else "unknown"
main_logger.log(
"Found pending request %s to run application %s on %s."
% (r.id, r.attributes.application, requested_name)
% (r_id, r_app, requested_name)
)
requests_by_name[requested_name] = r
except Exception as e:
result = "ERROR"
main_logger.log(f"ERROR examining request {r.id}: " + str(e))
main_logger.log(f"ERROR examining request {r_id}: " + str(e))
finally:
main_logger.log(f"DONE Examining request {r.id} with result {result}.")
main_logger.log(f"DONE Examining request {r_id} with result {result}.")
if result in ["ERROR"]:
dump_known_requests(state_file, [r], status=result)
@@ -673,6 +689,7 @@ def command( # noqa: C901
status = "ERROR"
run_log_file = None
run_reg_client = laconic
build_logger = None
try:
run_id = (
f"{r.id}-{str(time.time()).split('.')[0]}-"
@@ -718,7 +735,8 @@ def command( # noqa: C901
status = "DEPLOYED"
except Exception as e:
main_logger.log(f"ERROR {r.id}:" + str(e))
build_logger.log("ERROR: " + str(e))
if build_logger:
build_logger.log("ERROR: " + str(e))
finally:
main_logger.log(f"DEPLOYING {r.id}: END - {status}")
if build_logger:
@@ -64,7 +64,11 @@ def command( # noqa: C901
):
laconic = LaconicRegistryClient(laconic_config)
if not payment_address:
payment_address = laconic.whoami().address
whoami_result = laconic.whoami()
if whoami_result and whoami_result.address:
payment_address = whoami_result.address
else:
raise ValueError("Could not determine payment address from laconic whoami")
pub_key = base64.b64encode(open(public_key_file, "rb").read()).decode("ASCII")
hostname = urlparse(api_url).hostname
@@ -16,6 +16,7 @@ import shutil
import sys
import tempfile
from datetime import datetime
from typing import NoReturn
import base64
import gnupg
@@ -31,7 +32,7 @@ from stack_orchestrator.deploy.webapp.util import (
from dotenv import dotenv_values
def fatal(msg: str):
def fatal(msg: str) -> NoReturn:
print(msg, file=sys.stderr)
sys.exit(1)
@@ -134,24 +135,30 @@ def command( # noqa: C901
fatal(f"Unable to locate auction: {auction_id}")
# Check auction owner
if auction.ownerAddress != laconic.whoami().address:
whoami = laconic.whoami()
if not whoami or not whoami.address:
fatal("Unable to determine current account address")
if auction.ownerAddress != whoami.address:
fatal(f"Auction {auction_id} owner mismatch")
# Check auction kind
if auction.kind != AUCTION_KIND_PROVIDER:
auction_kind = auction.kind if auction else None
if auction_kind != AUCTION_KIND_PROVIDER:
fatal(
f"Auction kind needs to be ${AUCTION_KIND_PROVIDER}, got {auction.kind}"
f"Auction kind needs to be ${AUCTION_KIND_PROVIDER}, got {auction_kind}"
)
# Check auction status
if auction.status != AuctionStatus.COMPLETED:
fatal(f"Auction {auction_id} not completed yet, status {auction.status}")
auction_status = auction.status if auction else None
if auction_status != AuctionStatus.COMPLETED:
fatal(f"Auction {auction_id} not completed yet, status {auction_status}")
# Check that winner list is not empty
if len(auction.winnerAddresses) == 0:
winner_addresses = auction.winnerAddresses if auction else []
if not winner_addresses or len(winner_addresses) == 0:
fatal(f"Auction {auction_id} has no winners")
auction_winners = auction.winnerAddresses
auction_winners = winner_addresses
# Get deployer record for all the auction winners
for auction_winner in auction_winners:
@@ -198,9 +205,12 @@ def command( # noqa: C901
recip = gpg.list_keys()[0]["uids"][0]
# Wrap the config
whoami_result = laconic.whoami()
if not whoami_result or not whoami_result.address:
fatal("Unable to determine current account address")
config = {
# Include account (and payment?) details
"authorized": [laconic.whoami().address],
"authorized": [whoami_result.address],
"config": {"env": dict(dotenv_values(env_file))},
}
serialized = yaml.dump(config)
@@ -227,12 +237,22 @@ def command( # noqa: C901
if (not deployer) and len(deployer_record.names):
target_deployer = deployer_record.names[0]
app_name = (
app_record.attributes.name
if app_record and app_record.attributes
else "unknown"
)
app_version = (
app_record.attributes.version
if app_record and app_record.attributes
else "unknown"
)
deployment_request = {
"record": {
"type": "ApplicationDeploymentRequest",
"application": app,
"version": "1.0.0",
"name": f"{app_record.attributes.name}@{app_record.attributes.version}",
"name": f"{app_name}@{app_version}",
"deployer": target_deployer,
"meta": {"when": str(datetime.utcnow())},
}
@@ -20,9 +20,9 @@ import yaml
from stack_orchestrator.deploy.webapp.util import LaconicRegistryClient
def fatal(msg: str):
def fatal(msg: str) -> None:
print(msg, file=sys.stderr)
sys.exit(1)
sys.exit(1) # noqa: This function never returns
@click.command()
@@ -85,18 +85,17 @@ def command(
if dry_run:
undeployment_request["record"]["payment"] = "DRY_RUN"
elif "auto" == make_payment:
if "minimumPayment" in deployer_record.attributes:
amount = int(
deployer_record.attributes.minimumPayment.replace("alnt", "")
)
attrs = deployer_record.attributes if deployer_record else None
if attrs and "minimumPayment" in attrs:
amount = int(attrs.minimumPayment.replace("alnt", ""))
else:
amount = make_payment
if amount:
receipt = laconic.send_tokens(
deployer_record.attributes.paymentAddress, amount
)
undeployment_request["record"]["payment"] = receipt.tx.hash
print("Payment TX:", receipt.tx.hash)
attrs = deployer_record.attributes if deployer_record else None
if attrs and attrs.paymentAddress:
receipt = laconic.send_tokens(attrs.paymentAddress, amount)
undeployment_request["record"]["payment"] = receipt.tx.hash
print("Payment TX:", receipt.tx.hash)
elif use_payment:
undeployment_request["record"]["payment"] = use_payment
+21 -4
View File
@@ -39,9 +39,12 @@ WEBAPP_PORT = 80
def command(ctx, image, env_file, port):
"""run the specified webapp container"""
env = {}
env: dict[str, str] = {}
if env_file:
env = dotenv_values(env_file)
# Filter out None values from dotenv
for k, v in dotenv_values(env_file).items():
if v is not None:
env[k] = v
unique_cluster_descriptor = f"{image},{env}"
hash = hashlib.md5(unique_cluster_descriptor.encode()).hexdigest()
@@ -55,6 +58,11 @@ def command(ctx, image, env_file, port):
compose_env_file=None,
)
if not deployer:
print("Failed to create deployer", file=click.get_text_stream("stderr"))
ctx.exit(1)
return # Unreachable, but helps type checker
ports = []
if port:
ports = [(port, WEBAPP_PORT)]
@@ -72,10 +80,19 @@ def command(ctx, image, env_file, port):
# Make configurable?
webappPort = f"{WEBAPP_PORT}/tcp"
# TODO: This assumes a Docker container object...
if webappPort in container.network_settings.ports:
# Check if container has network_settings (Docker container object)
if (
container
and hasattr(container, "network_settings")
and container.network_settings
and hasattr(container.network_settings, "ports")
and container.network_settings.ports
and webappPort in container.network_settings.ports
):
mapping = container.network_settings.ports[webappPort][0]
container_id = getattr(container, "id", "unknown")
print(
f"Image: {image}\n"
f"ID: {container.id}\n"
f"ID: {container_id}\n"
f"URL: http://localhost:{mapping['HostPort']}"
)
@@ -43,7 +43,13 @@ def process_app_removal_request(
deployment_record = laconic.get_record(
app_removal_request.attributes.deployment, require=True
)
assert deployment_record is not None # require=True ensures this
assert deployment_record.attributes is not None
dns_record = laconic.get_record(deployment_record.attributes.dns, require=True)
assert dns_record is not None # require=True ensures this
assert dns_record.attributes is not None
deployment_dir = os.path.join(
deployment_parent_dir, dns_record.attributes.name.lower()
)
@@ -57,17 +63,20 @@ def process_app_removal_request(
# Or of the original deployment request.
if not matched_owner and deployment_record.attributes.request:
matched_owner = match_owner(
app_removal_request,
laconic.get_record(deployment_record.attributes.request, require=True),
original_request = laconic.get_record(
deployment_record.attributes.request, require=True
)
assert original_request is not None # require=True ensures this
matched_owner = match_owner(app_removal_request, original_request)
if matched_owner:
main_logger.log("Matched deployment ownership:", matched_owner)
main_logger.log(f"Matched deployment ownership: {matched_owner}")
else:
deployment_id = deployment_record.id if deployment_record else "unknown"
request_id = app_removal_request.id if app_removal_request else "unknown"
raise Exception(
"Unable to confirm ownership of deployment %s for removal request %s"
% (deployment_record.id, app_removal_request.id)
% (deployment_id, request_id)
)
# TODO(telackey): Call the function directly. The easiest way to build
@@ -80,13 +89,18 @@ def process_app_removal_request(
result = subprocess.run(down_command)
result.check_returncode()
deployer_name = (
webapp_deployer_record.names[0]
if webapp_deployer_record and webapp_deployer_record.names
else ""
)
removal_record = {
"record": {
"type": "ApplicationDeploymentRemovalRecord",
"version": "1.0.0",
"request": app_removal_request.id,
"deployment": deployment_record.id,
"deployer": webapp_deployer_record.names[0],
"request": app_removal_request.id if app_removal_request else "",
"deployment": deployment_record.id if deployment_record else "",
"deployer": deployer_name,
}
}
@@ -96,11 +110,11 @@ def process_app_removal_request(
laconic.publish(removal_record)
if delete_names:
if deployment_record.names:
if deployment_record and deployment_record.names:
for name in deployment_record.names:
laconic.delete_name(name)
if dns_record.names:
if dns_record and dns_record.names:
for name in dns_record.names:
laconic.delete_name(name)
@@ -224,6 +238,8 @@ def command( # noqa: C901
laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file
)
deployer_record = laconic.get_record(lrn, require=True)
assert deployer_record is not None # require=True ensures this
assert deployer_record.attributes is not None
payment_address = deployer_record.attributes.paymentAddress
main_logger.log(f"Payment address: {payment_address}")
@@ -236,6 +252,7 @@ def command( # noqa: C901
sys.exit(2)
# Find deployment removal requests.
requests = []
# single request
if request_id:
main_logger.log(f"Retrieving request {request_id}...")
@@ -259,32 +276,39 @@ def command( # noqa: C901
main_logger.log(f"Loading known requests from {state_file}...")
previous_requests = load_known_requests(state_file)
requests.sort(key=lambda r: r.createTime)
requests.reverse()
# Filter out None values and sort by createTime
valid_requests = [r for r in requests if r is not None]
valid_requests.sort(key=lambda r: r.createTime if r else "")
valid_requests.reverse()
# Find deployments.
named_deployments = {}
main_logger.log("Discovering app deployments...")
for d in laconic.app_deployments(all=False):
named_deployments[d.id] = d
if d and d.id:
named_deployments[d.id] = d
# Find removal requests.
removals_by_deployment = {}
removals_by_request = {}
main_logger.log("Discovering deployment removals...")
for r in laconic.app_deployment_removals():
if r.attributes.deployment:
if r and r.attributes and r.attributes.deployment:
# TODO: should we handle CRNs?
removals_by_deployment[r.attributes.deployment] = r
one_per_deployment = {}
for r in requests:
for r in valid_requests:
if not r or not r.attributes:
continue
if not r.attributes.deployment:
r_id = r.id if r else "unknown"
main_logger.log(
f"Skipping removal request {r.id} since it was a cancellation."
f"Skipping removal request {r_id} since it was a cancellation."
)
elif r.attributes.deployment in one_per_deployment:
main_logger.log(f"Skipping removal request {r.id} since it was superseded.")
r_id = r.id if r else "unknown"
main_logger.log(f"Skipping removal request {r_id} since it was superseded.")
else:
one_per_deployment[r.attributes.deployment] = r
+81 -42
View File
@@ -25,6 +25,7 @@ import uuid
import yaml
from enum import Enum
from typing import Any, List, Optional, TextIO
from stack_orchestrator.deploy.webapp.registry_mutex import registry_mutex
@@ -41,27 +42,35 @@ AUCTION_KIND_PROVIDER = "provider"
class AttrDict(dict):
def __init__(self, *args, **kwargs):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
def __getattribute__(self, attr):
def __getattribute__(self, attr: str) -> Any:
__dict__ = super(AttrDict, self).__getattribute__("__dict__")
if attr in __dict__:
v = super(AttrDict, self).__getattribute__(attr)
if isinstance(v, dict):
return AttrDict(v)
return v
return super(AttrDict, self).__getattribute__(attr)
def __getattr__(self, attr: str) -> Any:
# This method is called when attribute is not found
# Return None for missing attributes (matches original behavior)
return None
class TimedLogger:
def __init__(self, id="", file=None):
def __init__(self, id: str = "", file: Optional[TextIO] = None) -> None:
self.start = datetime.datetime.now()
self.last = self.start
self.id = id
self.file = file
def log(self, msg, show_step_time=True, show_total_time=False):
def log(
self, msg: str, show_step_time: bool = True, show_total_time: bool = False
) -> None:
prefix = f"{datetime.datetime.utcnow()} - {self.id}"
if show_step_time:
prefix += f" - {datetime.datetime.now() - self.last} (step)"
@@ -79,7 +88,7 @@ def load_known_requests(filename):
return {}
def logged_cmd(log_file, *vargs):
def logged_cmd(log_file: Optional[TextIO], *vargs: str) -> str:
result = None
try:
if log_file:
@@ -88,17 +97,22 @@ def logged_cmd(log_file, *vargs):
result.check_returncode()
return result.stdout.decode()
except Exception as err:
if result:
print(result.stderr.decode(), file=log_file)
else:
print(str(err), file=log_file)
if log_file:
if result:
print(result.stderr.decode(), file=log_file)
else:
print(str(err), file=log_file)
raise err
def match_owner(recordA, *records):
def match_owner(
recordA: Optional[AttrDict], *records: Optional[AttrDict]
) -> Optional[str]:
if not recordA or not recordA.owners:
return None
for owner in recordA.owners:
for otherRecord in records:
if owner in otherRecord.owners:
if otherRecord and otherRecord.owners and owner in otherRecord.owners:
return owner
return None
@@ -226,25 +240,27 @@ class LaconicRegistryClient:
]
# Most recent records first
results.sort(key=lambda r: r.createTime)
results.sort(key=lambda r: r.createTime or "")
results.reverse()
self._add_to_cache(results)
return results
def _add_to_cache(self, records):
def _add_to_cache(self, records: List[AttrDict]) -> None:
if not records:
return
for p in records:
self.cache["name_or_id"][p.id] = p
if p.id:
self.cache["name_or_id"][p.id] = p
if p.names:
for lrn in p.names:
self.cache["name_or_id"][lrn] = p
if p.attributes and p.attributes.type:
if p.attributes.type not in self.cache:
self.cache[p.attributes.type] = []
self.cache[p.attributes.type].append(p)
attr_type = p.attributes.type
if attr_type not in self.cache:
self.cache[attr_type] = []
self.cache[attr_type].append(p)
def resolve(self, name):
if not name:
@@ -556,26 +572,36 @@ def determine_base_container(clone_dir, app_type="webapp"):
return base_container
def build_container_image(app_record, tag, extra_build_args=None, logger=None):
def build_container_image(
app_record: Optional[AttrDict],
tag: str,
extra_build_args: Optional[List[str]] = None,
logger: Optional[TimedLogger] = None,
) -> None:
if app_record is None:
raise ValueError("app_record cannot be None")
if extra_build_args is None:
extra_build_args = []
tmpdir = tempfile.mkdtemp()
# TODO: determine if this code could be calling into the Python git
# library like setup-repositories
log_file = logger.file if logger else None
try:
record_id = app_record["id"]
ref = app_record.attributes.repository_ref
repo = random.choice(app_record.attributes.repository)
clone_dir = os.path.join(tmpdir, record_id)
logger.log(f"Cloning repository {repo} to {clone_dir} ...")
if logger:
logger.log(f"Cloning repository {repo} to {clone_dir} ...")
# Set github credentials if present running a command like:
# git config --global url."https://${TOKEN}:@github.com/".insteadOf
# "https://github.com/"
github_token = os.environ.get("DEPLOYER_GITHUB_TOKEN")
if github_token:
logger.log("Github token detected, setting it in the git environment")
if logger:
logger.log("Github token detected, setting it in the git environment")
git_config_args = [
"git",
"config",
@@ -583,9 +609,7 @@ def build_container_image(app_record, tag, extra_build_args=None, logger=None):
f"url.https://{github_token}:@github.com/.insteadOf",
"https://github.com/",
]
result = subprocess.run(
git_config_args, stdout=logger.file, stderr=logger.file
)
result = subprocess.run(git_config_args, stdout=log_file, stderr=log_file)
result.check_returncode()
if ref:
# TODO: Determing branch or hash, and use depth 1 if we can.
@@ -596,30 +620,32 @@ def build_container_image(app_record, tag, extra_build_args=None, logger=None):
subprocess.check_call(
["git", "clone", repo, clone_dir],
env=git_env,
stdout=logger.file,
stderr=logger.file,
stdout=log_file,
stderr=log_file,
)
except Exception as e:
logger.log(f"git clone failed. Is the repository {repo} private?")
if logger:
logger.log(f"git clone failed. Is the repository {repo} private?")
raise e
try:
subprocess.check_call(
["git", "checkout", ref],
cwd=clone_dir,
env=git_env,
stdout=logger.file,
stderr=logger.file,
stdout=log_file,
stderr=log_file,
)
except Exception as e:
logger.log(f"git checkout failed. Does ref {ref} exist?")
if logger:
logger.log(f"git checkout failed. Does ref {ref} exist?")
raise e
else:
# TODO: why is this code different vs the branch above (run vs check_call,
# and no prompt disable)?
result = subprocess.run(
["git", "clone", "--depth", "1", repo, clone_dir],
stdout=logger.file,
stderr=logger.file,
stdout=log_file,
stderr=log_file,
)
result.check_returncode()
@@ -627,7 +653,8 @@ def build_container_image(app_record, tag, extra_build_args=None, logger=None):
clone_dir, app_record.attributes.app_type
)
logger.log("Building webapp ...")
if logger:
logger.log("Building webapp ...")
build_command = [
sys.argv[0],
"--verbose",
@@ -643,10 +670,10 @@ def build_container_image(app_record, tag, extra_build_args=None, logger=None):
build_command.append("--extra-build-args")
build_command.append(" ".join(extra_build_args))
result = subprocess.run(build_command, stdout=logger.file, stderr=logger.file)
result = subprocess.run(build_command, stdout=log_file, stderr=log_file)
result.check_returncode()
finally:
logged_cmd(logger.file, "rm", "-rf", tmpdir)
logged_cmd(log_file, "rm", "-rf", tmpdir)
def push_container_image(deployment_dir, logger):
@@ -809,8 +836,12 @@ def skip_by_tag(r, include_tags, exclude_tags):
def confirm_payment(
laconic: LaconicRegistryClient, record, payment_address, min_amount, logger
):
laconic: LaconicRegistryClient,
record: AttrDict,
payment_address: str,
min_amount: int,
logger: TimedLogger,
) -> bool:
req_owner = laconic.get_owner(record)
if req_owner == payment_address:
# No need to confirm payment if the sender and recipient are the same account.
@@ -846,7 +877,8 @@ def confirm_payment(
)
return False
pay_denom = "".join([i for i in tx.amount if not i.isdigit()])
tx_amount = tx.amount or ""
pay_denom = "".join([i for i in tx_amount if not i.isdigit()])
if pay_denom != "alnt":
logger.log(
f"{record.id}: {pay_denom} in tx {tx.hash} is not an expected "
@@ -854,7 +886,7 @@ def confirm_payment(
)
return False
pay_amount = int("".join([i for i in tx.amount if i.isdigit()]))
pay_amount = int("".join([i for i in tx_amount if i.isdigit()]) or "0")
if pay_amount < min_amount:
logger.log(
f"{record.id}: payment amount {tx.amount} is less than minimum {min_amount}"
@@ -870,7 +902,8 @@ def confirm_payment(
used_request = laconic.get_record(used[0].attributes.request, require=True)
# Check that payment was used for deployment of same application
if record.attributes.application != used_request.attributes.application:
used_app = used_request.attributes.application if used_request else None
if record.attributes.application != used_app:
logger.log(
f"{record.id}: payment {tx.hash} already used on a different "
f"application deployment {used}"
@@ -890,8 +923,12 @@ def confirm_payment(
def confirm_auction(
laconic: LaconicRegistryClient, record, deployer_lrn, payment_address, logger
):
laconic: LaconicRegistryClient,
record: AttrDict,
deployer_lrn: str,
payment_address: str,
logger: TimedLogger,
) -> bool:
auction_id = record.attributes.auction
auction = laconic.get_auction(auction_id)
@@ -906,7 +943,9 @@ def confirm_auction(
auction_app = laconic.get_record(
auction_records_by_id[0].attributes.application, require=True
)
if requested_app.id != auction_app.id:
requested_app_id = requested_app.id if requested_app else None
auction_app_id = auction_app.id if auction_app else None
if requested_app_id != auction_app_id:
logger.log(
f"{record.id}: requested application {record.attributes.application} "
f"does not match application from auction record "