Coverage for gws-app/gws/base/exporter/worker.py: 81%
81 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""Export job worker."""
3from typing import Optional, cast
5import gws
6import gws.base.job
9class Object(gws.base.job.worker.Object):
10 """Export job worker.
12 Loads the requested features, runs the exporter and stores the export
13 result in the job. Progress is reported in percent, one step for loading,
14 one per feature and one for writing.
15 """
17 project: gws.Project
18 """Project of the request."""
19 request: gws.ExportRequest
20 """Export request."""
21 result: gws.ExportResult
22 """Export result, filled by the exporter."""
23 progressTotal: int = 1
24 """Total number of progress steps."""
25 progressStep: int = 0
26 """Current progress step."""
27 progressPercent: int = 0
28 """Last reported progress, in percent."""
30 @classmethod
31 def run(cls, root: gws.Root, job: gws.Job):
32 """Run an export job.
34 Called by the job manager. Reads the request from the file given in the job payload.
36 Args:
37 root: Root object.
38 job: The job.
39 """
40 request = gws.u.unserialize_from_path(job.payload.get('requestPath'))
41 w = cls(root, job.user, job, request)
42 w.work()
44 def __init__(self, root: gws.Root, user: gws.User, job: Optional[gws.Job], request: gws.ExportRequest):
45 """Create a worker.
47 Args:
48 root: Root object.
49 user: User the export runs for.
50 job: The job, or ``None`` to run without a job.
51 request: Export request.
52 """
53 super().__init__(root, user, job)
54 self.request = request
55 self.result = gws.ExportResult(
56 path='',
57 mimeType='',
58 numFiles=0,
59 numFeaturesTotal=0,
60 numFeaturesExported=0,
61 errors=[],
62 )
64 def work(self):
65 """Run the export and store the result in the job, if there is one.
67 Raises:
68 ``gws.NotFoundError``: If the project or the exporter does not exist.
69 ``gws.ForbiddenError``: If the user cannot read the project.
70 ``gws.JobTerminated``: If the job was cancelled or removed.
71 """
72 self.project = cast(gws.Project, self.user.require(self.request.projectUid, gws.ext.object.project))
74 exporter = self.root.app.exporterMgr.get_exporter(
75 [self.project, self.root.app],
76 self.request.exporterUid,
77 self.user,
78 )
79 if not exporter:
80 raise gws.NotFoundError('exporter not found')
82 # load + features + write
83 self.progressTotal = 1 + len(self.request.features or []) + 1
84 self.update_job(numSteps=100)
86 features = self.load_features()
87 self.notify('load')
89 ea = gws.ExportArgs(
90 exporter=exporter,
91 features=features,
92 shape=self.request.shape,
93 project=self.project,
94 user=self.user,
95 notify=self.notify,
96 )
98 exporter.run(ea, self.result)
99 self.update_job(state=gws.JobState.complete, result=self.result)
101 def load_features(self):
102 """Load the requested features.
104 Features of models with the ``client`` export strategy are built from
105 the request props, others are read from their models by uid. Features
106 of unknown models and features that are not found are skipped with a
107 warning.
109 Returns:
110 The features, in the order of the request.
111 """
112 if not self.request.features:
113 return []
115 self.result.numFeaturesTotal = len(self.request.features)
117 models: dict[str, gws.Model] = {}
118 uid_map: dict[str, set[str]] = {}
119 fs_map: dict[str, gws.Feature] = {}
121 mc = gws.ModelContext(op=gws.ModelOperation.export, user=self.user, project=self.project)
123 for fp in self.request.features:
124 model_uid = fp.modelUid
125 feature_uid = fp.uid
127 model = models.get(model_uid)
128 if not model:
129 model = self.root.app.modelMgr.get_model(model_uid, self.user)
130 if not model:
131 gws.log.warning(f'model not found: {model_uid}')
132 continue
133 models[model_uid] = model
135 guid = gws.u.join_uid(model_uid, feature_uid)
136 es = model.exportStrategy or gws.FeatureExportStrategy.load
137 if es == gws.FeatureExportStrategy.client:
138 fs_map[guid] = model.feature_from_props(fp, mc)
139 else:
140 uid_map.setdefault(model_uid, set()).add(feature_uid)
142 for model_uid, feature_uids in uid_map.items():
143 model = models[model_uid]
144 fs = model.get_features(feature_uids, mc)
145 for f in fs:
146 guid = gws.u.join_uid(model_uid, f.uid())
147 fs_map[guid] = f
149 fs = []
151 for fp in self.request.features:
152 guid = gws.u.join_uid(fp.modelUid, fp.uid)
153 f = fs_map.get(guid)
154 if not f:
155 gws.log.warning(f'feature not found: {guid}')
156 continue
157 fs.append(f)
159 return fs
161 def notify(self, event, details=None):
162 """Advance the progress by one step and update the job.
164 Args:
165 event: Event name, not used.
166 details: Event details, not used.
168 Raises:
169 ``gws.JobTerminated``: If the job was cancelled or removed.
170 """
171 job = self.get_job()
172 if not job:
173 return
174 self.progressStep += 1
175 p = int(self.progressStep / self.progressTotal * 100)
176 if p > self.progressPercent:
177 self.progressPercent = p
178 self.update_job(numSteps=100, step=self.progressPercent)