Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request; API access to the USPTO office rejection API; https://developer.uspto.gov/api-catalog/uspto-office-action-rejection-api #113

Open
KennethThompson opened this issue Sep 12, 2023 · 9 comments

Comments

@KennethThompson
Copy link
Contributor

Would it be possible to add in support for queries to the USPTO Office Action Rejection API?

https://developer.uspto.gov/api-catalog/uspto-office-action-rejection-api

Thanks very much!
Ken

@parkerhancock
Copy link
Owner

Hey! I'm working on a bunch of updates to the library. I'll add this one on.

The big new change is async support. The master branch now has async-capable objects for all of the USPTO api's. For iteration, you can now iterate through objects using async for and there are async versions of all methods that trigger I/O that start with an 'a' (i.e. .aget, .alen, .ato_list, .ato_pandas).

Right now, all those sync/async methods are duplicated (one version of each). Long term, I want the library to be async-native, with synchronous wrappers to support the current sync api's. That should also cut the code base in half :)

@parkerhancock
Copy link
Owner

Initial implementation is in PR #117. Would love your help answering some of the questions in that PR about how the API works.

Thanks!

@KennethThompson
Copy link
Contributor Author

Hey! I'm working on a bunch of updates to the library. I'll add this one on.

The big new change is async support. The master branch now has async-capable objects for all of the USPTO api's. For iteration, you can now iterate through objects using async for and there are async versions of all methods that trigger I/O that start with an 'a' (i.e. .aget, .alen, .ato_list, .ato_pandas).

Right now, all those sync/async methods are duplicated (one version of each). Long term, I want the library to be async-native, with synchronous wrappers to support the current sync api's. That should also cut the code base in half :)

My apologies for my lengthy delay in responding. I bit off more than I could chew on another open source project I contribute to, and then Christmas came.

I was finally able to pull down the latest build and do some testings. Thank you for your hard workj!

I see the async change has introduced some new complexities. I noticed that globalDossierApplication.objects.get() is using the async features, and my attempt to call the function is triggering an exception I can't seem to resolve. Steps to reproduce below + exception output. This is using pytest for reference

  1. Define a pytest unit test as follows:
def test_oa_():
    error_log = []
    try:
        app = GlobalDossierApplication.objects.get("16865867", type="application", office="US")
        found = False
        for oa in app.office_actions:
            print('ok!')
    except Exception as ex:
        just_the_string = traceback.format_exc()
        reason = f'[OAParserAgent] ERROR: {str(ex)}, Details = {just_the_string}.'
        print( reason)
  1. Execute test and observe the following exception:
During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/workspaces/tests/test_base.py", line 297, in test_oa_
    for oa in app.office_actions:
              ^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/patent_client/uspto/global_dossier/model.py", line 81, in office_actions
    .objects.get(self.country_code, self.app_num, self.kind_code)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/patent_client/util/manager.py", line 159, in get
    return run_sync(self.aget(*args, **kwargs))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/patent_client/util/asyncio_util.py", line 12, in run_sync
    return loop.run_until_complete(coroutine)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/nest_asyncio.py", line 99, in run_until_complete
    return f.result()
           ^^^^^^^^^^
  File "/usr/local/lib/python3.12/asyncio/futures.py", line 203, in result
    raise self._exception.with_traceback(self._exception_tb)
  File "/usr/local/lib/python3.12/asyncio/tasks.py", line 314, in __step_run_and_handle_result
    result = coro.send(None)
             ^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/patent_client/uspto/global_dossier/manager.py", line 41, in aget
    return await global_dossier_api.get_doc_list(country, doc_number, kind_code)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/patent_client/uspto/global_dossier/api.py", line 16, in get_doc_list
    response = await session.get(url)
               ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1757, in get
    return await self.request(
           ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1530, in request
    return await self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1617, in send
    response = await self._send_handling_auth(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1645, in _send_handling_auth
    response = await self._send_handling_redirects(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1682, in _send_handling_redirects
    response = await self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1719, in _send_single_request
    response = await transport.handle_async_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/hishel/_async/_transports.py", line 201, in handle_async_request
    response = await self._transport.handle_async_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_transports/default.py", line 366, in handle_async_request
    resp = await self._pool.handle_async_request(req)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/connection_pool.py", line 268, in handle_async_request
    raise exc
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/connection_pool.py", line 251, in handle_async_request
    response = await connection.handle_async_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/connection.py", line 103, in handle_async_request
    return await self._connection.handle_async_request(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/http2.py", line 185, in handle_async_request
    raise exc
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/http2.py", line 148, in handle_async_request
    status, headers = await self._receive_response(
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/http2.py", line 292, in _receive_response
    event = await self._receive_stream_event(request, stream_id)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/http2.py", line 333, in _receive_stream_event
    await self._receive_events(request, stream_id)
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/http2.py", line 361, in _receive_events
    events = await self._read_incoming_data(request)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/http2.py", line 452, in _read_incoming_data
    raise exc
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/http2.py", line 438, in _read_incoming_data
    data = await self._network_stream.read(self.READ_NUM_BYTES, timeout)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_backends/anyio.py", line 34, in read
    return await self._stream.receive(max_bytes=max_bytes)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/anyio/streams/tls.py", line 205, in receive
    data = await self._call_sslobject_method(self._ssl_object.read, max_bytes)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/anyio/streams/tls.py", line 147, in _call_sslobject_method
    data = await self.transport_stream.receive()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 1123, in receive
    await self._protocol.read_event.wait()
  File "/usr/local/lib/python3.12/asyncio/locks.py", line 209, in wait
    fut = self._get_loop().create_future()
          ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/asyncio/mixins.py", line 20, in _get_loop
    raise RuntimeError(f'{self!r} is bound to a different event loop')
RuntimeError: <asyncio.locks.Event object at 0xffff78162d20 [unset]> is bound to a different event loop

@parkerhancock
Copy link
Owner

Hey! Thanks for the note. This is actually a bug I've been trying to solve. I've tried to incorporate by essentially rewriting all the network access code one time as async, and then creating one-time event loops to run the code synchronously if async isn't being used. But that's causing issues like this, where if you try to access a property that requires another network call, it creates event loop chaos.

I think the solution here is ultimately going to be to completely factor out the actual network request from all other code so I can switch out a a sync/async client right at the point of hitting the wire. I'm looking at that this afternoon.

Thanks!

@KennethThompson
Copy link
Contributor Author

Hey! Thanks for the note. This is actually a bug I've been trying to solve. I've tried to incorporate by essentially rewriting all the network access code one time as async, and then creating one-time event loops to run the code synchronously if async isn't being used. But that's causing issues like this, where if you try to access a property that requires another network call, it creates event loop chaos.

I think the solution here is ultimately going to be to completely factor out the actual network request from all other code so I can switch out a a sync/async client right at the point of hitting the wire. I'm looking at that this afternoon.

Thanks!

You are well beyond my knowledge as I have not implemented async io in python before (just C++). However, one solution I saw was at this link: https://stackoverflow.com/questions/76460059/python-unknown-error-in-http-implementation. The solution recommendation was to use native asyncio methods rather than mixing threads with asyncio- not sure if this is duplicative of what you already found.

@parkerhancock
Copy link
Owner

Can you confirm which version of patent_client you're using? I've been using nest_asyncio in order to make this all work, and I want to make sure I know what version you're using.

@parkerhancock
Copy link
Owner

Also #133 is now a blocker to addressing this. Once I get the library all back up to snuff, I'll rework this in as well.

@kt1135
Copy link

kt1135 commented Jan 8, 2024

Also #133 is now a blocker to addressing this. Once I get the library all back up to snuff, I'll rework this in as well.

The initial request is returning results just fine:

app = GlobalDossierApplication.objects.get(docket.AppNumber, type="application", office="US")

However, once I go to download an office action document from the results, the ". . . is bound to a different event loop" exception occurs. Did you update the download code as well?

Full code I am using:

    error_log = []
    try:

        app = GlobalDossierApplication.objects.get(docket.AppNumber, type="application", office="US")
        #b, app_peds = queryPedsByAppSerNo(docket.AppNumber)
        
        found = False
        #found_oa_id = None
        for oa in app.office_actions:
            if oa.date.year == oa_date.year and oa.date.month == oa_date.month and oa.date.day == oa_date.day:
                found = True
                retryCount = 5
                while(True):
                    try:
                        tt = oa.download(filename=f'{filename}', path=base_output_path)
                        break
                    except Exception as ex:# HTTPError as he:
                        #raise ex
                        just_the_string = traceback.format_exc()
                        print( f'Error requesting download. Retries remaining = {retryCount-1}. Error = {str(ex)}: {just_the_string}')
                        print(just_the_string)
                        error_log.append(str(ex))
                        retryCount-=1
                        if( retryCount == 0 ):
                            raise Exception("Error. Retry count exceeded!")
                        time.sleep(60)

        if found == False:
            return False, f'Unable to locate action in filewrapper for download. Total OA count per USPTO: {len(app.office_actions)}', error_log

        return True, '', None

    except Exception as ex:
        just_the_string = traceback.format_exc()
        reason = f'[OAParserAgent] ERROR: {str(ex)}, Details = {just_the_string}.'
        print( reason)
        return False, reason, error_log

@kt1135
Copy link

kt1135 commented Jan 8, 2024

This is my corporate github account name- forgot I was still logged in! This is Ken Thompson, although I am sure you already inferred that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants