pyorbital icon indicating copy to clipboard operation
pyorbital copied to clipboard

tlefile.py urlopen does not recognize celestar ssl certificate

Open meteodave opened this issue 3 months ago • 12 comments

Issue:

In pyorbital/tlefile.py as called from satpy, urlopen does not recognize the certificates for TLE_URLS and produces an error "URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1010)>." Testing urlopen alone also produces the same error. However, when testing the python "request" module or running curl in CLI, the a certificate error is not produced for these TLE urls.

Configuration:

OS: linux
pyorbital version: 1.10.2
urllib3 version:  2.5.0 

Error:

URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1010)>

Code: Simple python code producing error with extracted TLE_URLS from pyorbital/tlefile.py.

from urllib.request import urlopen

TLE_URLS = ['https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=resource&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=cubesat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=sarsat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=noaa&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=engineering&FORMAT=tle']

fileobject = urlopen(TLE_URLS[0])

print(fileobject)

Output (from simple code above):

---------------------------------------------------------------------------
SSLCertVerificationError                  Traceback (most recent call last)
File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:1344, in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
   1343 try:
-> 1344     h.request(req.get_method(), req.selector, req.data, headers,
   1345               encode_chunked=req.has_header('Transfer-encoding'))
   1346 except OSError as err: # timeout error

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1338, in HTTPConnection.request(self, method, url, body, headers, encode_chunked)
   1337 """Send a complete request to the server."""
-> 1338 self._send_request(method, url, body, headers, encode_chunked)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1384, in HTTPConnection._send_request(self, method, url, body, headers, encode_chunked)
   1383     body = _encode(body, 'body')
-> 1384 self.endheaders(body, encode_chunked=encode_chunked)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1333, in HTTPConnection.endheaders(self, message_body, encode_chunked)
   1332     raise CannotSendHeader()
-> 1333 self._send_output(message_body, encode_chunked=encode_chunked)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1093, in HTTPConnection._send_output(self, message_body, encode_chunked)
   1092 del self._buffer[:]
-> 1093 self.send(msg)
   1095 if message_body is not None:
   1096 
   1097     # create a consistent interface to message_body

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1037, in HTTPConnection.send(self, data)
   1036 if self.auto_open:
-> 1037     self.connect()
   1038 else:

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1479, in HTTPSConnection.connect(self)
   1477     server_hostname = self.host
-> 1479 self.sock = self._context.wrap_socket(self.sock,
   1480                                       server_hostname=server_hostname)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/ssl.py:455, in SSLContext.wrap_socket(self, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, session)
    449 def wrap_socket(self, sock, server_side=False,
    450                 do_handshake_on_connect=True,
    451                 suppress_ragged_eofs=True,
    452                 server_hostname=None, session=None):
    453     # SSLSocket class handles server_hostname encoding before it calls
    454     # ctx._wrap_socket()
--> 455     return self.sslsocket_class._create(
    456         sock=sock,
    457         server_side=server_side,
    458         do_handshake_on_connect=do_handshake_on_connect,
    459         suppress_ragged_eofs=suppress_ragged_eofs,
    460         server_hostname=server_hostname,
    461         context=self,
    462         session=session
    463     )

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/ssl.py:1041, in SSLSocket._create(cls, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, context, session)
   1040                 raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
-> 1041             self.do_handshake()
   1042 except:

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/ssl.py:1319, in SSLSocket.do_handshake(self, block)
   1318         self.settimeout(None)
-> 1319     self._sslobj.do_handshake()
   1320 finally:

SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1010)

During handling of the above exception, another exception occurred:

URLError                                  Traceback (most recent call last)
Cell In[1], line 6
      2 from urllib.request import urlopen
      4 TLE_URLS = ['https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=resource&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=cubesat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=sarsat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=noaa&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=engineering&FORMAT=tle']
----> 6 fileobject = urlopen(TLE_URLS[0])
      8 print(fileobject)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:215, in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    213 else:
    214     opener = _opener
--> 215 return opener.open(url, data, timeout)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:515, in OpenerDirector.open(self, fullurl, data, timeout)
    512     req = meth(req)
    514 sys.audit('urllib.Request', req.full_url, req.data, req.headers, req.get_method())
--> 515 response = self._open(req, data)
    517 # post-process response
    518 meth_name = protocol+"_response"

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:532, in OpenerDirector._open(self, req, data)
    529     return result
    531 protocol = req.type
--> 532 result = self._call_chain(self.handle_open, protocol, protocol +
    533                           '_open', req)
    534 if result:
    535     return result

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:492, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args)
    490 for handler in handlers:
    491     func = getattr(handler, meth_name)
--> 492     result = func(*args)
    493     if result is not None:
    494         return result

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:1392, in HTTPSHandler.https_open(self, req)
   1391 def https_open(self, req):
-> 1392     return self.do_open(http.client.HTTPSConnection, req,
   1393                         context=self._context)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:1347, in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
   1344         h.request(req.get_method(), req.selector, req.data, headers,
   1345                   encode_chunked=req.has_header('Transfer-encoding'))
   1346     except OSError as err: # timeout error
-> 1347         raise URLError(err)
   1348     r = h.getresponse()
   1349 except:

URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1010)>


Output (from satpy run):

[DEBUG: 2025-09-11 18:48:09 : satpy.readers.core.yaml_reader] Reading ('/home/jovyan/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/etc/readers/ahi_l2_nc.yaml',)
[DEBUG: 2025-09-11 18:48:09 : satpy.readers.core.yaml_reader] Assigning to ahi_l2_nc: ['/home/jovyan/tropics-tobac_flow/tobac-flow-1.8.2/data/himawari/AHI-CHGT_v1r1_h09_s202509010000209_e202509010009403_c202509010016093.nc']
[DEBUG: 2025-09-11 18:48:09 : h5py._conv] Creating converter from 7 to 5
[DEBUG: 2025-09-11 18:48:09 : h5py._conv] Creating converter from 5 to 7
[DEBUG: 2025-09-11 18:48:09 : h5py._conv] Creating converter from 7 to 5
[DEBUG: 2025-09-11 18:48:09 : h5py._conv] Creating converter from 5 to 7
[DEBUG: 2025-09-11 18:48:09 : satpy.composites.config_loader] Looking for composites config file ahi.yaml
[DEBUG: 2025-09-11 18:48:10 : satpy.composites.config_loader] Looking for composites config file visir.yaml
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Band number = 10
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Time_interval: 2025-09-01 00:00:00 - 2025-09-01 00:10:00
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Band number = 10
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Time_interval: 2025-09-01 00:00:00 - 2025-09-01 00:10:00
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Band number = 10
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Time_interval: 2025-09-01 00:00:00 - 2025-09-01 00:10:00
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Band number = 10
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Time_interval: 2025-09-01 00:00:00 - 2025-09-01 00:10:00
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Band number = 10
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Time_interval: 2025-09-01 00:00:00 - 2025-09-01 00:10:00
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Band number = 10
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Time_interval: 2025-09-01 00:00:00 - 2025-09-01 00:10:00
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Band number = 10
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Time_interval: 2025-09-01 00:00:00 - 2025-09-01 00:10:00
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Band number = 10
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Time_interval: 2025-09-01 00:00:00 - 2025-09-01 00:10:00
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Band number = 10
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Time_interval: 2025-09-01 00:00:00 - 2025-09-01 00:10:00
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Band number = 10
[DEBUG: 2025-09-11 18:48:10 : ahi_hsd] Time_interval: 2025-09-01 00:00:00 - 2025-09-01 00:10:00
[DEBUG: 2025-09-11 18:48:10 : satpy.readers.core.yaml_reader] Requested orientation for Dataset B10 is 'native' (default). No flipping is applied.
[DEBUG: 2025-09-11 18:48:10 : satpy.readers.ahi_l2_nc] Reading in get_dataset CldTopHght.
[INFO: 2025-09-11 18:48:10 : satpy.readers.ahi_l2_nc] The AHI L2 cloud products do not have the metadata required to produce an area definition. Assuming standard Himawari-8/9 full disk projection.
[DEBUG: 2025-09-11 18:48:10 : satpy.modifiers.parallax] Calculating parallax correction using heights from cloud_top_height, with base area AHI FLDK area.
[WARNING: 2025-09-11 18:48:10 : satpy.utils] Orbital parameters missing from metadata.  Calculating from TLE using skyfield and astropy.
[DEBUG: 2025-09-11 18:48:10 : pyorbital.tlefile] Fetch TLE from the internet.
[INFO: 2025-09-11 18:48:10 : pyorbital.tlefile] attempting url: ['https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=resource&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=cubesat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=sarsat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=noaa&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=engineering&FORMAT=tle']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/utils.py:358, in get_satpos(data_arr, preference, use_tle)
    357 try:
--> 358     lon, lat = _get_sat_lonlat(data_arr, lonlat_prefixes)
    359     alt = _get_sat_altitude(data_arr, alt_prefixes)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/utils.py:396, in _get_sat_lonlat(data_arr, key_prefixes)
    395 def _get_sat_lonlat(data_arr, key_prefixes):
--> 396     orb_params = data_arr.attrs["orbital_parameters"]
    397     lon_keys = [prefix + "longitude" for prefix in key_prefixes]

KeyError: 'orbital_parameters'

During handling of the above exception, another exception occurred:

SSLCertVerificationError                  Traceback (most recent call last)
File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:1344, in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
   1343 try:
-> 1344     h.request(req.get_method(), req.selector, req.data, headers,
   1345               encode_chunked=req.has_header('Transfer-encoding'))
   1346 except OSError as err: # timeout error

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1338, in HTTPConnection.request(self, method, url, body, headers, encode_chunked)
   1337 """Send a complete request to the server."""
-> 1338 self._send_request(method, url, body, headers, encode_chunked)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1384, in HTTPConnection._send_request(self, method, url, body, headers, encode_chunked)
   1383     body = _encode(body, 'body')
-> 1384 self.endheaders(body, encode_chunked=encode_chunked)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1333, in HTTPConnection.endheaders(self, message_body, encode_chunked)
   1332     raise CannotSendHeader()
-> 1333 self._send_output(message_body, encode_chunked=encode_chunked)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1093, in HTTPConnection._send_output(self, message_body, encode_chunked)
   1092 del self._buffer[:]
-> 1093 self.send(msg)
   1095 if message_body is not None:
   1096 
   1097     # create a consistent interface to message_body

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1037, in HTTPConnection.send(self, data)
   1036 if self.auto_open:
-> 1037     self.connect()
   1038 else:

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/http/client.py:1479, in HTTPSConnection.connect(self)
   1477     server_hostname = self.host
-> 1479 self.sock = self._context.wrap_socket(self.sock,
   1480                                       server_hostname=server_hostname)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/ssl.py:455, in SSLContext.wrap_socket(self, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, session)
    449 def wrap_socket(self, sock, server_side=False,
    450                 do_handshake_on_connect=True,
    451                 suppress_ragged_eofs=True,
    452                 server_hostname=None, session=None):
    453     # SSLSocket class handles server_hostname encoding before it calls
    454     # ctx._wrap_socket()
--> 455     return self.sslsocket_class._create(
    456         sock=sock,
    457         server_side=server_side,
    458         do_handshake_on_connect=do_handshake_on_connect,
    459         suppress_ragged_eofs=suppress_ragged_eofs,
    460         server_hostname=server_hostname,
    461         context=self,
    462         session=session
    463     )

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/ssl.py:1041, in SSLSocket._create(cls, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, context, session)
   1040                 raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
-> 1041             self.do_handshake()
   1042 except:

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/ssl.py:1319, in SSLSocket.do_handshake(self, block)
   1318         self.settimeout(None)
-> 1319     self._sslobj.do_handshake()
   1320 finally:

SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1010)

During handling of the above exception, another exception occurred:

URLError                                  Traceback (most recent call last)
Cell In[1], line 52
     42 reader_kwargs = {
     43     'ahi_hsd': {
     44         'storage_options': storage_options,
   (...)     47     },
     48 }
     50 scn = Scene(filenames=filenames, reader_kwargs=reader_kwargs)
---> 52 scn.load(['parallax_corrected_B10'])
     54 print(scn.available_dataset_names())
     55 print(scn.all_dataset_names())

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1476, in Scene.load(self, wishlist, calibration, resolution, polarization, level, modifiers, generate, unload, **kwargs)
   1474 self._read_datasets_from_storage(**kwargs)
   1475 if generate:
-> 1476     self.generate_possible_composites(unload)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1539, in Scene.generate_possible_composites(self, unload)
   1532 def generate_possible_composites(self, unload):
   1533     """See which composites can be generated and generate them.
   1534 
   1535     Args:
   1536         unload (bool): if the dependencies of the composites
   1537                        should be unloaded after successful generation.
   1538     """
-> 1539     keepables = self._generate_composites_from_loaded_datasets()
   1541     if self.missing_datasets:
   1542         self._remove_failed_datasets(keepables)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1558, in Scene._generate_composites_from_loaded_datasets(self)
   1555 trunk_nodes = self._dependency_tree.trunk(limit_nodes_to=self.missing_datasets,
   1556                                           limit_children_to=self._datasets.keys())
   1557 needed_comp_nodes = set(self._filter_loaded_datasets_from_trunk_nodes(trunk_nodes))
-> 1558 return self._generate_composites_nodes_from_loaded_datasets(needed_comp_nodes)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1564, in Scene._generate_composites_nodes_from_loaded_datasets(self, compositor_nodes)
   1562 keepables = set()
   1563 for node in compositor_nodes:
-> 1564     self._generate_composite(node, keepables)
   1565 return keepables

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1588, in Scene._generate_composite(self, comp_node, keepables)
   1586 try:
   1587     delayed_prereq = False
-> 1588     prereq_datasets = self._get_prereq_datasets(
   1589         comp_node.name,
   1590         prereqs,
   1591         keepables,
   1592     )
   1593 except DelayedGeneration:
   1594     # if we are missing a required dependency that could be generated
   1595     # later then we need to wait to return until after we've also
   1596     # processed the optional dependencies
   1597     delayed_prereq = True

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1670, in Scene._get_prereq_datasets(self, comp_id, prereq_nodes, keepables, skip)
   1667 prereq_id = prereq_node.name
   1668 if prereq_id not in self._datasets and prereq_id not in keepables \
   1669         and isinstance(prereq_node, CompositorNode):
-> 1670     self._generate_composite(prereq_node, keepables)
   1672 # composite generation may have updated the DataID
   1673 prereq_id = prereq_node.name

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1622, in Scene._generate_composite(self, comp_node, keepables)
   1619     return
   1621 try:
-> 1622     composite = compositor(prereq_datasets,
   1623                            optional_datasets=optional_datasets,
   1624                            **comp_node.name.to_dict())
   1625     cid = DataID.new_id_from_dataarray(composite)
   1626     self._datasets[cid] = composite

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/modifiers/parallax.py:518, in ParallaxCorrectionModifier.__call__(self, projectables, optional_datasets, **info)
    516 base_area = to_be_corrected.attrs["area"]
    517 corrector = self._get_corrector(base_area)
--> 518 plax_corr_area = corrector(
    519         cth,
    520         cth_resampler=self.attrs.get("cth_resampler", "nearest"),
    521         cth_radius_of_influence=self.attrs.get("cth_radius_of_influence", 50_000),
    522         lonlat_chunks=self.attrs.get("lonlat_chunks", 1024),
    523         )
    524 res = resample_dataset(
    525         to_be_corrected, plax_corr_area,
    526         radius_of_influence=self.attrs.get("dataset_radius_of_influence", 50_000),
    527         fill_value=np.nan)
    528 res.attrs["area"] = to_be_corrected.attrs["area"]

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/modifiers/parallax.py:280, in ParallaxCorrection.__call__(self, cth_dataset, **kwargs)
    269 """Apply parallax correction to dataset.
    270 
    271 Args:
   (...)    277         lat/lon coordinates.
    278 """
    279 self.diagnostics.clear()
--> 280 return self.corrected_area(cth_dataset, **kwargs)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/modifiers/parallax.py:318, in ParallaxCorrection.corrected_area(self, cth_dataset, cth_resampler, cth_radius_of_influence, lonlat_chunks)
    286 """Return the parallax corrected SwathDefinition.
    287 
    288 Using the cloud top heights provided in ``cth_dataset``, calculate the
   (...)    313     corrected geolocation.
    314 """
    315 logger.debug("Calculating parallax correction using heights from "
    316              f"{cth_dataset.attrs.get('name', cth_dataset.name)!s}, "
    317              f"with base area {self.base_area.name!s}.")
--> 318 (sat_lon, sat_lat, sat_alt_m) = _get_satpos_from_cth(cth_dataset)
    319 self._check_overlap(cth_dataset)
    321 cth_dataset = self._prepare_cth_dataset(
    322         cth_dataset, resampler=cth_resampler,
    323         radius_of_influence=cth_radius_of_influence,
    324         lonlat_chunks=lonlat_chunks)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/modifiers/parallax.py:551, in _get_satpos_from_cth(cth_dataset)
    544 def _get_satpos_from_cth(cth_dataset):
    545     """Obtain satellite position from CTH dataset, height in meter.
    546 
    547     From a CTH dataset, obtain the satellite position lon, lat, altitude/m,
    548     either directly from orbital parameters, or, when missing, from the
    549     platform name using pyorbital and skyfield.
    550     """
--> 551     (sat_lon, sat_lat, sat_alt_km) = get_satpos(
    552             cth_dataset, use_tle=True)
    553     return (sat_lon, sat_lat, sat_alt_km * 1000)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/utils.py:365, in get_satpos(data_arr, preference, use_tle)
    361     if use_tle:
    362         logger.warning(
    363                 "Orbital parameters missing from metadata.  "
    364                 "Calculating from TLE using skyfield and astropy.")
--> 365         return _get_satpos_from_platform_name(data_arr)
    366     raise KeyError("Unable to determine satellite position. Either the "
    367                    "reader doesn't provide that information or "
    368                    "geolocation datasets were not available.")
    369 return lon, lat, alt

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/utils.py:426, in _get_satpos_from_platform_name(cth_dataset)
    423 from skyfield.toposlib import wgs84
    425 name = cth_dataset.attrs["platform_name"]
--> 426 tle = tlefile.read(name)
    427 es = EarthSatellite(tle.line1, tle.line2, name)
    428 ts = load.timescale()

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:150, in read(platform, tle_file, line1, line2)
    142 def read(platform, tle_file=None, line1=None, line2=None):
    143     """Read TLE for *platform*.
    144 
    145     The data are read from *tle_file*, from *line1* and *line2*, from
   (...)    148 
    149     """
--> 150     return Tle(platform, tle_file=tle_file, line1=line1, line2=line2)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:200, in Tle.__init__(self, platform, tle_file, line1, line2)
    197 self.mean_motion = None
    198 self.orbit = None
--> 200 self._read_tle()
    201 self._checksum()
    202 self._parse_tle()

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:238, in Tle._read_tle(self)
    236 else:
    237     uris, open_func = _get_uris_and_open_func(tle_file=self._tle_file)
--> 238     tle = _get_first_tle(uris, open_func, platform=self._platform)
    240     if not tle:
    241         raise KeyError("Found no TLE entry for '%s'" % self._platform)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:334, in _get_first_tle(uris, open_func, platform)
    333 def _get_first_tle(uris, open_func, platform=""):
--> 334     return _get_tles_from_uris(uris, open_func, platform=platform, only_first=True)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:340, in _get_tles_from_uris(uris, open_func, platform, only_first)
    338 tles = []
    339 for url in uris:
--> 340     tles += _get_tles_from_url(url, open_func, platform, only_first)
    341 if only_first:
    342     if tles:

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:360, in _get_tles_from_url(url, open_func, platform, only_first)
    359 def _get_tles_from_url(url, open_func, platform, only_first):
--> 360     with _uri_open(url, open_func) as fid:
    361         open_is_dummy = open_func == _dummy_open_stringio
    362         tles = []

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/contextlib.py:137, in _GeneratorContextManager.__enter__(self)
    135 del self.args, self.kwds, self.func
    136 try:
--> 137     return next(self.gen)
    138 except StopIteration:
    139     raise RuntimeError("generator didn't yield") from None

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:351, in _uri_open(uri, open_func)
    348 @contextlib.contextmanager
    349 def _uri_open(uri, open_func):
    350     #file_obj = open_func(uri, context=context)
--> 351     file_obj = open_func(uri)
    352     try:
    353         yield file_obj

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:215, in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    213 else:
    214     opener = _opener
--> 215 return opener.open(url, data, timeout)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:515, in OpenerDirector.open(self, fullurl, data, timeout)
    512     req = meth(req)
    514 sys.audit('urllib.Request', req.full_url, req.data, req.headers, req.get_method())
--> 515 response = self._open(req, data)
    517 # post-process response
    518 meth_name = protocol+"_response"

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:532, in OpenerDirector._open(self, req, data)
    529     return result
    531 protocol = req.type
--> 532 result = self._call_chain(self.handle_open, protocol, protocol +
    533                           '_open', req)
    534 if result:
    535     return result

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:492, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args)
    490 for handler in handlers:
    491     func = getattr(handler, meth_name)
--> 492     result = func(*args)
    493     if result is not None:
    494         return result

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:1392, in HTTPSHandler.https_open(self, req)
   1391 def https_open(self, req):
-> 1392     return self.do_open(http.client.HTTPSConnection, req,
   1393                         context=self._context)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:1347, in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
   1344         h.request(req.get_method(), req.selector, req.data, headers,
   1345                   encode_chunked=req.has_header('Transfer-encoding'))
   1346     except OSError as err: # timeout error
-> 1347         raise URLError(err)
   1348     r = h.getresponse()
   1349 except:

URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1010)>

meteodave avatar Sep 11 '25 19:09 meteodave

Thank you for reporting this issue! I tested with the provided code example, and it does work for me?

mraspaud avatar Sep 11 '25 19:09 mraspaud

Works for me also. Could it be a firewall change on your side @meteodave?

In [1]: from urllib.request import urlopen
   ...: 
   ...: TLE_URLS = ['https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROU
      ⋮ P=resource&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=cubesat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle', 'https://celestrak.org/NORAD/elemen
      ⋮ ts/gp.php?GROUP=sarsat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=noaa&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=tle', 'https://celestrak.org/NORA
      ⋮ D/elements/gp.php?GROUP=engineering&FORMAT=tle']
   ...: 
   ...: fileobject = urlopen(TLE_URLS[0])
   ...: 
   ...: print(fileobject)
<http.client.HTTPResponse object at 0x7ef86bb5bcd0>

djhoese avatar Sep 11 '25 19:09 djhoese

@djhoese and @mraspaud Thanks for checking. I can only get it to work like this by adding the ssl_context to point to the cert_path:

from urllib.request import urlopen
import certifi
import ssl

cert_path = certifi.where()

print(cert_path)

ssl_context = ssl.create_default_context(cafile=cert_path)

TLE_URLS = ['https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=resource&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=cubesat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=sarsat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=noaa&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=engineering&FORMAT=tle']

fileobject = urlopen(TLE_URLS[0],context=ssl_context)

print(fileobject)
/home/jovyan/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/certifi/cacert.pem
<http.client.HTTPResponse object at 0x7fd48567a950>

meteodave avatar Sep 11 '25 19:09 meteodave

We also have a quite annoying (but secure) stuff in our ssl chains here at the institute, and I've found the "truststore" package to be helpful https://pypi.org/project/truststore/

mraspaud avatar Sep 11 '25 19:09 mraspaud

Have you gotten it to work on your current machine on this exact network with the same python environment in the past?

djhoese avatar Sep 11 '25 19:09 djhoese

I am working on an AWS instance.

platform: Linux-6.1.147-172.266.amzn2023.x86_64-x86_64-with-glibc2.35
python: 3.12.11

This is the first time trying to access TLE data using pyorbital by way of satpy.

meteodave avatar Sep 11 '25 19:09 meteodave

I hopped over to another AWS instance in a different region and now urlopen works. Perhaps there is an issue with the system itself but I am surprised urlopen does not work but curl and request do. I will need to ask the sys admins about the difference. Thanks for your help.

meteodave avatar Sep 11 '25 19:09 meteodave

Interesting! Good that you got it to work on another instance. As for urlopen, I would be OK with a PR replacing it with requests...

mraspaud avatar Sep 12 '25 04:09 mraspaud

I am still waiting to hear back from the SAs. Here is the requests Python code that works for me.

#read urls
import requests

TLE_URLS = ['https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=resource&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=cubesat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=sarsat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=noaa&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=engineering&FORMAT=tle']

response = requests.get(TLE_URLS[0])

print(response)
<Response [200]>

meteodave avatar Sep 12 '25 13:09 meteodave

Our SAs created an updated instance in AWS and now it seemed to work last week using urlopen.

When I access celestrack.org this week, the web site indicates "service unavailable." Do you have a recommended alternative web site to obtain TLE?

Pyorbital returns the following error output:

[WARNING: 2025-09-22 15:19:54 : satpy.utils] Orbital parameters missing from metadata.  Calculating from TLE using skyfield and astropy.
[DEBUG: 2025-09-22 15:19:54 : pyorbital.tlefile] Fetch TLE from the internet.
[INFO: 2025-09-22 15:19:54 : pyorbital.tlefile] attempting url: ['https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=resource&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=cubesat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=sarsat&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=noaa&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=tle', 'https://celestrak.org/NORAD/elements/gp.php?GROUP=engineering&FORMAT=tle']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/utils.py:358, in get_satpos(data_arr, preference, use_tle)
    357 try:
--> 358     lon, lat = _get_sat_lonlat(data_arr, lonlat_prefixes)
    359     alt = _get_sat_altitude(data_arr, alt_prefixes)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/utils.py:396, in _get_sat_lonlat(data_arr, key_prefixes)
    395 def _get_sat_lonlat(data_arr, key_prefixes):
--> 396     orb_params = data_arr.attrs["orbital_parameters"]
    397     lon_keys = [prefix + "longitude" for prefix in key_prefixes]

KeyError: 'orbital_parameters'

During handling of the above exception, another exception occurred:

HTTPError                                 Traceback (most recent call last)
Cell In[5], line 2
      1 scn = Scene(filenames=filenames, reader_kwargs=reader_kwargs)
----> 2 scn.load(datasets)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1476, in Scene.load(self, wishlist, calibration, resolution, polarization, level, modifiers, generate, unload, **kwargs)
   1474 self._read_datasets_from_storage(**kwargs)
   1475 if generate:
-> 1476     self.generate_possible_composites(unload)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1539, in Scene.generate_possible_composites(self, unload)
   1532 def generate_possible_composites(self, unload):
   1533     """See which composites can be generated and generate them.
   1534 
   1535     Args:
   1536         unload (bool): if the dependencies of the composites
   1537                        should be unloaded after successful generation.
   1538     """
-> 1539     keepables = self._generate_composites_from_loaded_datasets()
   1541     if self.missing_datasets:
   1542         self._remove_failed_datasets(keepables)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1558, in Scene._generate_composites_from_loaded_datasets(self)
   1555 trunk_nodes = self._dependency_tree.trunk(limit_nodes_to=self.missing_datasets,
   1556                                           limit_children_to=self._datasets.keys())
   1557 needed_comp_nodes = set(self._filter_loaded_datasets_from_trunk_nodes(trunk_nodes))
-> 1558 return self._generate_composites_nodes_from_loaded_datasets(needed_comp_nodes)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1564, in Scene._generate_composites_nodes_from_loaded_datasets(self, compositor_nodes)
   1562 keepables = set()
   1563 for node in compositor_nodes:
-> 1564     self._generate_composite(node, keepables)
   1565 return keepables

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/scene.py:1622, in Scene._generate_composite(self, comp_node, keepables)
   1619     return
   1621 try:
-> 1622     composite = compositor(prereq_datasets,
   1623                            optional_datasets=optional_datasets,
   1624                            **comp_node.name.to_dict())
   1625     cid = DataID.new_id_from_dataarray(composite)
   1626     self._datasets[cid] = composite

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/modifiers/parallax.py:518, in ParallaxCorrectionModifier.__call__(self, projectables, optional_datasets, **info)
    516 base_area = to_be_corrected.attrs["area"]
    517 corrector = self._get_corrector(base_area)
--> 518 plax_corr_area = corrector(
    519         cth,
    520         cth_resampler=self.attrs.get("cth_resampler", "nearest"),
    521         cth_radius_of_influence=self.attrs.get("cth_radius_of_influence", 50_000),
    522         lonlat_chunks=self.attrs.get("lonlat_chunks", 1024),
    523         )
    524 res = resample_dataset(
    525         to_be_corrected, plax_corr_area,
    526         radius_of_influence=self.attrs.get("dataset_radius_of_influence", 50_000),
    527         fill_value=np.nan)
    528 res.attrs["area"] = to_be_corrected.attrs["area"]

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/modifiers/parallax.py:280, in ParallaxCorrection.__call__(self, cth_dataset, **kwargs)
    269 """Apply parallax correction to dataset.
    270 
    271 Args:
   (...)    277         lat/lon coordinates.
    278 """
    279 self.diagnostics.clear()
--> 280 return self.corrected_area(cth_dataset, **kwargs)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/modifiers/parallax.py:318, in ParallaxCorrection.corrected_area(self, cth_dataset, cth_resampler, cth_radius_of_influence, lonlat_chunks)
    286 """Return the parallax corrected SwathDefinition.
    287 
    288 Using the cloud top heights provided in ``cth_dataset``, calculate the
   (...)    313     corrected geolocation.
    314 """
    315 logger.debug("Calculating parallax correction using heights from "
    316              f"{cth_dataset.attrs.get('name', cth_dataset.name)!s}, "
    317              f"with base area {self.base_area.name!s}.")
--> 318 (sat_lon, sat_lat, sat_alt_m) = _get_satpos_from_cth(cth_dataset)
    319 self._check_overlap(cth_dataset)
    321 cth_dataset = self._prepare_cth_dataset(
    322         cth_dataset, resampler=cth_resampler,
    323         radius_of_influence=cth_radius_of_influence,
    324         lonlat_chunks=lonlat_chunks)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/modifiers/parallax.py:551, in _get_satpos_from_cth(cth_dataset)
    544 def _get_satpos_from_cth(cth_dataset):
    545     """Obtain satellite position from CTH dataset, height in meter.
    546 
    547     From a CTH dataset, obtain the satellite position lon, lat, altitude/m,
    548     either directly from orbital parameters, or, when missing, from the
    549     platform name using pyorbital and skyfield.
    550     """
--> 551     (sat_lon, sat_lat, sat_alt_km) = get_satpos(
    552             cth_dataset, use_tle=True)
    553     return (sat_lon, sat_lat, sat_alt_km * 1000)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/utils.py:365, in get_satpos(data_arr, preference, use_tle)
    361     if use_tle:
    362         logger.warning(
    363                 "Orbital parameters missing from metadata.  "
    364                 "Calculating from TLE using skyfield and astropy.")
--> 365         return _get_satpos_from_platform_name(data_arr)
    366     raise KeyError("Unable to determine satellite position. Either the "
    367                    "reader doesn't provide that information or "
    368                    "geolocation datasets were not available.")
    369 return lon, lat, alt

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/satpy/utils.py:426, in _get_satpos_from_platform_name(cth_dataset)
    423 from skyfield.toposlib import wgs84
    425 name = cth_dataset.attrs["platform_name"]
--> 426 tle = tlefile.read(name)
    427 es = EarthSatellite(tle.line1, tle.line2, name)
    428 ts = load.timescale()

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:150, in read(platform, tle_file, line1, line2)
    142 def read(platform, tle_file=None, line1=None, line2=None):
    143     """Read TLE for *platform*.
    144 
    145     The data are read from *tle_file*, from *line1* and *line2*, from
   (...)    148 
    149     """
--> 150     return Tle(platform, tle_file=tle_file, line1=line1, line2=line2)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:200, in Tle.__init__(self, platform, tle_file, line1, line2)
    197 self.mean_motion = None
    198 self.orbit = None
--> 200 self._read_tle()
    201 self._checksum()
    202 self._parse_tle()

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:238, in Tle._read_tle(self)
    236 else:
    237     uris, open_func = _get_uris_and_open_func(tle_file=self._tle_file)
--> 238     tle = _get_first_tle(uris, open_func, platform=self._platform)
    240     if not tle:
    241         raise KeyError("Found no TLE entry for '%s'" % self._platform)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:334, in _get_first_tle(uris, open_func, platform)
    333 def _get_first_tle(uris, open_func, platform=""):
--> 334     return _get_tles_from_uris(uris, open_func, platform=platform, only_first=True)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:340, in _get_tles_from_uris(uris, open_func, platform, only_first)
    338 tles = []
    339 for url in uris:
--> 340     tles += _get_tles_from_url(url, open_func, platform, only_first)
    341 if only_first:
    342     if tles:

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:360, in _get_tles_from_url(url, open_func, platform, only_first)
    359 def _get_tles_from_url(url, open_func, platform, only_first):
--> 360     with _uri_open(url, open_func) as fid:
    361         open_is_dummy = open_func == _dummy_open_stringio
    362         tles = []

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/contextlib.py:137, in _GeneratorContextManager.__enter__(self)
    135 del self.args, self.kwds, self.func
    136 try:
--> 137     return next(self.gen)
    138 except StopIteration:
    139     raise RuntimeError("generator didn't yield") from None

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/site-packages/pyorbital/tlefile.py:350, in _uri_open(uri, open_func)
    348 @contextlib.contextmanager
    349 def _uri_open(uri, open_func):
--> 350     file_obj = open_func(uri, context=context)
    351     #file_obj = open_func(uri)
    352     try:

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:215, in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    213 else:
    214     opener = _opener
--> 215 return opener.open(url, data, timeout)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:521, in OpenerDirector.open(self, fullurl, data, timeout)
    519 for processor in self.process_response.get(protocol, []):
    520     meth = getattr(processor, meth_name)
--> 521     response = meth(req, response)
    523 return response

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:630, in HTTPErrorProcessor.http_response(self, request, response)
    627 # According to RFC 2616, "2xx" code indicates that the client's
    628 # request was successfully received, understood, and accepted.
    629 if not (200 <= code < 300):
--> 630     response = self.parent.error(
    631         'http', request, response, code, msg, hdrs)
    633 return response

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:559, in OpenerDirector.error(self, proto, *args)
    557 if http_err:
    558     args = (dict, 'default', 'http_error_default') + orig_args
--> 559     return self._call_chain(*args)

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:492, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args)
    490 for handler in handlers:
    491     func = getattr(handler, meth_name)
--> 492     result = func(*args)
    493     if result is not None:
    494         return result

File ~/conda_virtual/tobac-1.6.1/lib/python3.12/urllib/request.py:639, in HTTPDefaultErrorHandler.http_error_default(self, req, fp, code, msg, hdrs)
    638 def http_error_default(self, req, fp, code, msg, hdrs):
--> 639     raise HTTPError(req.full_url, code, msg, hdrs, fp)

HTTPError: HTTP Error 503: Service Unavailable

meteodave avatar Sep 22 '25 15:09 meteodave

celestrak.org is throttled, so trying to connect to many times can mean a temporary ban (a few hours). We also use spacetrack at our institute, but it requires a login

mraspaud avatar Sep 22 '25 15:09 mraspaud

celestrak.org links do not respond from multiple domains so they perhaps have an issue providing TLEs today. Thank you for the spacetrack tip.

meteodave avatar Sep 22 '25 19:09 meteodave