|
| 1 | +# Copyright 2020 resspect software |
| 2 | +# Author: Rob Knop |
| 3 | +# |
| 4 | +# created on 12 March 2024 |
| 5 | +# |
| 6 | +# Licensed GNU General Public License v3.0; |
| 7 | +# you may not use this file except in compliance with the License. |
| 8 | +# You may obtain a copy of the License at |
| 9 | +# |
| 10 | +# https://www.gnu.org/licenses/gpl-3.0.en.html |
| 11 | +# |
| 12 | +# Unless required by applicable law or agreed to in writing, software |
| 13 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | +# See the License for the specific language governing permissions and |
| 16 | +# limitations under the License. |
| 17 | + |
| 18 | +import requests |
| 19 | + |
| 20 | +class TomClient: |
| 21 | + """A thin class that supports sending requests via "requests" to the DESC tom. |
| 22 | +
|
| 23 | + Usage: initialize one of these, giving it the url, your TOM |
| 24 | + username, and either your TOM password, or a file that has your TOM |
| 25 | + password in it: |
| 26 | +
|
| 27 | + tc = TomClient( username='rknop', passwordfile='/home/raknop/secrets/tom_rknop_passwd' ) |
| 28 | +
|
| 29 | + (You can give it a url with url=; it defaults to https://desc-tom.lbl.gov.) |
| 30 | +
|
| 31 | + Thereafter, just do something like |
| 32 | +
|
| 33 | + res = tc.request( "POST", "elasticc2/ppdbdiaobject/55772173" ) |
| 34 | +
|
| 35 | + and res will come back with a string that you can load into JSON |
| 36 | + that will have all the fun data about PPDBDiaObject number 55772173. |
| 37 | +
|
| 38 | + tc.request is just a thin front-end to python requests.request. The |
| 39 | + only reason to use this client rather than the python requests |
| 40 | + module directly is that this class takes care of the stupid fiddly |
| 41 | + bits of getting some headers that django demands set up right in the |
| 42 | + request object when you log in. |
| 43 | +
|
| 44 | + """ |
| 45 | + |
| 46 | + def __init__( self, url="https://desc-tom.lbl.gov", username=None, password=None, passwordfile=None, connect=True ): |
| 47 | + self._url = url |
| 48 | + self._username = username |
| 49 | + self._password = password |
| 50 | + self._rqs = None |
| 51 | + if self._password is None: |
| 52 | + if passwordfile is None: |
| 53 | + raise RuntimeError( "Must give either password or passwordfile. " ) |
| 54 | + with open( passwordfile ) as ifp: |
| 55 | + self._password = ifp.readline().strip() |
| 56 | + |
| 57 | + if connect: |
| 58 | + self.connect() |
| 59 | + |
| 60 | + def connect( self ): |
| 61 | + self._rqs = requests.session() |
| 62 | + res = self._rqs.get( f'{self._url}/accounts/login/' ) |
| 63 | + if res.status_code != 200: |
| 64 | + raise RuntimeError( f"Got status {res.status_code} from first attempt to connect to {self._url}" ) |
| 65 | + res = self._rqs.post( f'{self._url}/accounts/login/', |
| 66 | + data={ 'username': self._username, |
| 67 | + 'password': self._password, |
| 68 | + 'csrfmiddlewaretoken': self._rqs.cookies['csrftoken'] } ) |
| 69 | + if res.status_code != 200: |
| 70 | + raise RuntimeError( f"Failed to log in; http status: {res.status_code}" ) |
| 71 | + if 'Please enter a correct' in res.text: |
| 72 | + # This is a very cheesy attempt at checking if the login failed. |
| 73 | + # I haven't found clean documentation on how to log into a django site |
| 74 | + # from an app like this using standard authentication stuff. So, for |
| 75 | + # now, I'm counting on the HTML that happened to come back when |
| 76 | + # I ran it with a failed login one time. One of these days I'll actually |
| 77 | + # figure out how Django auth works and make a version of /accounts/login/ |
| 78 | + # designed for use in API scripts like this one, rather than desgined |
| 79 | + # for interactive users. |
| 80 | + raise RuntimeError( "Failed to log in. I think. Put in a debug break and look at res.text" ) |
| 81 | + self._rqs.headers.update( { 'X-CSRFToken': self._rqs.cookies['csrftoken'] } ) |
| 82 | + |
| 83 | + def request( self, method="GET", page=None, **kwargs ): |
| 84 | + """Send a request to the TOM |
| 85 | +
|
| 86 | + method : a string with the HTTP method ("GET", "POST", etc.) |
| 87 | +
|
| 88 | + page : the page to get; this is the URL but with the url you |
| 89 | + passed to the constructor removed. So, if you wanted to get |
| 90 | + https://desc-tom.lbl.gov/elasticc, you'd pass just "elasticc" |
| 91 | + here. |
| 92 | +
|
| 93 | + **kwargs : additional keyword arguments are passed on to |
| 94 | + requests.request |
| 95 | +
|
| 96 | + """ |
| 97 | + return self._rqs.request( method=method, url=f"{self._url}/{page}", **kwargs ) |
0 commit comments