diff --git a/.github/RELEASE_LABELS_GUIDE.md b/.github/RELEASE_LABELS_GUIDE.md new file mode 100644 index 00000000..7f700499 --- /dev/null +++ b/.github/RELEASE_LABELS_GUIDE.md @@ -0,0 +1,218 @@ +# Release Labels Quick Reference + +This is a quick reference guide for maintainers on using release labels for automated versioning. + +## âš ī¸ IMPORTANT: Default Behavior + +**If you merge a PR without a release label, NO RELEASE will be created.** + +This is intentional and safe by default - you must explicitly label PRs to trigger a release. + +## When Reviewing PRs + +Before merging any PR to `master`, decide if it should trigger a release and add the appropriate label: + +## Label Reference + +| Label | Color | Effect | Current → New | Use Case | +|-------|-------|--------|---------------|----------| +| **release:patch** | đŸŸĸ Green | Patch bump | 2.1.5 → 2.1.6 | Bug fixes, minor improvements | +| **release:minor** | 🟡 Yellow | Minor bump | 2.1.5 → 2.2.0 | New features, enhancements, new functionality | +| **release:major** | 🔴 Red | Major bump | 2.1.5 → 3.0.0 | Breaking changes, major API changes, removed features | +| **skip-release** (or no label) | âšĒ Gray | No release | 2.1.5 → 2.1.5 | Docs only, CI changes, tests, or changes not ready to release | + +**Note:** `skip-release` label is optional - if you don't add any label, the PR will be skipped for release automatically. + +## Decision Tree + +``` +Should this PR trigger a release? +│ +├─ NO → Don't add a label (or use 'skip-release') +│ Examples: +│ - Docs only changes +│ - README updates +│ - CI/workflow changes +│ - Tests only +│ - Work in progress you want to merge but not release yet +│ +└─ YES → Does it break existing code? + │ + ├─ YES → Use 'release:major' + │ Examples: + │ - Breaking API changes + │ - Removed features + │ - Changed method signatures + │ + └─ NO → Does it add new features? + │ + ├─ YES → Use 'release:minor' + │ Examples: + │ - New methods + │ - New functionality + │ - New API endpoints + │ + └─ NO → Use 'release:patch' + Examples: + - Bug fixes + - Performance improvements + - Minor enhancements +``` + +## Examples + +### release:patch +- Fix bug in `CubeService.get_dimension()` +- Improve error message +- Update dependency version +- Performance optimization +- Fix typo in code + +### release:minor +- Add new method to `ChoreService` +- Add support for new TM1 REST API endpoint +- Add optional parameter to existing method (backwards compatible) +- New utility function + +### release:major +- Remove deprecated method +- Change method signature (breaking) +- Rename class or module +- Change default behavior that breaks existing code +- Update minimum Python version + +### No label (or skip-release) +- Update README +- Fix documentation typo +- Update GitHub Actions workflow +- Add or update tests (no code changes) +- Add code comments +- Refactoring that you want to accumulate before releasing +- Any change you're not ready to release yet + +## Default Behavior + +If **NO label** is added → **NO RELEASE** (safe default) + +To trigger a release, you **MUST** add one of: `release:patch`, `release:minor`, or `release:major` + +## Priority + +If multiple PRs are merged in one day with different labels, the **highest priority label wins**: + +1. **release:major** (highest priority) +2. **release:minor** +3. **release:patch** (lowest priority) + +Example: +``` +Monday: + - PR #101: Docs update (no label) → skip + - PR #102: New feature (release:minor) → minor + - PR #103: Bug fix (release:patch) → patch + + Result: Next release will be MINOR (2.1.5 → 2.2.0) + (all 3 PRs included in changelog) + +Tuesday: + - PR #104: Test update (no label) → skip + - PR #105: CI fix (no label) → skip + + Result: NO RELEASE (no release labels found) +``` + +## Common Mistakes + +❌ **DON'T**: +- Forget to add a release label when you want a release (will be skipped!) +- Use `release:minor` for bug fixes +- Add a release label to docs-only changes +- Mix breaking changes with features in same PR + +✅ **DO**: +- Add appropriate release label before merging (if you want a release) +- Leave without label for docs, tests, CI, or work-in-progress +- Split breaking changes into separate PRs when possible +- Review the entire set of changes since last release +- Remember: **no label = no release** (safe default) + +## Workflow Timeline + +### Scenario 1: Release Day (PRs with release labels) + +``` +┌─────────────────────────────────────────────────────────┐ +│ Throughout the Day │ +│ ─────────────────────────────────────────────────────── │ +│ 10:00 AM Merge PR #123 (release:patch) ✅ │ +│ 2:00 PM Merge PR #124 (release:minor) ✅ │ +│ 5:00 PM Merge PR #125 (no label - docs only) │ +└─────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────┐ +│ 4:00 AM CET (Next Day) - Automated Workflow │ +│ ─────────────────────────────────────────────────────── │ +│ 1. Check for new commits → Found 3 PRs │ +│ 2. Determine version → MINOR (highest label found) │ +│ 3. Run full tests → 2-3 hours │ +│ 4. Tests PASS → Continue │ +│ 5. Bump version → 2.1.5 → 2.2.0 │ +│ 6. Create release → GitHub Release + changelog │ +│ 7. Publish → PyPI │ +└─────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────┐ +│ 7:00 AM CET │ +│ ─────────────────────────────────────────────────────── │ +│ ✅ Version 2.2.0 available on PyPI │ +│ Users can: pip install --upgrade TM1py │ +└─────────────────────────────────────────────────────────┘ +``` + +### Scenario 2: No Release (no release labels) + +``` +┌─────────────────────────────────────────────────────────┐ +│ Throughout the Day │ +│ ─────────────────────────────────────────────────────── │ +│ 10:00 AM Merge PR #126 (no label - tests) │ +│ 2:00 PM Merge PR #127 (no label - CI fix) │ +│ 5:00 PM Merge PR #128 (skip-release - docs) │ +└─────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────┐ +│ 4:00 AM CET (Next Day) - Automated Workflow │ +│ ─────────────────────────────────────────────────────── │ +│ 1. Check for new commits → Found 3 PRs │ +│ 2. Check for release labels → NONE found │ +│ 3. Skip release â­ī¸ │ +│ 4. Workflow completes (no version bump, no publish) │ +└─────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────┐ +│ 7:00 AM CET │ +│ ─────────────────────────────────────────────────────── │ +│ â„šī¸ No release created (version remains 2.2.0) │ +│ Changes merged to master but not published to PyPI │ +└─────────────────────────────────────────────────────────┘ +``` + +## Checking Labels via GitHub CLI + +```bash +# View labels on a PR +gh pr view 123 --json labels + +# Add label to PR +gh pr edit 123 --add-label "release:minor" + +# Remove label from PR +gh pr edit 123 --remove-label "release:patch" +``` + +## Questions? + +See the full documentation: +- [CONTRIBUTING.md](../../CONTRIBUTING.md) +- [RELEASE_SETUP_GUIDE.md](../../RELEASE_SETUP_GUIDE.md) +- [RELEASE_AUTOMATION_PROPOSAL.md](../../RELEASE_AUTOMATION_PROPOSAL.md) diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml new file mode 100644 index 00000000..761dbab8 --- /dev/null +++ b/.github/workflows/nightly-release.yml @@ -0,0 +1,353 @@ +name: Nightly Release + +on: + schedule: + # Run at 4 AM CET (3 AM UTC in winter, 2 AM UTC in summer) + # Using 2 AM UTC to be safe for CET/CEST transitions + - cron: '0 2 * * *' + workflow_dispatch: # Allow manual trigger for testing + +permissions: + contents: write + pull-requests: read + +jobs: + check-for-changes: + runs-on: ubuntu-latest + outputs: + has_changes: ${{ steps.check.outputs.has_changes }} + last_tag: ${{ steps.check.outputs.last_tag }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for proper comparison + + - name: Check for commits since last release + id: check + run: | + # Get the last release tag + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + echo "last_tag=$LAST_TAG" >> $GITHUB_OUTPUT + + if [ -z "$LAST_TAG" ]; then + echo "No previous tags found, will create first release" + echo "has_changes=true" >> $GITHUB_OUTPUT + exit 0 + fi + + # Check if there are commits since last tag + COMMITS_SINCE=$(git rev-list ${LAST_TAG}..HEAD --count) + echo "Commits since $LAST_TAG: $COMMITS_SINCE" + + if [ "$COMMITS_SINCE" -gt 0 ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "✅ Found $COMMITS_SINCE new commits since last release" + else + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "â­ī¸ No new commits since last release, skipping" + fi + + run-tests: + needs: check-for-changes + if: needs.check-for-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + strategy: + matrix: + environment: ["tm1-12", "tm1-11-cloud"] + environment: ${{ matrix.environment }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + pip install -e .[pandas,dev] + + - name: Retrieve TM1 Connection Details + run: echo "Retrieving TM1 connection details" + env: + TM1_CONNECTION: ${{ vars.TM1_CONNECTION }} + TM1_CONNECTION_SECRET: ${{ secrets.TM1_CONNECTION_SECRET }} + + - name: Generate config.ini + run: | + python Tests/resources/generate_config.py + env: + TM1_CONNECTION: ${{ vars.TM1_CONNECTION }} + TM1_CONNECTION_SECRET: ${{ secrets.TM1_CONNECTION_SECRET }} + + - name: Run integration tests + run: pytest Tests/ + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ matrix.environment }} + path: Tests/test-reports/ + retention-days: 7 + + determine-version: + needs: [check-for-changes, run-tests] + runs-on: ubuntu-latest + outputs: + new_version: ${{ steps.version.outputs.new_version }} + version_type: ${{ steps.version.outputs.version_type }} + changelog: ${{ steps.changelog.outputs.changelog }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine version bump type + id: version + env: + GH_TOKEN: ${{ github.token }} + run: | + LAST_TAG="${{ needs.check-for-changes.outputs.last_tag }}" + + # Parse current version (default to 2.2.0 if no tags) + if [ -z "$LAST_TAG" ]; then + CURRENT_VERSION="2.2.0" + else + CURRENT_VERSION="${LAST_TAG#v}" # Remove 'v' prefix if present + fi + + echo "Current version: $CURRENT_VERSION" + + # Split version into parts + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + + # Get all merged PRs since last tag + if [ -z "$LAST_TAG" ]; then + COMMITS=$(git log --format="%H" HEAD) + else + COMMITS=$(git log --format="%H" ${LAST_TAG}..HEAD) + fi + + # Determine version bump type by checking PR labels + VERSION_TYPE="skip" # Default to skip (no release unless explicitly labeled) + FOUND_RELEASE_LABEL=false + + for COMMIT in $COMMITS; do + # Get PR number from commit message + PR_NUM=$(git log --format=%s -n 1 $COMMIT | grep -oP '#\K[0-9]+' | head -1) + + if [ -n "$PR_NUM" ]; then + echo "Checking PR #$PR_NUM for release labels..." + + # Get PR labels using GitHub CLI + LABELS=$(gh pr view $PR_NUM --json labels --jq '.labels[].name' 2>/dev/null || echo "") + + if echo "$LABELS" | grep -q "release:major"; then + VERSION_TYPE="major" + FOUND_RELEASE_LABEL=true + echo "Found release:major label in PR #$PR_NUM" + break # Major takes precedence + elif echo "$LABELS" | grep -q "release:minor"; then + VERSION_TYPE="minor" + FOUND_RELEASE_LABEL=true + echo "Found release:minor label in PR #$PR_NUM" + # Don't break, continue checking for major + elif echo "$LABELS" | grep -q "release:patch"; then + # Only set to patch if we haven't found minor yet + if [ "$VERSION_TYPE" != "minor" ]; then + VERSION_TYPE="patch" + FOUND_RELEASE_LABEL=true + fi + echo "Found release:patch label in PR #$PR_NUM" + elif echo "$LABELS" | grep -q "skip-release"; then + echo "Found skip-release label in PR #$PR_NUM" + # Explicit skip, don't change VERSION_TYPE + fi + fi + done + + # If no release labels found, skip the release + if [ "$FOUND_RELEASE_LABEL" = false ]; then + echo "â­ī¸ No release labels found on any merged PRs, skipping release" + VERSION_TYPE="skip" + fi + + # Calculate new version + case $VERSION_TYPE in + major) + NEW_VERSION="$((MAJOR + 1)).0.0" + ;; + minor) + NEW_VERSION="${MAJOR}.$((MINOR + 1)).0" + ;; + patch) + NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" + ;; + skip) + NEW_VERSION="" # No version bump + echo "â­ī¸ Skipping release - no release labels found" + ;; + esac + + echo "Version bump type: $VERSION_TYPE" + echo "New version: $NEW_VERSION" + echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "version_type=$VERSION_TYPE" >> $GITHUB_OUTPUT + + - name: Generate changelog + id: changelog + env: + GH_TOKEN: ${{ github.token }} + run: | + LAST_TAG="${{ needs.check-for-changes.outputs.last_tag }}" + + echo "Generating changelog..." + + # Get commits since last tag + if [ -z "$LAST_TAG" ]; then + COMMITS=$(git log --format="- %s (%h)" HEAD) + else + COMMITS=$(git log --format="- %s (%h)" ${LAST_TAG}..HEAD) + fi + + # Create changelog + CHANGELOG="## What's Changed"$'\n\n'"$COMMITS"$'\n\n'"**Full Changelog**: https://github.com/${{ github.repository }}/compare/${LAST_TAG}...${{ steps.version.outputs.new_version }}" + + # Save to output (handle multiline) + { + echo 'changelog<> $GITHUB_OUTPUT + + create-release: + needs: [determine-version] + if: needs.determine-version.outputs.version_type != 'skip' && needs.determine-version.outputs.new_version != '' + runs-on: ubuntu-latest + environment: release-approval # Requires manual approval + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Update version in pyproject.toml + run: | + NEW_VERSION="${{ needs.determine-version.outputs.new_version }}" + sed -i "s/^version = .*/version = \"$NEW_VERSION\"/" pyproject.toml + sed -i "s|tarball/.*\"|tarball/$NEW_VERSION\"|" pyproject.toml + + - name: Commit version bump + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add pyproject.toml + git commit -m "chore: bump version to ${{ needs.determine-version.outputs.new_version }}" + git push + + - name: Create and push tag + run: | + git tag ${{ needs.determine-version.outputs.new_version }} + git push origin ${{ needs.determine-version.outputs.new_version }} + + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ needs.determine-version.outputs.new_version }} + release_name: Release ${{ needs.determine-version.outputs.new_version }} + body: | + ${{ needs.determine-version.outputs.changelog }} + + --- + + 🤖 This release was automatically created by the nightly release workflow. + + Install via pip: + ```bash + pip install --upgrade TM1py + ``` + draft: false + prerelease: false + + publish-to-pypi: + needs: [determine-version, create-release] + runs-on: ubuntu-latest + environment: pypi-publish # Requires manual approval (configure in GitHub repo settings) + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ needs.determine-version.outputs.new_version }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install build tools + run: | + python -m pip install --upgrade pip + pip install build twine + + - name: Build package + run: python -m build + + - name: Check package + run: twine check dist/* + + - name: Publish to PyPI + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: twine upload dist/* + + - name: Success notification + run: | + echo "✅ Successfully published TM1py ${{ needs.determine-version.outputs.new_version }} to PyPI!" + echo "Users can now install it with: pip install --upgrade TM1py" + + notify-on-skip: + needs: [determine-version] + if: needs.determine-version.outputs.version_type == 'skip' + runs-on: ubuntu-latest + steps: + - name: Notify skip + run: | + echo "â­ī¸ Release skipped - no release labels found on merged PRs" + echo "" + echo "To trigger a release, add one of these labels to PRs before merging:" + echo " - release:patch (for bug fixes)" + echo " - release:minor (for new features)" + echo " - release:major (for breaking changes)" + echo "" + echo "Or use 'skip-release' label to explicitly skip a PR" + + notify-on-failure: + needs: [run-tests, determine-version, create-release, publish-to-pypi] + if: failure() + runs-on: ubuntu-latest + steps: + - name: Create issue on failure + uses: actions/github-script@v7 + with: + script: | + const issue = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '🚨 Nightly Release Failed', + body: `The nightly release workflow failed. Please check the [workflow run](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) for details.`, + labels: ['release', 'bug', 'automation'] + }); + console.log('Created issue:', issue.data.number); diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 00000000..23b97bc5 --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -0,0 +1,44 @@ +name: PR Validation + +on: + pull_request: + branches: [master] + types: [opened, synchronize, reopened] + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install black ruff + + - name: Run Black (formatting check) + run: black --check --diff . + + - name: Run Ruff (linting) + run: ruff check . + + # Placeholder for future unit tests + # Uncomment when unit tests are available + # - name: Run unit tests + # run: | + # pip install -e .[dev] + # pytest Tests/unit/ -v + + - name: Validation Summary + if: success() + run: | + echo "✅ All validation checks passed!" + echo "- Code formatting (Black): PASSED" + echo "- Linting (Ruff): PASSED" + # echo "- Unit tests: PASSED" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..dfa10fac --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,233 @@ +# Contributing to TM1Py + +Thank you for your interest in contributing to TM1Py! This document provides guidelines and information about the contribution process. + +## Table of Contents +- [Getting Started](#getting-started) +- [Development Setup](#development-setup) +- [Making Changes](#making-changes) +- [Pull Request Process](#pull-request-process) +- [Release Process](#release-process) +- [Code Style](#code-style) + +## Getting Started + +1. Fork the repository +2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/tm1py.git` +3. Add upstream remote: `git remote add upstream https://github.com/cubewise-code/tm1py.git` + +## Development Setup + +1. **Create a virtual environment:** + ```bash + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` + +2. **Install TM1Py in development mode:** + ```bash + pip install -e .[pandas,dev] + ``` + +3. **Install development tools:** + ```bash + pip install black ruff pytest + ``` + +## Making Changes + +1. **Create a branch:** + ```bash + git checkout -b your-feature-name + ``` + +2. **Make your changes** following the code style guidelines + +3. **Format your code:** + ```bash + black . + ruff check --fix . + ``` + +4. **Test your changes** (if you have access to a TM1 instance): + ```bash + pytest Tests/ + ``` + +## Pull Request Process + +### 1. Before Opening a PR + +- Ensure your code follows the style guidelines +- Run formatting tools (Black, Ruff) +- Update documentation if needed +- Write clear, descriptive commit messages + +### 2. Opening a PR + +- Push your branch to your fork +- Open a PR against the `master` branch +- Fill out the PR template (if available) +- Describe what your PR does and why + +**Note**: You don't need to follow any special commit message format! Maintainers will handle versioning via labels. + +### 3. PR Validation + +Your PR will automatically trigger validation checks: +- **Code formatting** (Black) +- **Linting** (Ruff) +- **Future**: Unit tests (when available) + +These checks must pass before your PR can be merged. + +### 4. Review Process + +- Maintainers will review your PR +- Address any feedback or requested changes +- Once approved, a maintainer will add the appropriate release label and merge + +## Release Process + +TM1Py uses **automated nightly releases** with semantic versioning. + +### For Contributors + +**You don't need to do anything special!** Just: +1. Create your PR +2. Wait for review +3. That's it! + +No need for special: +- Commit message formats +- Branch naming conventions +- Version bumping +- Release notes + +### For Maintainers + +**IMPORTANT**: Before merging a PR, you must add the appropriate label to control the version bump. + +| Label | Version Bump | When to Use | Example | +|-------|--------------|-------------|---------| +| `release:patch` | `2.1.5` → `2.1.6` | Bug fixes, small improvements | Fix pandas compatibility issue | +| `release:minor` | `2.1.5` → `2.2.0` | New features, enhancements | Add support for new TM1 REST API endpoint | +| `release:major` | `2.1.5` → `3.0.0` | Breaking changes, major updates | Remove deprecated methods, change API | +| `skip-release` (or no label) | No version bump | Docs, tests, CI changes, or changes you don't want to release yet | Update README, fix typo in docs | + +**Default behavior**: If no label is added, **NO release will be created**. This is a safe default to prevent accidental releases. + +**To create a release**: You must explicitly add `release:patch`, `release:minor`, or `release:major` label. + +### Release Timeline + +**Daily cycle:** +``` +Day 1: + 10:00 AM - PR #123 (bug fix, labeled 'release:patch') merged + 2:00 PM - PR #124 (feature, labeled 'release:minor') merged + 5:00 PM - PR #125 (docs update, no label) merged + + 4:00 AM (next day) - Nightly workflow starts: + - Runs full integration tests (2-3 hours) + - If tests pass: + → Creates release 2.2.0 (because of the minor label) + → Publishes to PyPI (includes all 3 PRs) + → Updates documentation + 7:00 AM - Users can: pip install --upgrade TM1py + +Day 2: + 11:00 AM - PR #126 (minor fix, no label) merged + 3:00 PM - PR #127 (test update, no label) merged + + 4:00 AM (next day) - Nightly workflow starts: + - No release labels found + - Skips release (no version bump, no PyPI publish) +``` + +### How It Works + +1. **Merge to master** → PR is merged after validation passes +2. **Nightly at 4 AM CET** → Automated workflow runs: + - Checks for new commits since last release + - Runs full integration test suite + - Determines version bump from PR labels + - Creates GitHub Release + - Publishes to PyPI +3. **Next morning** → New version available to users! + +## Code Style + +### Python Code Style + +- **Line length**: 120 characters (configured in Black) +- **Formatting**: Use Black for automatic formatting +- **Linting**: Use Ruff for import sorting and error detection +- **Target version**: Python 3.7+ + +### Running Code Style Tools + +```bash +# Format code +black . + +# Check imports and linting +ruff check . + +# Auto-fix linting issues +ruff check --fix . +``` + +### Import Organization + +Imports should be organized by Ruff/isort: +1. Standard library imports +2. Third-party imports +3. Local application imports + +Example: +```python +import os +from typing import List + +import requests +from requests.auth import HTTPBasicAuth + +from TM1py.Objects import Cube +from TM1py.Services import TM1Service +``` + +## Testing + +### Running Tests + +If you have access to TM1 instances: + +```bash +# Run all tests +pytest Tests/ + +# Run specific test file +pytest Tests/test_cube_service.py + +# Run with verbose output +pytest Tests/ -v +``` + +### Test Configuration + +Tests require TM1 connection configuration. See `Tests/resources/` for setup instructions. + +## Questions? + +- Check existing [Issues](https://github.com/cubewise-code/tm1py/issues) +- Review [Discussions](https://github.com/cubewise-code/tm1py/discussions) +- Read the [Documentation](https://tm1py.readthedocs.io/) + +## License + +By contributing, you agree that your contributions will be licensed under the MIT License. + +--- + +Thank you for contributing to TM1Py! 🎉 diff --git a/TM1py/__init__.py b/TM1py/__init__.py index 4448f539..2e945999 100644 --- a/TM1py/__init__.py +++ b/TM1py/__init__.py @@ -74,4 +74,10 @@ from TM1py.Services.ViewService import ViewService from TM1py.Utils import Utils -__version__ = "2.2" +# Version is managed in pyproject.toml +try: + from importlib.metadata import version + __version__ = version("TM1py") +except Exception: + # Fallback for development installations + __version__ = "2.2.0" diff --git a/pyproject.toml b/pyproject.toml index 784d2f33..b57bfdd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,53 @@ [build-system] -requires = ["setuptools", "wheel"] +requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" +[project] +name = "TM1py" +version = "2.2.0" +description = "A python module for TM1." +readme = "README.md" +license = {text = "MIT"} +authors = [ + {name = "Marius Wirtz", email = "MWirtz@cubewise.com"} +] +keywords = ["TM1", "IBM Cognos TM1", "Planning Analytics", "PA", "Cognos"] +classifiers = [ + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Natural Language :: English", +] +requires-python = ">=3.6" +dependencies = [ + "ijson", + "requests", + "pytz", + 'requests_negotiate_sspi; platform_system=="Windows"', + "mdxpy>=1.3.1", + "networkx", +] + +[project.optional-dependencies] +pandas = ["pandas"] +dev = [ + "pytest", + "pytest-xdist", + "python-dateutil", + "black", + "ruff", +] + +[project.urls] +Homepage = "https://github.com/cubewise-code/tm1py" +Download = "https://github.com/Cubewise-code/TM1py/tarball/2.2.0" + [tool.black] line-length = 120 target-version = ["py37"] diff --git a/setup.py b/setup.py index e1b38908..93029bb6 100644 --- a/setup.py +++ b/setup.py @@ -1,53 +1,7 @@ from setuptools import setup -SCHEDULE_VERSION = "2.2" -SCHEDULE_DOWNLOAD_URL = "https://github.com/Cubewise-code/TM1py/tarball/" + SCHEDULE_VERSION - -with open("README.md", "r") as f: - long_description = f.read() - +# All configuration is now in pyproject.toml +# This setup.py is kept for backwards compatibility setup( - name="TM1py", packages=["TM1py", "TM1py/Exceptions", "TM1py/Objects", "TM1py/Services", "TM1py/Utils"], - version=SCHEDULE_VERSION, - description="A python module for TM1.", - long_description=long_description, - long_description_content_type="text/markdown", - license="MIT", - author="Marius Wirtz", - author_email="MWirtz@cubewise.com", - url="https://github.com/cubewise-code/tm1py", - download_url=SCHEDULE_DOWNLOAD_URL, - keywords=["TM1", "IBM Cognos TM1", "Planning Analytics", "PA", "Cognos"], - classifiers=[ - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Natural Language :: English", - ], - install_requires=[ - "ijson", - "requests", - "pytz", - 'requests_negotiate_sspi;platform_system=="Windows"', - "mdxpy>=1.3.1", - "networkx", - ], - extras_require={ - "pandas": ["pandas"], - "dev": [ - "pytest", - "pytest-xdist", - "python-dateutil", - "black", - "ruff", - ], - }, - python_requires=">=3.6", )