Skip to content

Improve error handling and documentation across the SDK#1047

Draft
gjmulhol wants to merge 19 commits intomainfrom
docs/improve-sdk-documentation
Draft

Improve error handling and documentation across the SDK#1047
gjmulhol wants to merge 19 commits intomainfrom
docs/improve-sdk-documentation

Conversation

@gjmulhol
Copy link
Copy Markdown

Summary

Comprehensive improvements to error handling and docstrings across the citrine-python SDK.

Error Handling

  • Bug fixes: copy-paste bug in migrate_deprecated_argument, bare ExceptionTypeError, NotFound.build() lambda signature, "Cant" typo
  • Hint support: CitrineException base class now accepts an optional hint kwarg appended to error messages as actionable guidance
  • ServerError: new exception for 5xx responses with structured method, path, status_code, response_text, request_id attributes
  • Request ID: NonRetryableHttpException extracts x-request-id from response headers for support escalation
  • Default hints: NotFound, Unauthorized, BadRequest, Conflict each include actionable guidance
  • Silent swallowing fixes: replaced pass/logger.info with logger.debug/logger.warning in _session.py and pageable.py
  • Exception chaining: added from e to re-raises in collection.py and ingestion.py
  • Timeout messages: PollingTimeoutError and ConditionTimeoutError now explain server continues running and suggest increasing timeout
  • Validation/batch messages: batcher collision/dependency errors, collection uid=None, deserialization errors all improved with context and suggestions
  • Programmatic access: validation_errors property and has_failure() method on NonRetryableHttpException
  • Auth messages: UnauthorizedRefreshToken includes host URL for key regeneration

Documentation

  • Entry point: Citrine class with usage example, Raises section, property return types
  • Exceptions module: module docstring explaining retryable vs non-retryable hierarchy; all exception classes expanded
  • Scores/objectives/constraints: module docstrings, LI/EI/EV score semantics explained, constraint inclusive/exclusive notation
  • Design execution: candidates(), hierarchical_candidates(), predict() all documented with Parameters/Returns
  • PredictRequest: all 5 constructor parameters documented
  • DesignCandidate/Variable: explain subtypes, primary_score semantics
  • Dimensions/descriptors: module docstrings, default bound behavior, subtype listings
  • Data sources: module docstring, GemTableDataSource usage guidance
  • Collection base: all CRUD methods documented with Parameters/Returns/Raises
  • Feature effects: Shapley value semantics (positive/negative meaning), all attributes documented
  • Jobs/waiting: module docstring, JobSubmissionResponse attributes, ConditionTimeoutError guidance

Test plan

  • 23 new tests added (hint formatting, ServerError attributes, request_id extraction, validation_errors property, has_failure, default hints)
  • All 1365 tests pass (1342 original + 23 new)
  • Docstring-only changes verified with full test suite

🤖 Generated with Claude Code

Gregory Mulholland and others added 19 commits March 25, 2026 15:12
- Citrine class: add usage example, Raises section, explain
  each property's return type and available operations
- exceptions.py: add module-level docstring explaining the
  exception hierarchy (retryable vs non-retryable)
- All exception classes: expand one-line docstrings to explain
  what each exception means and what users should do about it
- NonRetryableHttpException: document all public attributes
- JobFailureError: document job_id and failure_reasons attrs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- scores.py: add module docstring explaining the three score
  types and when to use each (LI for multi-objective sequential,
  EI for single-objective sequential, EV for no-baseline)
- LIScore: explain "simultaneously exceeding" semantics
- EIScore: clarify single-objective limit and magnitude focus
- EVScore: explain equal-weight summing and how to influence
  relative weighting
- Objectives: explain what descriptor_key must match
- ScalarRangeConstraint: clarify inclusive/exclusive semantics
  and explain that violated constraints yield a zero score

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DesignExecution: explain the execution lifecycle and link to
  Score, candidates(), and hierarchical_candidates()
- candidates(): document return ordering (by score), per_page,
  and return type with full Parameters/Returns sections
- hierarchical_candidates(): explain the difference from
  candidates() (includes process history and sub-materials)
- predict(): document PredictRequest input and DesignCandidate
  return with full Parameters/Returns sections
- PredictRequest: add full Parameters section documenting all
  5 constructor arguments
- DesignCandidate: explain primary_score semantics
- DesignVariable: list all subtypes and when each is used
- DesignCandidateComment: add missing class docstring

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- dimensions.py: add module docstring explaining role in design
  spaces; list all Dimension subtypes in base class
- ContinuousDimension/IntegerDimension: explain default bound
  behavior (falls back to descriptor bounds)
- EnumeratedDimension: explain use cases and value parsing
- descriptors.py: add module docstring explaining role in the
  platform; list all 6 Descriptor subtypes in base class

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Module docstring: explain what data sources are and that
  GemTableDataSource is the most common type
- DataSource base: list all 4 subtypes with brief descriptions
- GemTableDataSource: explain what GEM Tables are and how to
  find table IDs and versions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Collection class: explain available CRUD operations and that
  concrete subclasses may add more
- get(): add Parameters, Returns, and Raises sections
- register(): add Parameters, Returns, and Raises sections
- update(): add Parameters and Returns sections
- delete(): add Parameters and Returns sections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All four Shapley classes had minimal (1-line) docstrings with
no explanation of what Shapley values are or how to interpret
them. Now:

- Module docstring: explain Shapley values, positive/negative
  semantics, and the class hierarchy
- ShapleyMaterial: document material_id and value attributes
- ShapleyFeature: document feature name and materials list
- ShapleyOutput: document output name and features list
- FeatureEffects: document all attributes including status,
  failure_reason, and link to as_dict convenience property

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- JobSubmissionResponse: explain what it is and how to use
  job_id for status polling
- waiting.py: add module docstring explaining polling utilities
- ConditionTimeoutError: explain that the server continues
  running independently and suggest increasing timeout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix copy-paste bug in migrate_deprecated_argument where both
  sides of the error message showed new_arg_name instead of
  old_arg_name
- Replace bare Exception with TypeError in validate_type for
  clearer error semantics
- Fix NotFound.build() lambda signature (lambda self: -> lambda:)
  that silently prevented api_error from being populated
- Fix "Cant" typo in WorkflowNotReadyException message

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract x-request-id or x-correlation-id from response headers
and store as request_id attribute. Also store the HTTP method.
Both are included in the error message for easier debugging and
support escalation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace silent ValueError pass with logger.debug for 401 JSON
  parsing (was completely invisible to users/developers)
- Downgrade AttributeError from logger.error to logger.debug
  (this is an expected case, not a true error)
- Upgrade JSONDecodeError in _extract_response_json from
  logger.info to logger.warning (returning empty dict silently
  can cause confusing downstream failures)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add 'from e' to exception re-raises in collection.py and
  ingestion.py so the original cause is preserved in tracebacks
- Replace 'raise e' with bare 'raise' to preserve traceback
  context when re-raising the same exception
- Add logger.warning in pageable.py when response data is not a
  dict (was silently swallowed, hiding type errors)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PollingTimeoutError now includes job_id, timeout duration,
  and explains the server-side job continues running
- ConditionTimeoutError now explains client timeout is
  independent of server-side work and suggests increasing
  timeout or polling manually

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Batcher collision error now explains what a collision means
  and suggests ensuring unique UIDs
- Batcher dependency error now shows count and first 10
  dependency names, suggests increasing batch_size
- Collection uid=None error now explains the likely cause
  (unregistered object) and suggests calling .register()
- Deserialization errors truncate data dicts to 200 chars and
  value reprs to 100 chars to avoid overwhelming output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add validation_errors property and has_failure() method to
NonRetryableHttpException for structured access to API
validation errors without parsing message strings.

Enables patterns like:
  except BadRequest as e:
      for ve in e.validation_errors:
          print(f"Field '{ve.property}': {ve.failure_message}")
      if e.has_failure("missing_required_field"):
          # handle specifically

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add optional 'hint' keyword argument to CitrineException that
appends actionable guidance to error messages. When present,
__str__ outputs the base message followed by "\n\nHint: <hint>".

This is the foundation for adding actionable hints to all
exception subclasses in subsequent PRs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace bare CitrineException(response.text) for 5xx errors
with a structured ServerError that includes method, path,
status code, response text (truncated to 500 chars), request
ID, and an actionable hint about contacting support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each HTTP exception subclass now has a _default_hint providing
actionable guidance:
- NotFound: verify resource UID
- Unauthorized: check API key validity
- BadRequest: check validation errors
- WorkflowConflictException: wait and retry

Hints are wired through NonRetryableHttpException.__init__ to
the base CitrineException hint mechanism.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UnauthorizedRefreshToken now includes a descriptive message and
hint directing users to regenerate their API key at the
platform URL or set the CITRINE_API_KEY environment variable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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