Skip to content

Fix/application number slash - #319

Merged
dpieski merged 30 commits into
mainfrom
fix/application-number-slash
Nov 11, 2026
Merged

dpieski merged 30 commits into
mainfrom
fix/application-number-slash

Conversation

@dpieski

@dpieski dpieski commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Description

Fixes application-number handling across the library, and moves every client's search-filter query building into one shared module.

  1. sanitize_application_number now returns series-code input as 8 digits (18/511,739 → 18511739). It used to keep the slash, so get_application_by_number and the other per-application PatentDataClient methods requested a path the API rejects with HTTP 403.
  2. The application-number search filters on all nine search clients (application_number_q, application_number_text_q, patent_application_number_q, and the PTAB interference party filters) now go through the same cleanup. Before, 17/765301 matched nothing in any of them.
  3. A new module, pyUSPTO.clients.query, holds the functions that build each query clause (term, phrase, contains, any_of, date_range, application_number, either, all_of, and others). Every search method now builds its q or criteria value with them. That fixes the inconsistent quoting:
    1. Multi-word text filters (names, titles, descriptions) are quoted in every client.
    2. Double quotes and backslashes inside a quoted value are escaped, including in PTAB trials document_type_description_q.
    3. A code filter that contains an expression is wrapped in parentheses, so the field applies to every term. The office action and enriched citation category filters are code filters, so office_action_category_q="CTNF OR CTFR" works.
    4. An empty string adds no clause. An empty list (customer_number_q=[]) raises ValueError instead of running the search unfiltered.
  4. PetitionDecisionsClient.get_decision_by_id, BulkDataClient.get_product_by_id, and the BulkDataClient.download_file fallback percent-encode IDs and file names in the URL path. Non-string IDs such as uuid.UUID still work.
  5. BulkDataClient.download_file(file_name=...) uses file_name only as the local save name. It used to request that name from the server.
  6. A 403 response with Missing Authentication Token now says the requested path may not exist. examples/error_handling_example.py says so too.
  7. CHANGELOG: adds the missing [0.6.1] section, dates [0.6.0], and documents all of the above under [Unreleased].

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (no functional change)
  • Documentation update
  • Chore / dependency update

Testing

  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have tested this manually (if applicable)

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

Related Issues

N/A

Additional Notes

  1. Live results behind the fixes. Each one has a live integration test that failed before the fix and passes now:
    1. appellant_name_q="Block, Inc." returned 4303 PTAB appeal decisions from 2025 instead of 1, because Inc. was searched as free text.
    2. patent_number_q="10000000 OR 10000001" returned 3 applications instead of 2.
    3. inventor_name_q='Smith "John' returned nothing.
    4. NN/NNNNNN application numbers matched nothing in any search client.
    5. office_action_category_q="CTNF OR CTFR" for application 15061308 returned 0 citations instead of 25.
  2. One query output changes without changing results. PTAB trials and PTAB interferences no longer quote single-word text values. Live, trialStatusCategory:Instituted and trialStatusCategory:"Instituted" both return 220 proceedings.
  3. Known limit: a wildcard text value that also contains a hyphen or slash (Garcia-Lopez*) is quoted, so the wildcard is searched as plain text. Live, "Garcia-Lopez*" returns the same 119 applications as "Garcia-Lopez". Both the unquoted and backslash-escaped forms return HTTP 404, so there is no form that makes the wildcard expand. The docstring and CHANGELOG say so.
  4. This is not a breaking change. No parameter was added, removed, or retyped. sanitize_application_number returns a different string for series-code input, but the old form never worked in a lookup. An empty list filter now raises, but it used to send a malformed field:() clause.
  5. Verification:
    1. 1100 unit tests pass, and ruff and mypy are clean. CI passes on Python 3.11 through 3.15, and the Read the Docs build passes.
    2. All live integration tests except bulk data passed on a full run. After the last six fixes, the live suites for every client they touch passed again.
    3. Bulk data live tests were not run. The BulkDataClient changes are covered by unit tests.
  6. test_ptab_appeals_integration.py no longer wraps API calls in try/except. The same pattern is still in the patent data, petition, PTAB interferences, and PTAB trials live test files, which is follow-up work.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread src/pyUSPTO/clients/query.py Resolved
Comment thread src/pyUSPTO/clients/query.py Resolved
Comment thread src/pyUSPTO/clients/base.py Resolved
Comment thread src/pyUSPTO/clients/bulk_data.py Resolved
Comment thread src/pyUSPTO/clients/query.py Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped — your organization's remaining monthly code review budget is too low to start another review, so this review was not started.

An organization admin can view or raise the monthly spending cap at claude.ai/admin-settings/usage, on the Claude Code Review row under Services. The cap resets at the start of the next billing period.

Once the cap resets or is raised, someone with write access to this repository can comment @claude review on this pull request to trigger a review.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

application_number(
"patentApplicationNumber", patent_application_number_q
),
contains("legalSectionCode", legal_section_code_q),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟣 pre-existing, not blocking: pre-existing: Callers who follow the new docstring and pass legal_section_code_q="101 OR 103" get a broken criteria string instead of an OR filter. contains without quoted sends the value raw as legalSectionCode:*101 OR 103*, so the field applies only to *101 and the rest is searched as free text. Fix: wrap any value that contains whitespace in parentheses, in both unquoted contains and the pass-through branches of publication_number, which covers the 3 sites listed. Same pattern at 3 sites (oa_citations.py:159, query.py:185, query.py:188).
A small fix can ride a push you are already making; otherwise a short reply is enough.

Why this was flagged

The new docstring at oa_citations.py:90-93 says every convenience filter is a code filter and that a value containing spaces is wrapped in parentheses. legal_section_code_q goes through contains("legalSectionCode", ...) at oa_citations.py:159. With quoted=False that path returns f"{field}:*{value}*" (query.py:230) and never checks for whitespace. publication_number behaves the same way: a value with *, or one that _PUBLICATION_NUMBER_RE does not match (for example "20210138233 OR 20210138234"), is returned as f"{field}:{text}" at query.py:185/188 with no parentheses. In both cases only the first term is tied to the field, so search_citations/search_applications return wrong or unrelated records. The base branch built the same strings, so this is pre-existing. The PR does say code filters with expressions now get parentheses in every search client, and these two builders do not.

Verification: Pre-existing. The new docstring at oa_citations.py:90-93 says a value that contains spaces "is wrapped in parentheses, so the field applies to every term." But oa_citations.py:159 calls contains("legalSectionCode", legal_section_code_q), whose unquoted branch is return f"{field}:*{value}*", with no whitespace check. So the criteria becomes legalSectionCode:*101 OR 103*. The base built exactly the same string.

@dpieski
dpieski merged commit 836d520 into main Nov 11, 2026
11 checks passed
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

Successfully merging this pull request may close these issues.

1 participant