8

I have two expressions. I need to try one expression, if it is raise an exception try another, but if the second raises an exception too - to raise the exception.

I tried this, but it is looks ugly and I am not sure it is the best way to solve this issue:

try:                                                           
    image = self.images.order_by(func.random()).limit(1)       
except:                                                        
    try:                                                       
        image = self.images.order_by(func.rand()).limit(1)     
    except ProgrammingError:                                   
        raise ProgrammingError(                                
            'The database engine must be PostgtreSQL or MySQL')

How do you do it?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
I159
  • 29,741
  • 31
  • 97
  • 132
  • 2
    Why are you calling the same code twice with different try..except clauses? What are you actually trying to accomplish? This code looks like nonsense. –  Aug 13 '12 at 14:28
  • 1
    What particular exception are you trying to raise in the first part? Anything? – RocketDonkey Aug 13 '12 at 14:29
  • 4
    @Maulwurfn: there is a subtle difference between the two, based on the difference between function support in two different databases. `func.random()` vs. `func.rand()`. – Martijn Pieters Aug 13 '12 at 14:30
  • 1
    This is not the same. At first time I call `func.random()` which is specific for Postgres and at the second time I call `func.rand()` which is specific for MySQL. – I159 Aug 13 '12 at 14:30
  • 1
    The fact that you are using SQLAlchemy is *instrumental* to understanding your context. I've added the tag for you. – Martijn Pieters Aug 13 '12 at 14:33
  • Nobody can guess that two different databases are in the game...SO is not about *guessing* –  Aug 13 '12 at 14:35

5 Answers5

7

Use a loop:

for methname in ("random", "rand"):
    try:
        image = self.images.order_by(getattr(func, methname)()).limit(1)
        break
    except ProgrammingError:
        continue
else:
    raise ProgrammingError("The database engine must be PostgtreSQL or MySQL")

The loop's else clause is executed only if the loop terminates normally (i.e., without a break) which is why we break after doing the image assignment. If you consider this too tricksy, because the else clause is so infrequently used with for, then this would also work:

image = None
for methname in ("random", "rand"):
    try:
        image = self.images.order_by(getattr(func, methname)()).limit(1)
    except ProgrammingError:
        continue
if not image:
    raise ProgrammingError("The database engine must be PostgtreSQL or MySQL")
kindall
  • 178,883
  • 35
  • 278
  • 309
5

Making a separate function is very helpful.

def get_random_image(self):
    for rand in func.random, func.rand:
        try:                                                           
            return self.images.order_by(rand()).limit(1)
        except ProgrammingError:                                                        
            pass
    raise ProgrammingError('This database engine is not supported')
Oleh Prypin
  • 33,184
  • 10
  • 89
  • 99
3

In this particular case, I'd actually try and detect the database before selecting the function. Can you reach the database connection from your code? If so, just switch on the drivername:

random = None
if drivername == 'postgres':
    random = func.random
elif drivername == 'mysql':
    random = func.rand
else:
    raise ValueError('This module requires that you use PostgreSQL or MySQL')

Then, when selecting images, use the random value:

image = self.images.order_by(random()).limit(1)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I read somewhere that `func.rand` also works for SQLite. It's not good to make additional limits where it actually might work. And anyway, Python's philosophy always encourages to use `try`. In my opinion this answer is also worse than the code in the original post. – Oleh Prypin Aug 13 '12 at 14:43
  • 1
    @BlaXpirit: I normally would agree, but in this case you end up with nested try/except branches where one of the three outcomes will *always* be selected (not throw an exception). At the very least you need to find out *once* which one of the three it'll be and avoid the overhead on repeated calls. – Martijn Pieters Aug 13 '12 at 14:45
1

Actually it MIGHT be a design flaw. Raising exceptions is to act on an event that should normally not occur. If you want to do something functionally into an except (except for handling the exception), than it looks like the first statement that you want to try is not a statement that should get an exception at all.

So instead of:

try:
    do statement 1
except ...
    try:
        do statement 2
    except:

think about :

if (statement_1 result == ...)
    try:
         do statement 2
    except:
Michel Keijzers
  • 15,025
  • 28
  • 93
  • 119
0

If you want to check if rand or random is a function of a class, you also could use

if 'rand' in dir(some object of a class)
Michel Keijzers
  • 15,025
  • 28
  • 93
  • 119