| | 260 | |
|---|
| | 261 | def view(self, cls, attrs, expr=None, **kwargs): |
|---|
| | 262 | """Yield tuples of attrs for the given cls which match the expr. |
|---|
| | 263 | |
|---|
| | 264 | cls: The Unit subclass for which to yield property tuples. |
|---|
| | 265 | attrs: a sequence of strings; each should be the name of |
|---|
| | 266 | a UnitProperty on the given cls. |
|---|
| | 267 | expr: a lambda or logic.Expression. If provided, data will only |
|---|
| | 268 | be yielded for units of the given cls which match the expr. |
|---|
| | 269 | **kwargs: additional expr filters in name=value format. |
|---|
| | 270 | |
|---|
| | 271 | Each yielded value will be a list of values, in the same order as |
|---|
| | 272 | the attrs arg. This facilitates unpacking in iterative consumer |
|---|
| | 273 | code like: |
|---|
| | 274 | |
|---|
| | 275 | for id, name in sandbox.view(Invoice, ['ID', 'Name'], f): |
|---|
| | 276 | print id, ": ", name |
|---|
| | 277 | |
|---|
| | 278 | This is generally much faster than recall, and should be preferred |
|---|
| | 279 | for performance-sensitive code. |
|---|
| | 280 | """ |
|---|
| | 281 | if expr and not isinstance(expr, logic.Expression): |
|---|
| | 282 | expr = logic.Expression(expr) |
|---|
| | 283 | if kwargs: |
|---|
| | 284 | f = logic.filter(**kwargs) |
|---|
| | 285 | if expr: |
|---|
| | 286 | expr += f |
|---|
| | 287 | else: |
|---|
| | 288 | expr = f |
|---|
| | 289 | |
|---|
| | 290 | if self.logflags & logflags.VIEW: |
|---|
| | 291 | self.log("VIEW %s [%s]: %s" % (cls.__name__, attrs, expr)) |
|---|
| | 292 | |
|---|
| | 293 | for row in self.storage(cls).view(cls, attrs, expr): |
|---|
| | 294 | yield row |
|---|
| | 295 | |
|---|
| | 296 | def sum(self, cls, attr, expr=None, **kwargs): |
|---|
| | 297 | """Sum of all non-None values for the given cls.attr.""" |
|---|
| | 298 | expr = logic.Expression(lambda x: getattr(x, attr) != None) + expr |
|---|
| | 299 | return sum([row[0] for row in self.view(cls, (attr,), expr, **kwargs)]) |
|---|
| | 300 | |
|---|
| | 301 | def distinct(self, cls, attrs, expr=None, **kwargs): |
|---|
| | 302 | """List of distinct UnitProperty tuples for the given cls. |
|---|
| | 303 | |
|---|
| | 304 | If only one attribute is specified, a list of values will be returned. |
|---|
| | 305 | If more than one attribute is specified, a zipped list will be returned. |
|---|
| | 306 | |
|---|
| | 307 | Notice that you can also use this function as a count() function |
|---|
| | 308 | (in fact it's the only way to do it) by using attrs = ['ID']. |
|---|
| | 309 | """ |
|---|
| | 310 | if expr and not isinstance(expr, logic.Expression): |
|---|
| | 311 | expr = logic.Expression(expr) |
|---|
| | 312 | if kwargs: |
|---|
| | 313 | f = logic.filter(**kwargs) |
|---|
| | 314 | if expr: |
|---|
| | 315 | expr += f |
|---|
| | 316 | else: |
|---|
| | 317 | expr = f |
|---|
| | 318 | |
|---|
| | 319 | if self.logflags & logflags.VIEW: |
|---|
| | 320 | self.log("DISTINCT %s [%s]: %s" % (cls.__name__, attrs, expr)) |
|---|
| | 321 | |
|---|
| | 322 | seen = {} |
|---|
| | 323 | for row in self.storage(cls).distinct(cls, attrs, expr): |
|---|
| | 324 | if row not in seen: |
|---|
| | 325 | seen[row] = None |
|---|
| | 326 | |
|---|
| | 327 | seen = seen.keys() |
|---|
| | 328 | seen.sort() |
|---|
| | 329 | if len(attrs) == 1: |
|---|
| | 330 | seen = [x[0] for x in seen] |
|---|
| | 331 | return seen |
|---|
| | 332 | |
|---|
| | 333 | def count(self, cls, expr): |
|---|
| | 334 | """Number of Units of the given cls which match the given expr.""" |
|---|
| | 335 | return len(self.distinct(cls, cls.identifiers, expr)) |
|---|
| | 336 | |
|---|
| | 337 | def range(self, cls, attr, expr=None, **kwargs): |
|---|
| | 338 | """Distinct, non-None attr values (ordered and continuous, if possible). |
|---|
| | 339 | |
|---|
| | 340 | If the given attribute is a known discrete, ordered type |
|---|
| | 341 | (like int, long, datetime.date), this returns the closed interval: |
|---|
| | 342 | |
|---|
| | 343 | [min(attr), ..., max(attr)] |
|---|
| | 344 | |
|---|
| | 345 | That is, all possible values will be output between min and max, |
|---|
| | 346 | even if they do not appear in the dataset. |
|---|
| | 347 | |
|---|
| | 348 | If the given attribute is not reasonably discrete (e.g., str, |
|---|
| | 349 | unicode, or float) then all distinct, non-None values are returned |
|---|
| | 350 | (sorted, if possible). |
|---|
| | 351 | """ |
|---|
| | 352 | existing = [x for x in self.distinct(cls, [attr], expr, **kwargs) |
|---|
| | 353 | if x is not None] |
|---|
| | 354 | if not existing: |
|---|
| | 355 | return [] |
|---|
| | 356 | |
|---|
| | 357 | attr_type = getattr(cls, attr).type |
|---|
| | 358 | if issubclass(attr_type, (int, long)): |
|---|
| | 359 | return range(min(existing), max(existing) + 1) |
|---|
| | 360 | else: |
|---|
| | 361 | try: |
|---|
| | 362 | import datetime |
|---|
| | 363 | except ImportError: |
|---|
| | 364 | pass |
|---|
| | 365 | else: |
|---|
| | 366 | if issubclass(attr_type, datetime.date): |
|---|
| | 367 | def date_gen(): |
|---|
| | 368 | start, end = min(existing), max(existing) |
|---|
| | 369 | for d in range((end + 1) - start): |
|---|
| | 370 | yield start + datetime.timedelta(d) |
|---|
| | 371 | return date_gen() |
|---|
| | 372 | |
|---|
| | 373 | try: |
|---|
| | 374 | existing.sort() |
|---|
| | 375 | except TypeError: |
|---|
| | 376 | pass |
|---|
| | 377 | |
|---|
| | 378 | return existing |
|---|